DEV Community

xVE
xVE

Posted on

Analyzing Akamai BMP 4.1.3 - Part 2

PART 1
This article was uploaded to a self-published website because the engine here doesn't support KAPEX or LATEX. If you want to see a cleaner and more readable article, go to https://xve-e.github.io/2026/03/23/analyzing-akamai-bmp-part-2.html .
App showcase: Iberia 14.81.0
IDA Pro: 9.3

1. Analyzing the post-decompress lib

The decompiler often misidentified the number of arguments and return values for the polymorphic dispatcher, i used a secret technique to get around this using asm.

As we saw earlier, sub_25E0AC is a large polymorphic dispatcher.

sub_25E0AC(string_ptr)                    → strlen or string copy
sub_25E0AC(string_ptr, 0x8641, 0xFFFFFFFF) → string deobfuscation
sub_25E0AC(plaintext, output, len, key, iv) → AES-128-CBC encrypt
sub_25E0AC(qword_2466A8)                  → MT19937 extract
Enter fullscreen mode Exit fullscreen mode

graphsub_25E0AC

JNI Entry Points

VA Java Name Purpose
0x9D394 SensorDataBuilder.buildN Main entry: serialize + encrypt sensor data
0x9D074 SensorDataBuilder.encryptKeyN Generate session ID (20-char base62 → base64)
0xA0144 addOne Set MT flag dword_247A38
0xA0150 sampleTest Set MT flag dword_247A3C
0xA015C presentData Set MT flag dword_247A40
0xA0168 testOne Set MT flag dword_247A44

Internal Functions (Called by buildN)

VA Size Name Purpose
0x9ED74 0xAFC sub_9ED74 Core encrypt+format: MT → AES → HMAC → b64 → header assembly
0x9EAF0 0x34 sub_9EAF0 Crypto context singleton getter
0x9EB24 0x250 sub_9EB24 One-time key initialization
0x9E840 0x1D0 sub_9E840 LCG-based string deobfuscation
0x9E660 0xF8 sub_9E660 RSA_public_encrypt wrapper
0x9E594 0x90 sub_9E594 HMAC-SHA256 wrapper
0x9E620 0x24 sub_9E620 RAND_bytes wrapper
0x9E75C 0xE0 sub_9E75C Base64 encode (OpenSSL BIO)
0x9FAD0 0x130 sub_9FAD0 MT19937 bounded random
0x9F91C 0xFC sub_9F91C C++ stringstream initialization
0x9FDF8 0xD0 sub_9FDF8 Stringstream write (string)
0x1CFD2C 0x158 sub_1CFD2C Stringstream write (integer)
0x9DBD0 0x98 sub_9DBD0 JNI CallIntMethodV wrapper

1.1. Serialization Pipeline

Function: Java_com_cyberfend_cyfsecurity_SensorDataBuilder_buildN @ 0x9D394

Property Value
Size 0x83C (2108 bytes)
Input ArrayList<Pair<String, String>> — 28 entries from Java
Output Encrypted header string "6,a,{rsa1},{rsa2}${b64}${timing}"

sub_9D394graph

Step-by-step from Assembly

  • Phase 1: JNI Environment Setup (0x9D3CC–0x9D54C)

The function begins by resolving all required JNI references prior to data extraction:

FindClass("android/os/Build$VERSION")
GetStaticFieldID("SDK_INT", "I")
GetStaticIntField()                          must be >= 1 (sanity check)
FindClass("java/util/ArrayList")
GetMethodID("size",  "()I")                  pair_count
GetMethodID("get",   "(I)Ljava/lang/Object;")
FindClass("android/util/Pair")
GetFieldID("first",  "Ljava/lang/Object;")
GetFieldID("second", "Ljava/lang/Object;")
Enter fullscreen mode Exit fullscreen mode

  • Phase 2: Pair Vector Extraction (0x9D554–0x9D6A8)

Each element of the input ArrayList is extracted via a JNI iteration loop over indices $i \in [0,\ \text{pair_count} - 1]$:

pair  = ArrayList.get(i)
key   = GetStringUTFChars(pair.first)
value = GetStringUTFChars(pair.second)
Enter fullscreen mode Exit fullscreen mode

Pairs are stored in a C++ vector as 48-byte structs with the layout:

$$\text{struct PairEntry} = \underbrace{\text{key}}{\text{std::string, 24 B}} \;|\; \underbrace{\text{value}}{\text{std::string, 24 B}}$$

The 24-byte std::string layout corresponds to the standard libc++ small-string-optimized (SSO) representation on AArch64.


  • Phase 3: Output Initialization from First Pair (0x9D6F8–0x9D724)

The value field of the first pair (index 0) is used as the initial content of the serialized output buffer; its key is discarded. In the observed execution context, this value is the SDK version string "4.1.3".


  • Phase 4: Separator String Deobfuscation (0x9D728–0x9D768)

The separator literal is stored obfuscated in .rodata and decoded at runtime. The encoded form "WUfOL#f}+" is passed to a .pb dispatcher alongside the constant 0x8641, which drives a substitution-based decode:

9d728  ADRL  X9, aWufolF         ; load encoded string "WUfOL#f}+"
9d738  STRB  W8, [SP, #var_90]   ; SSO length field = 9
9d744  STUR  X9, [SP, #var_90+1] ; copy encoded bytes onto stack
9d748  ADD   X8, SP, #var_78     ; destination buffer = var_78
9d750  MOV   W1, #0x8641         ; deobfuscation constant
9d754  MOV   W2, #0xFFFFFFFF     ; flag
9d758  BL    sub_25E0AC          ; decode → "-1,2,-94," stored in var_78
Enter fullscreen mode Exit fullscreen mode

The decode mechanism and constant 0x8641 are identical to those observed in buildVerification() , indicating a shared obfuscation scheme across the serialization pipeline.


  • Phase 5: Pair Serialization Loop (0x9D76C–0x9D84C)

Pairs at indices $i \in [1,\ \text{pair_count} - 1]$ are serialized in order. Each iteration appends to the output buffer with the pattern:

$$\text{output} \mathrel{+}= \text{separator} \;|\; \text{key}_i \;|\; \texttt{","} \;|\; \text{value}_i$$

The struct stride is 48 bytes (ADD X22, X22, #0x30).

9d7b0  ADD   X0, SP, #var_60    ; output string ptr
9d7b4  BL    sub_25E0AC         ; append(output, separator)
9d7d4  LDRB  W9, [X8]           ; load key SSO flag (offset +0)
9d7f0  BL    sub_1CDC48         ; append(output, key)
9d7f8  MOV   X1, X20            ; X20 = "," literal @ 0x51163
9d7fc  BL    sub_1CE08C         ; append(output, ",")
9d81c  LDRB  W9, [X8, #0x18]    ; load value SSO flag (offset +24)
9d83c  BL    sub_1CDC48         ; append(output, value)
9d840  ADD   X22, X22, #0x30    ; advance to next struct (stride = 48)
9d844  ADD   X24, X24, #1       ; i++
9d84c  B.NE  loop_start
Enter fullscreen mode Exit fullscreen mode

  • Phase 6: SECURITY_PATCH Field Appended via JNI (0x9D850–0x9D92C)

After exhausting the input vector, the function retrieves Build.VERSION.SECURITY_PATCH directly from the Android runtime via JNI reflection, bypassing the Java-side pair list entirely:

9d888  ADRL  X2, aSecurityPatch    ; field name "SECURITY_PATCH"
9d890  ADRL  X3, aLjavaLangStrin   ; descriptor "Ljava/lang/String;"
9d8a4  BLR   X8                    ; GetStaticFieldID
9d8bc  BLR   X8                    ; GetStaticObjectField
9d8d4  BLR   X8                    ; GetStringUTFChars → X20 = e.g. "2025-04-01"
Enter fullscreen mode Exit fullscreen mode

The value is appended with a dedicated tag identifier -164:

$$\text{output} \mathrel{+}= \texttt{"-1,2,-94,"} \;\|\; \texttt{"-164,"} \;\|\; \text{SECURITY\_PATCH}$$

9d900  BL    sub_25E0AC            ; append(output, "-1,2,-94,")
9d904  ADRL  X1, a164              ; literal "-164"
9d910  BL    sub_25E0AC            ; append(output, "-164")
9d914  ADRL  X1, asc_51163         ; ","
9d920  BL    sub_25E0AC            ; append(output, ",")
9d928  MOV   X1, X20               ; SECURITY_PATCH string
9d92c  BL    sub_1CE08C            ; append(output, security_patch)
Enter fullscreen mode Exit fullscreen mode

The native-side injection of SECURITY_PATCH — absent from the Java-supplied pair list — constitutes an integrity signal that cannot be trivially spoofed by intercepting the Java layer alone.


  • Phase 7: Encryption and Formatting (0x9D930–0x9D96C)

The assembled plaintext is passed to the shared cryptographic context singleton and encrypted via sub_9ED74, which implements the same AES-128-CBC + HMAC-SHA256 pipeline.

9d930  BL    sub_9EAF0     ; acquire crypto context singleton
9d964  ADD   X8, SP, #var_90   ; output buffer
9d968  ADD   X1, SP, #var_B0   ; plaintext string
9d96c  BL    sub_9ED74     ; encrypt + assemble → "6,a,{rsa1},{rsa2}${b64}${timing}"
Enter fullscreen mode Exit fullscreen mode

  • Phase 8: Return to Java (0x9D99C–0x9D9AC)

The formatted output string is converted to a managed Java string and returned to the caller:

9d99c  BLR   X8           ; NewStringUTF(output_cstr)
9d9ac  ; return jstring
Enter fullscreen mode Exit fullscreen mode

Serialization Format

4.1.3-1,2,-94,-90,{val}-1,2,-94,-91,{val}-1,2,-94,-70,-1,2,-94,-80,...-1,2,-94,-164,{SECURITY_PATCH}
Enter fullscreen mode Exit fullscreen mode
  • First: SDK version (pair 0 value only)
  • Then: {separator}{key},{value} for each remaining pair
  • Last: {separator}-164,{SECURITY_PATCH} (from JNI)

2. Cryptographic Pipeline — sub_9ED74

Function: sub_9ED74 @ 0x9ED74

Property Value
Size 0xAFC (2812 bytes)
Calling convention __usercall — X8 = output ptr, X0 = crypto context, X1 = plaintext
Input Crypto context (from sub_9EAF0), serialized sensor plaintext
Output "6,a,{rsa1},{rsa2}${base64(IV+cipher+HMAC)}${timing}"

  • Phase 1: Separator Decode (0x9EDC8–0x9EE50)

Employs the same deobfuscation routine as buildN : the encoded string "WUfOL#f}+" is decoded with constant 0x8641, yielding the separator "-1,2,-94,". The result is stored in var_180 for later concatenation with the suffix "-170" during payload assembly.


  • Phase 2: MT19937 PRNG Initialization (0x9EE64–0x9EE80)

Two atomic guard flags gate one-time initialization of the Mersenne Twister state:

  • byte_2466A0 — controls MT state array initialization
  • byte_247A30 — controls seeding from clock_gettime(CLOCK_REALTIME)

The seeding routine at 0x9F698–0x9F6E4 implements the standard MT19937 initialization recurrence:

$$\text{state}[i] = i + 1812433253 \cdot \left(\text{state}[i-1] \oplus \left(\text{state}[i-1] \gg 30\right)\right), \quad i \in [1, 623]$$

; state[0] = seed (from clock_gettime)
9f6ac  STR   X9,  [qword_2466A8]
9f6b0  MOV   X10, #1                  ; i = 1
loop:
9f6c0  MUL+ADD  X9 = i + 1812433253 * (state[i-1] ^ (state[i-1] >> 30))
9f6c4  STR   X9,  [qword_2466A8 + i*8]
9f6cc  ADD   X10, X10, #1
9f6d0  CMP   X10, #624
9f6d4  B.NE  loop
9f6e0  STR   XZR, [qword_247A28]      ; index = 0
Enter fullscreen mode Exit fullscreen mode

  • Phase 3: Verification Value Generation (0x9EE84–0x9F060)

Five sampling ranges are loaded from .rodata:

Variable Range Conditional Flag Semantic
var_188 [1, 1000] — (unconditional) base values
var_190 [1, 6] dword_247A38 (addOne) optional
var_198 [1, 7] dword_247A3C (sampleTest) optional
var_1A0 [1, 8] dword_247A40 (presentData) optional
var_1A8 [1, 4] dword_247A44 (testOne) optional

Sampling sequence (registers mapped from assembly):

v13 = MT_rand(1, 1000)
v14 = MT_rand(1, 6)   if dword_247A38 == 1 else 0
v15 = MT_rand(1, 1000)
v16 = MT_rand(1, 7)   if dword_247A3C == 1 else 0
v17 = MT_rand(1, 1000)
v18 = MT_rand(1, 8)   if dword_247A40 == 1 else 0
v19 = MT_rand(1, 1000)
v20 = MT_rand(1, 4)   if dword_247A44 == 1 else 0
Enter fullscreen mode Exit fullscreen mode

Four verification values are derived through a chained XOR construction:

$$\text{val}1 = v{14} + 7 \cdot v_{13}$$

$$\text{val}2 = \left(v{16} + 8 \cdot v_{15}\right) \oplus \text{val}_1$$

$$\text{val}3 = \left(v{18} + 9 \cdot v_{17}\right) \oplus \text{val}_2$$

$$\text{val}4 = \left(v{20} + 5 \cdot v_{19}\right) \oplus \text{val}_3$$

The AArch64 encoding uses shift-and-subtract/add idioms for constant multiplication:

; val1 = v14 + 7*v13
9efe4  LSL   W8, W22, #3         ; W8 = v13 * 8
9efe8  SUB   W8, W8,  W22        ; W8 = v13*8 - v13 = v13*7
9efec  ADD   W22, W24, W8        ; W22 = v14 + 7*v13

; val2 = (v16 + 8*v15) ^ val1
9f00c  LSL   W8, W23, #3
9f010  ADD   W8, W26, W8
9f014  EOR   W22, W8,  W22

; val3 = (v18 + 9*v17) ^ val2
9f030  ADD   W8, W25, W25,LSL#3  ; W8 = v17 + v17*8 = v17*9
9f034  ADD   W8, W28, W8
9f038  EOR   W22, W8,  W22

; val4 = (v20 + 5*v19) ^ val3
9f054  ADD   W8, W27, W27,LSL#2  ; W8 = v19 + v19*4 = v19*5
9f058  ADD   W8, W19, W8
9f05c  EOR   W1,  W8,  W22
Enter fullscreen mode Exit fullscreen mode

The final verification string is serialized via a std::stringstream-equivalent dispatcher with the form "val1,val2,val3,val4".


  • Phase 4: Plaintext Payload Assembly (0x9F070–0x9F0E4)

The plaintext buffer is constructed by sequential concatenation:

$$\text{plaintext} \mathrel{+}= \texttt{"-1,2,-94,"} \;|\; \texttt{"-170,"} \;|\; \text{val}_1\texttt{,val}_2\texttt{,val}_3\texttt{,val}_4$$

9f090  BL    sub_1CDC48         ; append(plaintext, "-1,2,-94,")
9f0a0  ADRL  X1, a170           ; literal "-170"
9f0ac  BL    sub_25E0AC         ; append(plaintext, "-170")
9f0b0  ADRL  X1, asc_51163      ; ","
9f0bc  BL    sub_1CE08C         ; append(plaintext, ",")
9f0e4  BL    sub_1CDC48         ; append(plaintext, "val1,val2,val3,val4")
Enter fullscreen mode Exit fullscreen mode

  • Phase 5: AES-128-CBC Encryption (0x9F110–0x9F16C)

A fresh 16-byte IV is generated per invocation via RAND_bytes(). The AES key and IV are read from the crypto context at offsets ctx[0] and ctx[8] respectively. PKCS#7 padding is applied implicitly. The output layout is:

$$\text{ciphertext_blob} = \text{IV}{16} \;|\; \text{AES\text{-}128\text{-}CBC}(\text{plaintext},\; k{\text{AES}},\; \text{IV})$$

The IV is prepended via a single 128-bit NEON store (STR Q0), and the output buffer is allocated as encrypted_len + 17 bytes.

9f114  BL    sub_9E620          ; RAND_bytes(16) → ctx[8]
9f12c  LDP   X3, X4, [X20]     ; X3 = AES key ptr, X4 = IV ptr
9f160  LDR   Q0, [X8]          ; load IV (16 bytes, NEON Q-register)
9f168  STR   Q0, [X0], #0x10   ; prepend IV; advance pointer
9f16c  BL    sub_25E0AC        ; memcpy(buf+16, ciphertext, encrypted_len)
Enter fullscreen mode Exit fullscreen mode

  • Phase 6: HMAC-SHA256 Authentication (0x9F190–0x9F1A4)

The authentication tag is computed over the full IV || ciphertext blob, using a 32-byte HMAC key at ctx[16]:

$$\text{tag} = \text{HMAC\text{-}SHA256}!\left(k_{\text{HMAC}},\; \text{IV} | \text{ciphertext}\right)$$

9f190  ADD   W26, W25, #0x10   ; input_len = encrypted_len + 16
9f194  LDR   X2,  [X20, #0x10] ; HMAC key ptr (32 bytes) from ctx[16]
9f198  MOV   X0,  X22          ; data = IV || ciphertext
9f19c  MOV   W1,  W26          ; data length
9f1a0  BL    sub_9E594         ; HMAC-SHA256 → X23 (32-byte tag)
Enter fullscreen mode Exit fullscreen mode

  • Phase 7: Final Binary Assembly and Base64 Encoding (0x9F1B8–0x9F204)

The authenticated ciphertext is assembled into a contiguous binary blob:

$$\text{output_bin} = \text{IV}{16} \;|\; \text{ciphertext}{n} \;|\; \text{HMAC}_{32}$$

Total length: $n + 48$ bytes. The 32-byte HMAC is written atomically via two 128-bit NEON registers (LDP Q0, Q1 / STP Q0, Q1). The blob is then Base64-encoded:

$$\text{b64} = \text{Base64}!\left(\text{output_bin}\right)$$

9f1d8  LDP   Q0, Q1, [X23]     ; load 32-byte HMAC (two NEON Q-registers)
9f1dc  ADD   X8,  X24, X19     ; offset = base + (IV + ciphertext length)
9f1e0  STP   Q0, Q1, [X8]      ; store 32-byte HMAC
9f1f8  ADD   W1,  W25, #0x30   ; total_len = encrypted_len + 48
9f200  BL    sub_9E75C         ; base64_encode(output_bin, total_len) → X25
Enter fullscreen mode Exit fullscreen mode

  • Phase 8: Final Header String Construction (0x9F21C–0x9F4F4)

The complete header value is assembled through three logical segments:

Segment A — Key material header:

$$\texttt{"6,a,{base64(RSA(}k_{\text{AES}}\texttt{))},{base64(RSA(}k_{\text{HMAC}}\texttt{))}"}$$

where ctx[24] and ctx[32] hold the RSA-wrapped, Base64-encoded symmetric keys.

Segment B — Timing telemetry:

$$\texttt{"{}\Delta t_{\text{encrypt}}\texttt{µs},{}\Delta t_{\text{hmac}}\texttt{µs},{}\Delta t_{\text{b64}}\texttt{µs}"}$$

Derived from three consecutive clock_gettime delta measurements converted to microseconds via FCVTMS.

Final concatenation:

$$\text{header} = \text{Segment_A} \;|\; \texttt{"\$"} \;|\; \text{b64} \;|\; \texttt{"\$"} \;|\; \text{Segment_B}$$

9f4a4  BL    sub_1CDC48        ; output = Segment_A
9f4ac  LDR   X1, [off_245020]  ; "$"
9f4b4  BL    sub_25E0AC        ; append "$"
9f4c0  BL    sub_1CE08C        ; append b64 (X25)
9f4c4  LDR   X1, [off_245020]  ; "$"
9f4cc  BL    sub_25E0AC        ; append "$"
9f4f4  BL    sub_25E0AC        ; append Segment_B (timing)
Enter fullscreen mode Exit fullscreen mode

3. Mersenne Twister Verification

3.1 MT19937 Implementation

The native code employs a standard MT19937 PRNG, confirmed by three structural markers: a 624-element state array at qword_2466A8, the initialization multiplier 1812433253, and a twist operation matching the reference implementation.

3.2 sub_9FAD0: Bounded Random Sampling

This function implements rejection sampling for uniform distribution over the range [lo, hi]:

int mt_rand_range(int lo, int hi) {
    int range = hi - lo + 1;
    int result;
    do {
        result = mt_extract() % range;
    } while (result >= range);   // rejection to ensure uniformity
    return lo + result;
}
Enter fullscreen mode Exit fullscreen mode

3.3 Verification String Serialization

The four values $\text{val}_{1..4}$ are written to a C++ std::stringstream as comma-separated decimal integers:

  • sub_25E0AC(stream, value) — write integer
  • sub_9FDF8(stream, ",", 1) — write separator
  • sub_1CFD2C(stream, value) — write final integer

Output format: "val1,val2,val3,val4" (e.g., "427,1052,5765,4661").

3.4 Flag Behavior

The four control flags (dword_247A38, dword_247A3C, dword_247A40, dword_247A44) are set by the JNI entry points addOne, sampleTest, presentData, and testOne respectively. Their static value in the binary is 0xFFFFFFFF (not equal to 1), rendering all conditional terms zero by default. At runtime, the Java layer may activate individual flags by calling the corresponding JNI setter with argument 1. When enabled, a bounded random offset is added to each accumulator term. For the initial sensor data generation these flags are typically inactive, yielding $v_{14} = v_{16} = v_{18} = v_{20} = 0$.


4. Key Initialization — sub_9EB24

Function: sub_9EB24 @ 0x9EB24

Invoked when ctx[40] == 0 (uninitialized). Executes once per process lifetime.


  • Phase 1: Key Buffer Allocation

Three heap buffers are allocated from the crypto context:

9eb5c  BL    sub_25E0AC    ; ctx[0]  = malloc(17)  → AES key buffer  (16 B + null)
9ec54  BL    sub_20AF7C    ; ctx[8]  = malloc(17)  → IV buffer       (16 B + null)
9ec64  BL    sub_25E0AC    ; ctx[16] = malloc(33)  → HMAC key buffer (32 B + null)
Enter fullscreen mode Exit fullscreen mode

  • Phase 2: RSA Public Key Deobfuscation

268 bytes of obfuscated key material are loaded from off_245030 and decoded via the LCG substitution cipher with seed 63:

9eb7c  LDR   X3, [off_245030]   ; load 268-byte obfuscated PEM blob
9ebf4  BL    sub_9E840          ; deobfuscate(material, seed=63, flag=-1) → PEM
Enter fullscreen mode Exit fullscreen mode

  • Phase 3: Session Key Generation and RSA Encryption
; AES key: 16 random bytes, RSA-encrypted, Base64-encoded → ctx[24]
9ec38  BL    sub_9E660     ; RSA_public_encrypt(pem_key, rand_16)
9ec48  BL    sub_9E75C     ; base64(encrypted) → ctx[24]

; HMAC key: 32 random bytes, RSA-encrypted, Base64-encoded → ctx[32]
9ec9c  BL    sub_9E660     ; RSA_public_encrypt(pem_key, rand_32)
9eca8  BL    sub_9E75C     ; base64(encrypted) → ctx[32]

; Mark context as initialized
9ecd4  STRB  #1, [X2, #40] ; ctx[40] = 1
Enter fullscreen mode Exit fullscreen mode

5. String Deobfuscation

5.1 Native: LCG Substitution Cipher (sub_9E840)

Used for RSA key deobfuscation (off_245030) and separator decoding ("WUfOL#f}+").

The charset is constructed from 92 printable ASCII characters in the range [32, 126], excluding " (0x22), ' (0x27), and \ (0x5C). For each input byte, a linear congruential generator advances the state and produces a reverse-lookup shift:

def build_charset():
    return [ch for ch in range(32, 127) if ch not in (34, 39, 92)]

def lcg_decode(data: bytes, seed: int) -> bytes:
    charset = build_charset()
    n   = len(charset)
    lcg = seed
    out = []
    for byte in data:
        idx   = charset.index(byte)
        shift = ((lcg >> 8) & 0xFFFF) % n
        out.append(charset[(idx - shift + n) % n])
        lcg = (lcg * 65793 + 4282663) & 0x7FFFFF
    return bytes(out)
Enter fullscreen mode Exit fullscreen mode

5.2 Java: XOR Table Cipher (C0018K.kpR)

Used for all Java-side string deobfuscation. A pseudo-random table of 32 768 entries is derived from a feedback recurrence seeded at 3, then applied as a positional XOR stream:

def build_table(size: int = 32768) -> list[int]:
    table, prev = [0] * size, 3
    for i in range(size):
        val    = prev ^ i
        prev   = (prev + val + 88) % 63   # constant +88 in C0018K.kpR
        table[i] = prev
    return table

def decode_kpR(encoded: str) -> str:
    table = build_table()
    return ''.join(chr(ord(c) ^ table[i]) for i, c in enumerate(encoded))
Enter fullscreen mode Exit fullscreen mode

Note: The method CYFMonitor.KkI uses an additive constant of +11 with a distinct table. The two routines are not interchangeable.


6. Java-Side Architecture

6.1 CYFManager.buildSensorData()

The primary Java entry point orchestrates the full sensor data pipeline:

  1. Evaluate event count thresholds to select fast path or full path
  2. Collect data from 12+ sensor subsystems
  3. Assemble a LinkedHashMap<String, String> with ~25 entries
  4. Convert to ArrayList<Pair<String, String>>
  5. Invoke native buildN(pairs) → encrypted header
  6. Append $[3]..$[6] sections: Proof-of-Work, CCA token, signal, metadata

6.2 Fast Path (event count < 16)

When both GA and IIT accumulators hold fewer than 16 events and EG.D.isValid() is true, the function returns cached sensor data from EG.D.get(). Every fifth call triggers key rotation via encryptString(). Cache access is serialized via AtomicBoolean.compareAndSet() with a 5-second reentrance lock.

6.3 Full Path (event count ≥ 16)

  1. Call buildN() with suffix ,0 → return this header
  2. If GA ≥ 16 OR IIT ≥ 16: call buildN() again with suffix ,1 → cache via EG.D.put()
  3. If GA ≥ 128 OR IIT ≥ 128: reset both accumulators

6.4 Sensor Collector Classes (java)

Class Alias Data Collected
C0005GA EG.GA Accelerometer, gyroscope, magnetometer (orientation)
C0009GN EG.GN/McP.IIT Motion analysis, jerk derivatives (9 axes)
C0022KG EG.KG Touch events (DOWN/MOVE/UP with coordinates)
C0018K EG.K Text input events (keystroke timing)
C0054Z EG.Z EditText field metadata
C0008GK EG.GK Activity lifecycle (resume/pause events)
C0006GE EG.GE Device info (40+ fields)
C0051Vw EG.Vw/KWS System fingerprint, device ID
C0001C EG.C DCI/JavaScript bridge (WebView challenges)
C0002D EG.D CPR signal cache
C0042U EG.U Proof-of-Work responses
C0038M EG.M CCA challenge tokens

I'm going to rest here, wait for part 3.

Top comments (0)