WeChat 8.7.0 ClientCheckData White-Box AES: From AES-CBC to 03/08 Table Interpreter Recovery

3963 words
20 minutes

WeChat 8.7.0 ClientCheckData White-Box AES: From AES-CBC to 03/08 Table Interpreter Recovery

The first two RQTX articles focused on VM runtime observation, trace aggregation, and semantic hash recovery. This third article moves to another piece of the WeChat 8.7.0 request-protection surface: the white-box AES used around client check data.

This component does not look like a normal AES-CBC(key, iv, data) library call. It behaves more like a table interpreter. The runtime code is mostly stable, while the concrete algorithm instance is carried by a PB template. That template contains an IV, TableKey, TableValue, FinalTable, and a few profile fields. The interpreter feeds those tables round by round and eventually emits ciphertext that is semantically equivalent to AES-128-CBC.

This is a common industry pattern. A protocol wants to preserve AES-CBC semantics, while the client wants to avoid shipping a raw AES key as a normal constant. The key schedule, SBox, MixColumns contribution, and final round key material are folded into lookup tables. The key does not appear as a plain source constant, but the computation still needs the equivalent material somewhere.

The article starts from standard AES-CBC, then peels into the 03/08 table interpreter, how traces reveal the algorithm boundary, how key/IV recovery works, and how the edge challenge vector can be used for reverse validation.

Key Findings#

  • The client check data encryption path is semantically AES-128-CBC: data is processed in 16-byte blocks, each block is XORed with the IV or previous ciphertext block, then encrypted.
  • The runtime is not a normal AES implementation. It is a PB-template-driven white-box table interpreter: runtime code = interpreter, PB template = concrete AES instance.
  • 00000008 has a weak FinalTable shape. Each byte lane can be fitted as an AES final-round SBox lane, yielding the round-10 key and then the AES-128 master key through inverse key schedule.
  • 00000003 adds byte-level encoding. Its FinalTable no longer directly reveals the final-round key, but the hidden XOR coordinates can still be recovered from TableValue, allowing first-round TableKey fitting.
  • In 8.7.0, the VER08 template is best modeled as descriptor plus template blob plus length plus transform gateway. The gateway materializes protobuf wire records into a typed object before the later OLLVM-style table interpreter consumes them.
  • The IV is not a cryptanalytic problem. It is a CBC chaining value carried by the template. Stronger protection needs per-message IV policy, template integrity, stronger table encoding, and cross-byte fusion.
  • The page challenge is generated by an edge function. Every refresh produces a random hex input and the corresponding 03/08 AES-CBC ciphertexts for validation against a hook, offline interpreter, or recovered standard AES implementation.

Edge-Generated White-Box AES Challenge#

The vector below is generated when the page loads. Every refresh creates a new 24-byte hex input. The server applies the client check data block-padding semantics and returns ciphertext for both 00000008 and 00000003 profiles.

The browser receives only the input, lengths, padding mode, and ciphertexts. The profile key/IV material is used server-side.

If the card says the server profile is not configured, the deployment has not received the 03/08 equivalent profiles yet. The edge function fails closed and does not return placeholder ciphertext.

White-box AES Challenge Generating
input_kind
hex_bytes
input_hex
loading
input_length
- bytes
padding
-
padded_length
- bytes
00000008 cipher_hex
-
00000008 profile_source
-
00000003 cipher_hex
-
00000003 profile_source
-
generated_at
-

Start With Plain AES-CBC#

White-box AES is easier to understand once the plain CBC layer is clear:

ciphertext = AES-CBC-Encrypt(key, iv, plaintext)

CBC mode is short:

C[-1] = IV
for each 16-byte plaintext block P[i]:
X[i] = P[i] xor C[i-1]
C[i] = AES_Encrypt(K, X[i])

AES-128 block encryption then follows the familiar round structure:

state = input
state = AddRoundKey(state, K0)
for round 1..9:
state = SubBytes(state)
state = ShiftRows(state)
state = MixColumns(state)
state = AddRoundKey(state, Kround)
state = SubBytes(state)
state = ShiftRows(state)
state = AddRoundKey(state, K10)

SubBytes is the byte SBox, ShiftRows rotates rows in the 4x4 state, MixColumns linearly mixes each column, and AddRoundKey XORs round-key bytes into the state. AES-128 has eleven round keys, K0..K10.

A normal implementation often exposes AES constants, SBox tables, T-tables, key schedule code, or a library call boundary. A white-box implementation tries to remove those obvious shapes from source code by folding them into tables.

The White-Box Intuition: Turn Formulae Into Tables#

A normal AES round can be viewed as:

output_column = MixColumns(SubBytes(input_column xor round_key_column))

A white-box implementation precomputes pieces of that relation:

TableKey[position][input_byte] -> contribution bytes

The runtime uses the current state byte as a lookup index, obtains contribution bytes, and then combines those contributions through another table:

TableValue[encoded_contribution_0, encoded_contribution_1, ...] -> output_byte

The source no longer contains a clear SBox[x xor key] expression, nor a visible MixColumns formula such as 2*x ^ 3*y ^ z. It mostly contains indexing, offsets, and byte movement.

For the 03/08 family, the core model is:

runtime code = generic table interpreter
PB template = concrete AES-CBC instance

Changing the PB template changes the profile while the interpreter remains the same.

ClientCheckData Encryption Flow#

At the outer layer, the dataflow is:

flowchart TD A[client check data bytes] --> B[block padding] B --> C[split into 16-byte blocks] C --> D[CBC XOR with IV or previous ciphertext] D --> E[white-box AES table interpreter] E --> F[ciphertext block] F --> G[next CBC chaining block]

Inside the interpreter:

flowchart TD A[plain block xor chaining block] --> B[Transpose to AES-like state] B --> C[visible ShiftRows] C --> D[round 0 TableKey] D --> E[round 0 TableValue] E --> F[ShiftRows] F --> G[round 1..8 repeat] G --> H[FinalTable] H --> I[TransposeBack] I --> J[cipher block]

Several boundaries are useful during analysis:

  • CBC XOR is visible, so block boundaries are clear.
  • Transpose and TransposeBack convert between byte arrays and AES-like state layout.
  • ShiftRows remains visible and appears after each round.
  • The nine main rounds become TableKey -> TableValue -> ShiftRows.
  • The AES final round is collapsed into FinalTable.

This is not AES disappearing. It is AES represented as a table interpreter.

The 8.7.0 VER08 Template Boundary#

In the 8.7.0 client check data path, VER08 should not be understood as an isolated AES_encrypt() function. The cleaner boundary is:

collect CCD fields
-> build dataBody
-> build middle wrapper with field 3 = dataBody
-> zlib-compatible compression
-> VER08 AES-CBC-compatible envelope
-> outer ccData

The white-box AES layer is only the envelope layer. If a hook or offline validator mixes raw business fields, the pre-compression wrapper, compressed bytes, and white-box entry bytes, it will produce false algorithm mismatches.

The 8.7.0 VER08 entry can be modeled with four pieces:

template descriptor
template blob
template length
transform gateway

IDA Callsite Reference#

In IDA, the main collector callsite has this shape:

ADRL X8, dword_11D077548
LDR W21, [X8]
...
ADRL X1, unk_11D036528
ADRL X2, unk_11D037454
MOV X3, X21
MOV X4, X20
BL sub_1129E7A20

That gives a direct parameter map:

dword_11D077548 -> template length word
unk_11D036528 -> template descriptor
unk_11D037454 -> template blob
X21 -> template length value
X20 -> scratch / arena-like context
sub_1129E7A20 -> VER08 template/table expansion gateway

In the current 8.7.0 view, the template length is 0x400f3. That is not ciphertext length. It is the full VER08 template blob length, and it lines up with the later field layout and the TableKey/TableValue/FinalTable sizes.

The collector passes descriptor, blob, length, and scratch arena into the gateway. The first gateway stage is not an AES round. It parses protobuf wire records, maps them through descriptor rows, and materializes typed object slots. Large length-delimited fields are represented as source pointer plus length first; the interpreter reads the table windows later.

That prevents a common misread. Looking only at gateway size, switches, and control flow, it may appear to be one huge AES function. Semantically it is closer to:

wire-format template parser
-> descriptor-backed object materializer
-> table expansion / transform gateway

No ordinary AES-CBC library boundary is visible on this path. The AES semantics live in template fields, table windows, and interpreter access rhythm.

Gateway Parser Details#

The gateway can be modeled with this signature:

void sub_1129E7A20(
VerObject *out,
VerDescriptor *descriptor,
uint8_t *blob,
int blob_len,
Arena *arena_or_scratch);

Its first stage parses the template blob into 12-byte wire records:

struct WireRecord {
uint32_t tag; // protobuf wire tag, field_number = tag >> 3
uint32_t lo; // scalar low bits or value start offset
uint32_t hi; // scalar high bits or value end offset
};

The important parser behavior is:

initial record storage: stack scratch, capacity 18
growth path: heap array, capacity grows in 0x40-record chunks
record size: 0x0c
supported wire types: 0, 1, 2, 5
rejected wire types: 3, 4
length-delimited values: stored as source offsets, not copied immediately

The second stage is descriptor-driven. The descriptor points to a fixed-size entry array, and the current top-level entries can be read as:

entry array pointer: 0x11d036108
entry count: 22
entry size: 0x30
fields 1,2: type 12, flag 1
field 3: type 5, flag 1
fields 4-8: type 12, flag 0
fields 9-12: type 12, flag 1
fields 13-21: type 12, flag 0
field 50: type 13, flag 0

type 12 handles length-delimited bytes/string-like fields, type 5 handles a scalar lane, and type 13 handles profile scalar fields such as field 50. Materialized fields are then inserted into a hash/table-like object. Useful gateway ranges to label are:

0x1129ea26c..0x1129ea288
type-12 bytes/slice materialization:
stores source pointer and length
0x1129e8a54..0x1129e8a70
scalar/simple materialization into typed field object
0x1129ea000..0x1129ea044
nested-message handler exists,
but top-level VER08 table fields use bytes/slice slots

So sub_1129E7A20 is better labeled as the VER08 template/table expansion gateway, not as a compact AES round function. It is large, switch-heavy, and recursive in shape, but the current client check data path does not show an ordinary AES-CBC API boundary.

What Lives In The PB Template#

The template is a protobuf wire-format blob. Its fields can be understood by role:

FieldRoleReverse-engineering value
field 1profile/version stringSeparates 00000003, 00000008, and other table-generation strategies
field 216-byte IVCBC initial chaining block
field 3scalar control parameterIn 8.7.0 VER08, this acts as a round-plan control parameter
field 9/11small auxiliary tablesProfile metadata or helper data
field 10TableKey, nine roundsAbsorbs round key, SBox, and MixColumns contribution
field 12TableValue, nine roundsCombines contributions; close to XOR in 08, encoded binary combiner in 03
field 18FinalTableAbsorbs final SubBytes and last-round key material
field 50profile scalarTemplate parameter

For 8.7.0 VER08, the roles are more concrete:

field 1 version selector: "00000008"
field 2 16-byte CBC IV
field 3 scalar control parameter, observed as 0x3060
field 9 small required table block, 0x80 bytes
field 10 large TableKey block, 0x24000 bytes
field 11 small required table block, 0x40 bytes
field 12 large TableValue block, 0x1b000 bytes
field 18 FinalTable block, 0x1000 bytes
field 50 scalar profile parameter, observed as 3

The same descriptor family also reserves optional slots that are not the main table carriers for the current VER08 instance. This matters for version migration: when a later version shifts field numbers, start by recovering the roles of version, IV, large table blocks, scalar control, and gateway xrefs rather than searching for a raw key.

The large table sizes mirror AES structure:

TableKey = 9 rounds * 16 positions * 256 inputs * 4 contribution bytes
TableValue = 9 rounds * 16 output positions * 0x300-byte combiner windows
FinalTable = 16 positions * 256 entries

These are not arbitrary constants. They align with the 16-byte AES state, the nine rounds that include MixColumns, and the final round without MixColumns.

Single-Block Semantics#

After reducing implementation detail, one block can be described as:

state = Transpose(plainBlock xor chainingBlock)
state = ShiftRows(state)
for round = 0..8:
secTable = TableKey[round](state)
state = TableValue[round](secTable)
state = ShiftRows(state)
state = FinalTable(state)
cipherBlock = TransposeBack(state)

On 8.7.0 VER08, that plan can be grounded in field windows:

CBCXor with field 2 IV
Transpose4x4
ShiftRows
for round = 0..8:
read field 10 TableKey window at round * 0x4000
read field 12 TableValue window at round * 0x3000
ShiftRows
read field 18 FinalTable
TransposeBack4x4

That rhythm is what deserves trace labels: the 0x4000-sized TableKey round window, the 0x3000-sized TableValue round window, the stable ShiftRows interval, and the separate final table.

Compared with normal AES:

Normal AESTable interpreter
AddRoundKeyFolded into TableKey and FinalTable
SubBytesFolded into TableKey and FinalTable
MixColumnsSplit into contribution generation and TableValue combining
ShiftRowsStill visible
CBC XORStill visible
final round16 independent FinalTable byte lanes

Once a trace reveals these boundaries, the AES shape is already mostly exposed.

How Trace Locates The Algorithm#

Searching for AES, SBox, or CBC strings is usually weak. The better route is to compress behavior from the boundary inward.

First, observe length behavior. The ciphertext length is always a multiple of 16. Non-aligned input is padded to the next block; aligned input does not receive an extra full block. That padding behavior is a useful boundary marker.

Second, observe chaining. The first plaintext block is XORed with a 16-byte initial block, and later plaintext blocks are XORed with the previous ciphertext block. That is the CBC shell.

Third, observe state layout. After CBC XOR, the 16 bytes are transposed into a 4x4 state and then shifted by AES row rules. Transpose + ShiftRows is a strong AES fingerprint.

Fourth, observe the 64-byte intermediate buffer. Each TableKey round takes 16 state bytes and emits four contribution bytes for each one. That gives a 64-byte secTable, matching the idea of byte-level contribution generation.

Fifth, observe TableValue access. It does not simply XOR four bytes in source. Each output byte walks three nibble-level lookup stages. In 08 this is semantically close to XOR; in 03 the same shape is encoded into hidden coordinates.

The trace then becomes a named pipeline:

padding
-> CBC chaining
-> state transpose
-> visible ShiftRows
-> TableKey contribution
-> TableValue combiner
-> FinalTable
-> ciphertext

That naming is the move from runtime trace to semantic algorithm recovery.

00000008: Why FinalTable Leaks The Last-Round Key#

00000008 is the more direct profile. TableValue is table-driven, but semantically close to XOR. TableKey contributions are close to raw MixColumns contributions. The most important weakness is FinalTable.

Each final lane is:

FinalTable[position][x] -> y

For 08, each lane can be fitted as:

T[x] = SBox[x xor a] xor b

a is input-side material absorbed by the table, while b corresponds to a byte of the last-round key. Fitting is simple:

for a in 0..255:
b = T[0] xor SBox[a]
if T[x] == SBox[x xor a] xor b for every x:
recover b

Repeat that for 16 lanes, reorder from state position to ciphertext position, and the AES round-10 key is recovered:

FinalTable
-> 16 lane SBox fit
-> round10 key
-> inverse AES-128 key schedule
-> master key

The AES-128 key schedule is reversible. Given K10, walking the schedule backward yields the master key.

The IV is more direct: it is carried by the template as the CBC initial chaining block.

For 08, the recovery route is therefore:

field18 FinalTable -> round10 key -> master key
field2 IV -> IV

That is table-driven AES-CBC, not a strong extraction-resistant white-box design.

00000003: More White-Box-Like, Still Recoverable#

00000003 improves on 08. Its FinalTable no longer directly fits SBox[x xor a] xor b, and TableKey contribution bytes are not raw MixColumns contribution in the current byte coordinate.

It blocks the shortest 08-style route:

FinalTable -> round10 key

However, the complete truth tables are still present. Each 0x100 TableValue subtable can be treated as an encoded binary combiner:

encoded_op(encoded_a, encoded_b) -> encoded_sum

The underlying operation is still XOR, but input and output coordinates have been changed. The recovery route is to normalize the binary operation:

read binary combiner table
-> find left/right projections
-> recover hidden identity
-> build XOR-isomorphic coordinates
-> decode contribution bytes

Once the hidden XOR coordinates are recovered, first-round TableKey contribution can be fitted back to AES semantics:

decoded_contribution ~= affine(coeff * SBox[x xor keyGuess])

coeff is one of the AES MixColumns coefficients 01/02/03. For each key byte, the candidate that matches all 256 inputs is the recovered byte. Since the first round uses the AES master key directly, no inverse key schedule is needed.

The 03 recovery route is:

TableValue
-> recover hidden XOR coordinates
-> decode first-round contribution
-> fit coeff * SBox[x xor keyGuess]
-> master key
field2
-> IV

It is harder than 08, but still a PB-only recovery because the template carries the complete algorithm instance.

03 vs 08#

Dimension0000000800000003
FinalTableDirect SBox-lane fitEncoded permutation, not directly fitted
TableValueClose to XOR combinerEncoded binary combiner
TableKey contributionClose to raw MixColumns contributionByte-level encoded contribution
PB-only key recoveryVery short through FinalTableMedium effort through hidden XOR coordinates
Automated feature matchingEasyHarder
White-box strengthWeakBetter, still insufficient

03 raises the cost of simple pattern matching and static fitting. A script that directly searches SBox-like final lanes will work on 08 but not on 03. Still, the deeper structures remain:

  • complete truth tables are readable;
  • output bytes are not strongly fused;
  • round boundaries are stable;
  • CBC boundaries are stable;
  • the IV is template-provided;
  • there is no per-build binding material or runtime-derived table in the core semantics.

So 03 is better described as encoded table-driven AES, not a strong white-box design.

From Key/IV Recovery To Semantic Proof#

Recovering key and IV is only the first step. The useful proof is equivalence.

flowchart TD A[PB table interpreter] --> D[compare ciphertext] B[Recovered AES-CBC key and IV] --> D C[Independent random input vectors] --> A C --> B D --> E[semantic equivalence]

The first layer is single-block verification, including non-aligned inputs so padding participates in the computation.

The second layer is multi-block verification, which exercises CBC chaining. It is not enough to validate AES block encryption alone.

The third layer is profile verification. 08 and 03 take different recovery routes, but both should land on the same abstract interface:

encrypt_client_check_data(profile, plaintext_hex) -> cipher_hex

The page challenge is a lightweight entry point for this proof. Refresh the page, take the generated input and ciphertexts, and compare them with a hook boundary, an offline interpreter, or a recovered standard AES-CBC implementation.

Reverse Validation With A Hook#

The hook should not patch the return value. That would destroy the verification. The clean approach is to replace the input once at the encryption boundary, then observe the real ciphertext produced by the runtime path.

The boundary can be modeled as:

encrypt_client_check_data(input_bytes) -> cipher_bytes

Record four fields:

page.input_hex
runtime input after patch
runtime cipher_hex
page.cipher_hex

When all four align, the edge AES-CBC equivalent implementation, the local encryption boundary, and the recovered 03/08 profile are referring to the same semantics.

A Frida script should start read-only. Confirm argument length, output length, and timing first, then perform one controlled input replacement:

Use a four-step workflow:

  1. Observe first, do not write memory. Confirm the function receives envelope plaintext, not raw CCD fields or the pre-compression wrapper.
  2. Record the input pointer, input length, output location, and output length. Output length must be a multiple of 16; non-aligned input should only pad to the next block.
  3. Replace input once. The replacement length must exactly match the page input_hex length to avoid breaking the caller’s buffer lifetime.
  4. Read the real runtime ciphertext in onLeave, then compare it with the page 00000008 or 00000003 cipher_hex.

Jailbroken attach and Frida Gadget repackaging differ only in script loading, not in validation logic. In a jailbroken lab, attach after the process is stable. In a repackaged lab, let Gadget load the same script after initialization. The script should not publish hard-coded addresses; resolve slide and target boundaries inside the authorized local environment.

const challenge = {
profile: "00000008",
inputHex: "<page-input-hex>",
expectedCipherHex: "<page-cipher-hex>",
};
const targetAddress = ptr("<locally-resolved-envelope-entry>");
let patchedOnce = false;
function hexToBytes(hex) {
const out = new Uint8Array(hex.length / 2);
for (let i = 0; i < out.length; i++) {
out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
}
return out;
}
function readHex(ptrValue, length) {
return Array.from(new Uint8Array(ptrValue.readByteArray(length)))
.map((value) => value.toString(16).padStart(2, "0"))
.join("");
}
Interceptor.attach(targetAddress, {
onEnter(args) {
const input = resolveInputBuffer(args);
const length = resolveInputLength(args);
const expectedLength = challenge.inputHex.length / 2;
this.check = false;
if (patchedOnce || length !== expectedLength) {
return;
}
this.beforeHex = readHex(input, length);
input.writeByteArray(hexToBytes(challenge.inputHex));
patchedOnce = true;
this.check = true;
},
onLeave(retval) {
if (!this.check) return;
const cipher = resolveOutputBuffer(retval, this);
const cipherLength = challenge.expectedCipherHex.length / 2;
const cipherHex = readHex(cipher, cipherLength);
console.log(JSON.stringify({
profile: challenge.profile,
input_before: this.beforeHex,
input_after: challenge.inputHex,
cipher_runtime: cipherHex,
cipher_expected: challenge.expectedCipherHex,
matched: cipherHex === challenge.expectedCipherHex,
}));
},
});

resolveInputBuffer, resolveInputLength, and resolveOutputBuffer depend on the local boundary. Keep the patch near the white-box AES envelope entry. Patching higher-level business objects or lower-level ciphertext destroys the clarity of the test.

If runtime output does not match the page ciphertext, check these first:

  • whether the hook point is after compression and before the white-box envelope;
  • whether injected input length exactly matches page input_hex;
  • whether the captured output buffer belongs to the current call rather than a reused previous result;
  • whether the selected profile is wrong, because 00000008 and 00000003 are not interchangeable;
  • whether aligned input was accidentally given an extra padding block.

Why White-Box AES Is Hard#

White-box cryptography tries to protect key material even when the attacker can observe, debug, and copy the runtime environment. That is much harder than server-side encryption.

The problem with the 03/08 family is not that there is no obfuscation. The problem is that the obfuscation does not remove enough recoverable structure:

  • independent FinalTable lanes invite byte-wise analysis;
  • full TableValue truth tables allow hidden XOR coordinate recovery;
  • per-input TableKey contribution allows SBox/key fitting;
  • a static IV is not a security boundary;
  • unsigned templates can be analyzed, replaced, or downgraded.

Stronger designs usually combine several measures:

input/output external encoding
+ cross-byte fusion
+ fused rounds
+ per-build table layout
+ runtime-derived transient tables
+ template signature
+ per-message IV
+ anti-downgrade policy

White-box AES can still have engineering value. It raises extraction cost and preserves protocol compatibility. But it should not be treated as a guarantee that key material can never be recovered.

Reading Path For Beginners#

If this is your first white-box AES case, read it in this order:

  1. Understand AES-CBC: blocks, IV, padding, chaining.
  2. Understand AES rounds: SBox, ShiftRows, MixColumns, AddRoundKey.
  3. Think of SBox[x xor key] as a lookup table.
  4. Think of MixColumns as four contributions combined by XOR.
  5. Then map the white-box design: TableKey creates contributions, TableValue combines them, FinalTable finishes the round.
  6. Finally, read the recovery logic: 08 leaks through final lanes; 03 requires recovering hidden XOR coordinates first.

Viewed this way, white-box AES is not magic. It is standard AES turned into tables and encodings.

FAQ#

Is the WeChat client check data white-box AES standard AES-CBC?#

Its external semantics are equivalent to AES-128-CBC, but the runtime is not a normal library call. It interprets TableKey, TableValue, and FinalTable from a PB template, and the resulting ciphertext can be verified against recovered key/IV through standard AES-CBC.

Are 00000003 and 00000008 two completely different algorithms?#

They are better understood as two table-generation strategies for the same AES-CBC table interpreter. 08 is closer to unencoded or weakly encoded table-driven AES. 03 adds byte-level encoding to contribution and combiner tables.

Why can 00000008 recover key through FinalTable?#

Because each 08 FinalTable byte lane can be fitted as SBox[x xor a] xor b. Recovering the 16 b values yields the last-round key; inverse AES-128 key schedule then yields the master key.

Why is 00000003 still recoverable?#

Its FinalTable does not directly fit the same lane model, but TableValue stores complete encoded binary combiner truth tables. After recovering hidden XOR coordinates, first-round TableKey contributions can be fitted to coeff * SBox[x xor keyGuess].

What is the page challenge for?#

It is a reverse-validation vector. Refresh the page to get a new hex input and 03/08 ciphertexts, then compare them with a local hook, offline interpreter, or recovered standard AES-CBC implementation.

How should the 8.7.0 VER08 entry be located?#

Do not start with key search. A more stable route is to follow the client check data collector into the envelope transform, then recover the relationship among version, IV, large table blocks, scalar control, and gateway. Once that boundary is found, validate the path as compressed bytes -> VER08 envelope -> ciphertext.

Can white-box AES fully prevent key extraction?#

Not by itself. White-box AES raises extraction cost. If full tables, stable round boundaries, independent final lanes, and static IVs remain analyzable, key/IV recovery may still be possible. Stronger designs need encoding, byte fusion, template integrity, and runtime diversity together.

Share Article

If this article helped you, please share it with others!

WeChat 8.7.0 ClientCheckData White-Box AES: From AES-CBC to 03/08 Table Interpreter Recovery
https://taskagent.one/en/posts/wechat-client-check-whitebox-aes-cbc/
Author
TaskAgent Reverse Lab
Published at
2026-05-11
License
CC BY-NC-SA 4.0

Comments

Anonymous Discussion

Stay anonymous and keep the discussion technical. Nickname and website are optional.

Loading
Loading comments
0/1200
Profile Image of the Author
TaskAgent Reverse Lab
AI-agent-driven, expert-guided research on protected algorithms.
Research Boundaries
This site records reverse engineering, security validation, and engineering reviews in authorized environments only. It does not publish bypass, abuse, or unauthorized attack material.
Categories
Tags
Site Statistics
Posts
3
Categories
1
Tags
24
Total Words
44,956
Visits
Loading...
Running Days
0 days
Last Activity
0 days ago

Table of Contents