When to use / when not to use
- Use: the cryptographic implementation "has no visible key"—no standard library API, the key does not exist explicitly in files/memory/resources (the key is hidden inside the implementation itself)
- Use: decompilation shows large read-only lookup tables (from hundreds of KB up) + function bodies that are almost entirely lookup logic, with no standard algorithm structure (no Crypt* calls, no standard S-box constants, no OpenSSL symbols)
- Use: DRM/licensing/in-app encryption, or firmware license verification where a white-box implementation is suspected (Chow-type white-box AES, white-box SM4, etc.)
- Do not use: ordinary cryptographic implementations (follow the regular chain [[re-crypto-id]] → [[re-crypto-keys]] → [[re-crypto-decrypt]])
- Do not use: both the algorithm and the key are already known (decrypt directly)
- Prerequisite check: unpack/deobfuscate packed or obfuscated samples first ([[re-anti-analysis]]); the white-box tables and code cannot be analyzed when contaminated by the packer layer
Tool preparation
Static-first (sandbox-free); when "table encryption at rest, decrypted at runtime" is involved, tables must be obtained dynamically (default sandbox, [[re-analyze/platform-tips]] highest principle). Verify every tool before use.
python3 — table extraction and key derivation scripts
- For installation and verification see [[re-proto-rev]] tool preparation (python3); for large-table processing, numpy is recommended (
pip install numpy)
Decompiler — locate the lookup code and table addresses
- [[re-ghidra]] (default, import → auto-analysis → decompile); [[re-ida]] / [[re-radare2]] as per
RE_DECOMPILERsession variable - For verification see the corresponding skill's tool preparation; table addresses/sizes are subject to the decompiler's Data view
[[re-angr]] — optional (symbolic execution to assist in solving index relationships)
- All platforms:
pip install angr - Verification:
python3 -c "import angr; print(angr.__version__)" - Purpose: when the lookup code is obfuscated or the index relationship is complex, symbolic execution directly solves the "input byte → table entry" correspondence
[[re-memdump]] — for table encryption (runtime decryption), obtain tables dynamically
- Default to dump-first (gcore, see the "direct read vs dump" decision table in [[re-analyze/platform-tips]])
- For verification see [[re-memdump]] tool preparation
Operating steps
Execute in order; archive each step's artifacts (table files/scripts/recovered keys) with sha256 + path for citation in the report ([[re-ioc]]).
-
White-box implementation identification (lookup table characteristics, large lookup code blocks with no standard library calls):
# 静态侧: 找 .rodata/.data 里的查表候选——256 条 4/8 字节条目、连续、值分布均匀 objdump -h sample.bin | grep -E 'rodata|data' # 反编译确认: 函数体是否"state 异或常量 → 查表 → 组合"结构、有无 Crypt* / OpenSSL 符号- Feature checklist: ① Large read-only tables (one round of white-box AES has 4 tables of 256×4 bytes; a Chow-type encoded table set can reach 700KB-1MB+) ② Function bodies are mainly lookups, with no standard round function form ③ No standard constants ([[re-crypto-id]]'s AES S-box/Rcon fingerprints do not hit) ④ No cryptographic API imports ([[re-imports]] clean)
- Decompilation confirmation: xref the table addresses (Ghidra
Ctrl+Shift+F), and look at the form of the referencing functions—"lookup + combination" rather than "round function + constant tables" is a white-box candidate - Division of labor with [[re-crypto-id]]: regular fingerprints hit → standard algorithm chain; no fingerprints hit but large tables present → white-box hypothesis (see pitfall 4)
-
Table extraction:
- Ghidra: select the table data segment and right-click Export (or use the script
getBytesto dump the whole segment); IDA:ida_bytes.get_bytes(start, size)export - Parse script (by entry width/endianness):
import struct data = open('tables.bin','rb').read() for off in range(0, len(data), 4): # 4 字节条目(AES T 表): 每条 [S(x)<<24 | ...] v = struct.unpack_from('<I', data, off)[0] # 记录值分布/每字节位模式,输出供步骤 3 分析 - Record: each table's address, size, entry width, and which functions reference it—the table↔function↔round mapping is the map for key recovery
- Ghidra: select the table data segment and right-click Export (or use the script
-
Key recovery (table inversion → key derivation, or known-plaintext attack):
- Approach A Table inversion: the white-box tables embed the key—the first-round table
T0[x]encodesS(x) ^ k0(x is the input byte), the last-round table encodes the final round key; when the encoding layer (affine/byte mapping) is simple, invert it directlyk0 - Approach B Known-plaintext attack: look up the table for a fixed input byte to get the output, construct "input→output" pairs, and solve when the encoding layer can be modeled (affine encoding solves linear equations, byte mapping looks up collisions)
- Approach C Dynamic table extraction: the table itself is encrypted (see Pitfall 3) → at runtime [[re-memdump]] dump it, then search memory for table characteristics, take the decrypted table, and proceed with Approach A/B
- Approach D DCA (Differential Computation Analysis): the encoding layer is complex (full Chow encoding) and manual reverse derivation is unrealistic → use daredevil from SideChannelMarvels to perform DCA (Differential Computation Analysis) on the white-box execution traces to recover the key
- Key hypothesis verification: first compare the recovered candidate key against the standard implementation's output (Step 4); do not trust it directly
- Approach A Table inversion: the white-box tables embed the key—the first-round table
-
Compare with a standard implementation (same algorithm, different implementation):
from Crypto.Cipher import AES # pip install pycryptodome key = bytes.fromhex('<候选密钥>') c = AES.new(key, AES.MODE_ECB) print(c.encrypt(b'hello whitebox!!').hex()) # 与样本白盒实现对同一明文/密文的输出比对- Comparison confirmation: ① algorithm and parameters (AES-128/192/256, 16-byte block, ECB/CBC mode, number of rounds) ② correctness of the candidate key ③ correspondence between the white-box tables and the standard T-tables/round function
- Variant check: SM4 (32 rounds, 8-bit block bytes)/DES white-box/custom—first determine the family by block size and number of rounds, then apply the corresponding structure (see Pitfall 4)
-
Report (key/algorithm/impact):
- Content: key (16/24/32-byte hex), algorithm and variant, extraction method (table inversion/known plaintext/DCA/dynamic), impact assessment (key leakage → all data/traffic can be decrypted, DRM can be forged), evidence (table address, sha256, comparison output)
- After key extraction, proceed to [[re-crypto-decrypt]] to decrypt the target data/traffic; put the key and table hashes into [[re-ioc]]
Cross-domain combination
- [[re-protocol]]: this skill is the white-box branch of that gateway's encryption link—when the encryption identification/key/decryption trio encounters "table-lookup encryption", switch to this
- [[re-crypto-id]]: prerequisite—for "table-lookup encryption" where no constant table fingerprints (S-box/Rcon/CRC) match at all → switch to this skill; white-box tables can also be misidentified by fingerprint tools (see Pitfall 4)
- [[re-crypto-keys]]: the white-box branch when the conventional key extraction path (hardcoded/memory search) fails—the key is in the tables, not in an explicit location
- [[re-crypto-decrypt]]: downstream—decrypt data/traffic after recovering the key
- [[re-binary-core]]: decompilation foundation ([[re-ghidra]] / [[re-ida]] / [[re-radare2]])
- [[re-angr]]: optional—symbolic execution assists with table indexing/encoding-layer solving
- [[re-deobfuscate]]: when white-box + obfuscation are combined, first restore the control flow (Pitfall 2)
- [[re-memdump]]: dynamically extract tables when they are encrypted (decrypted at runtime) (Pitfall 3)
- [[re-malware]]: analysis of white-box implementations of malicious sample communication encryption/ransomware
- Reference [[re-analyze/platform-tips]] for the static-first principle and the "direct read vs dump" decision table (timing of dynamic table extraction)
Common Pitfalls and Traps
- Huge White-Box Tables (Memory/Time): Symptom—After dumping/parsing hundreds of MB of tables, the script hangs and memory explodes, dragging analysis out for days; Cause—Chow-style white-box table sets are 700KB-1MB+, and even larger after encoding/obfuscation is layered on; Countermeasure—Process in chunks (read in shards by table address range), use numpy vectorized parsing, and first use xref to filter "only analyze referenced tables"; compress and archive table files before analysis
- Obfuscation + White-Box Combination: Symptom—The table is found, but the lookup code is entirely wrapped in control-flow flattening/junk instructions, and the index relationships cannot be read out; Cause—White-box and code obfuscation are layered together (a [[re-anti-analysis]] domain technique to prevent automated analysis); Countermeasure—First use [[re-deobfuscate]] to restore the control flow before performing index analysis; or skip static restoration and use [[re-angr]] symbolic execution to directly solve the "input byte → table entry" correspondence; if neither path works, then use DCA (which does not depend on code semantics)
- Table Encryption (Secondary Protection): Symptom—The statically dumped table has high entropy/is all ciphertext, but decompilation shows a preceding loop that decrypts the table; Cause—The white-box table itself is encrypted and only decrypted at runtime (to prevent static extraction); Countermeasure—Use [[re-memdump]] for dynamic dumping, with the dump timing after the decryption loop has executed ([[re-memdump]] pitfall: dumping too early fails to capture the table; save immediately upon seeing decrypted data); after obtaining the plaintext table, proceed to step 3
- Misjudging Algorithm Variants: Symptom—Tables extracted according to the AES white-box structure do not fit at all, wasting effort; Cause—The implementation is SM4/DES white-box or a custom variant, with different encoding methods/round structures; Countermeasure—In step 4, first determine the family (block size, number of rounds, and compare output against a standard implementation); after confirming the algorithm, extract tables according to the corresponding structure; when different encoding layers (affine/byte mapping) make table reversal difficult, use DCA rather than manual reverse derivation
- False Table Fingerprint Hits: Symptom—A suspected S-box/constant is "found" in the table, leading to misjudging a standard algorithm and taking the wrong path; Cause—A table after white-box encoding may happen to contain fragments of standard constants (or be contaminated by unreferenced code from static libraries); Countermeasure—After a hit, you must use xref to confirm that it is referenced by the encryption path ([[re-crypto-id]] has the same pitfall), then check it against the number of rounds and block size
- Three Schools of White-Box Implementation (Determine the School Before Choosing the Attack): Symptom—Obtaining a white-box target and not knowing which attack method to use; Cause—White-box AES has three types of implementations, and the attack methods differ: ①Chow Lookup Tables(broken by the 2004 BGE algebraic attack, generalized by Michiels in 2008, and Lepoint in 2013 recovered the key with 2^22 complexity); ②Bringer Perturbation Terms(inserting additional perturbation equations + linear encoding; the Xiao-Lai implementation from 2010 was broken by Mulder); ③Biryukov ASASA(alternating Affine-Sbox layers, algebraic/statistical/decomposition attacks); Countermeasure—First identify the school: Chow characteristics = lookup table set (TBoxes = SubBytes+AddRoundKey merged, TyiTableBoxes = MixColumns precomputed, ioInvTable randomly obfuscated, shiftTab embeds ShiftRows into table generation); ASASA = alternating affine-nonlinear layers; Chow structure key extraction uses algebraic attacks (merging specific lookup tables to remove nonlinearity); DFA (differential fault analysis) starting from MixColumns is a classic attack entry point