SuperAPI AI Directory

re-crypto-decrypt

Data & analytics

View repository

Updated 10-15 21:34

About this skill

Encrypted Data Recovery

When to Use / When Not to Use

  • Use: need to recover ciphertext (data blob / traffic / config section) into plaintext
  • Use: algorithm and key are known (or have been derived via [[re-crypto-id]] / [[re-crypto-keys]]), and batch decryption is needed
  • Use: recover the decryption logic from a sample and rewrite it as a standalone reusable script
  • Don't use: both algorithm and key are unknown (first [[re-crypto-id]] → [[re-crypto-keys]])
  • Don't use: statically readable plaintext ([[re-triage]] low entropy, read directly)
  • Don't use: want to run the original sample and observe output (that's the job of [[re-behavior]] / [[re-sandbox]]—the decryption script is for reproducing without the sample)

Tool Preparation

Verify all tools before use. This skill deals with dump/decompilation artifacts and ciphertext data; the sample-running part happens inside [[re-sandbox]] ([[re-analyze/platform-tips]] highest principle).

python3 + pycryptodome — the mainstay of decryption scripts

  • Linux: apt install python3 python3-pip / dnf install python3 python3-pip / pacman -S python python-pip
  • macOS: brew install python
  • Windows: python.org installer (check Add to PATH); Linux version inside WSL
  • Crypto library: pip install pycryptodome(standard algorithms such as AES/DES/RSA/ChaCha)
  • Verify: python3 -c "from Crypto.Cipher import AES; print('ok')";python3 --version

Target program dump/decompilation artifacts — the basis for recovering the algorithm

  • Dump: [[re-memdump]] default dump (gcore) — locate the ciphertext input point and the decryption call site
  • Decompilation: output of [[re-ghidra]] / [[re-ida]] / [[re-radare2]] (function decompilation view)
  • Verify: file out is an ELF core; the target function can be found in the decompiler (ghidra / rizin can start)

angr (optional) — symbolic execution to supplement logic that is hard to recover

  • All platforms: pip install angr——For Python version compatibility, refer to [[re-angr]]'s "Tool Preparation"(the version matrix is maintained in only one place there, to avoid drift across multiple locations); many dependencies, venv recommended: python3 -m venv venv && venv/bin/pip install angr
  • Verify: venv/bin/python -c "import angr; print(angr.__version__)"
  • Use: when decompilation branch explosion/obfuscation is severe, use symbolic execution to solve the decryption function output (load target binary → set ciphertext input as symbolic → constraint solving)

Procedure

Execute in order, recording the results of each step. Prerequisite: the algorithm ([[re-crypto-id]]) and key ([[re-crypto-keys]]) are confirmed, or at least there is a candidate for one of them; archive the script and verification results (plaintext sample + sha256) for reference in the report.

  1. Falsify "ciphertext": first locate the true ciphertext boundary(skipping this step is the most common way crypto analysis fails):

    • A high-entropy section may be "pseudo-ciphertext"—container/compressed data streams mistaken for an encryption layer (such as the zip data body, or a compressed stream appended after an image). Compressed data entropy is also 8.0, indistinguishable from encryption
    • First do an overall structural observation: file header/tail magic numbers, format marker distribution (JPEG per-segment size statistics), using a known tail structure as an anchor (the zip EOCD's cd_offset/cd_size to infer the data start; the EOCD signature is known plaintext)
    • Determine format boundariesDo not use first/last bare marker searches: marker byte pairs can legitimately appear in the payload—PNG's IEND string can fall within tEXt chunk data, JPEG's FF D9 can fall inside length-delimited segments such as APP1 (taking the first hit as the end will truncate legitimate data). The correct approach is to parse according to the format specification: PNG, starting from the 8-byte signature, follows Length/Type/Data/CRC traversing chunk by chunk, until the structure is valid and the zero-length IEND(verifying the CRC chunk by chunk if necessary); JPEG, starting from SOI , is parsed by marker/segment, and after entering SOS it is processed according to FF00 stuffing and RSTn rules,and only the EOI at a syntactically valid position is the end. Raw byte searching can only be used to produce candidate positions; candidates must undergo structural validation before boundaries can be determined
    • For files of the same size, first compare the last 16 bytes/overall hash—a "header-swapped copy" (a second encapsulation of the same payload, differing only in the header) directly eliminates an entire branch
    • Compression ratio is a signal: a layer that becomes significantly larger after decompression indicates the content has structure (compressible = encoding/plaintext), while a ratio close to 1:1 indicates the inner layer is already compressed or encrypted
    • After confirming it is an isolated high-entropy blob, proceed to the ciphertext localization in step 1
  2. Locate the decryption function (cross-reference the ciphertext input point):

    • Starting from the ciphertext offset: use the high-entropy region offset from step 2 of [[re-crypto-id]] → in the decompiler, find the function that reads that offset/that global variable → follow the call chain to see who wrote it (the writer is often the decryption function)
    • Starting from the API: the Crypt*/EVP_* call site found in step 4 of [[re-crypto-keys]] is the candidate; observe whether the ciphertext pointer among the arguments points to the offset from step 2
    • Dynamic assistance: [[re-gdb]] / [[re-x64dbg]] set a breakpoint at the candidate function (inside the sandbox), print the arguments/return value, and confirm it outputs readable plaintext
    • No clear function found → the ciphertext may be processed by an inlined algorithm (with no call boundary); go back to [[re-crypto-id]] and locate it using data characteristics (at references to constant tables)
  3. Reconstruct the algorithm by decompilation:

    • Transcribe the decompiled view section by section into pseudocode, making clear: the algorithm (AES-CBC/custom XOR...), the key and IV sources (fixed value/derived/context), the mode and padding (where the CBC IV is, PKCS7 or zero padding)
    • Custom algorithm: translate the bit operations one by one (XOR/shift/table lookup), and pay attention to endianness (same as pitfall 1 in [[re-proto-rev]]—try both little and big endian first for length/key fields)
    • When reconstructing standard algorithms, pay attention to details: whether AES uses CBC or ECB, key length 16/24/32, whether the IV reuses the key (a common incorrect implementation, see pitfall 1)
    • Loop/table-lookup logic that is unclear in the decompilation → use angr symbolic execution as a fallback (construct a solving script and treat the function as a black box to obtain the output)
  4. Rewrite as a standalone script (python):

    # decrypt.py —— 按反编译还原的算法重写
    from Crypto.Cipher import AES
    import sys
    key = bytes.fromhex("...")          # 来自 [[re-crypto-keys]] 步骤 1/2/5
    iv  = key[:16]                       # 样本实现: IV = key 前 16 字节
    data = open(sys.argv[1], 'rb').read()
    pt = AES.new(key, AES.MODE_CBC, iv).decrypt(data)
    print(pt)                            # 或写文件 + 后续校验
    
    • Parameterize the script (key/IV/input file passed via arguments or configuration), write it correctly once and reuse it repeatedly—for batch traffic decryption
    • Key point: the script logic must match the sample (padding handling, tail truncation); when inconsistent, go back and check the boundary conditions (see pitfall 1)
  5. Verify with known plaintext:

    • Sources of known plaintext: protocol header magic (such as \xAA\x55), file headers (PK zip / \x89PNG), message fields (the fixed header in step 2 of [[re-proto-rev]]), or plaintext strings observed in the behavior in [[re-behavior]]
    • Verification method: a known plaintext fragment can be found in the decryption output → success; if not found → check in order: whether the key/IV are correct (try each [[re-crypto-keys]] candidate one by one), endianness, padding handling, and whether there is an outer layer of encryption (see Pitfall 3)
    • When there is no known plaintext, use "readability" to verify: output printable rate >70% or passes file - identify the format (PDF/zip/text) → treat as a success candidate
  6. Traffic scenario: decrypt the plaintext traffic stream:

    • After confirming the traffic encryption algorithm and key according to [[re-crypto-id]] / [[re-crypto-keys]], extract the ciphertext payload from the pcap ([[re-netcap]] step 3 tshark export):
      tshark -r c2.pcap -Y 'tcp.payload' -T fields -e data.data | sed 's/://g' | xxd -r -p > payloads.bin
      
    • Write a batch script: split by stream (one segment per TCP stream), call the decryption logic segment by segment (same script as step 3), and output plaintext stream files
    • Verification: the protocol structure (session sequence number/command word) can be seen in the plaintext stream, then convert to [[re-proto-rev]] for state machine reconstruction
    • Note session key changes (rederived on each handshake) → in the script, obtain the corresponding key for each session (the derivation restoration in [[re-crypto-keys]] step 5)

Cross-domain combination

  • [[re-protocol]]: step 4 of this gateway workflow (decryption) — the landing point of encrypted communication links (crypto-id → crypto-keys → crypto-decrypt → proto-rev)
  • [[re-malware]]: C2 traffic decryption — step 4 of re-malware; use the decrypted plaintext (commands/configuration) for behavior judgment and IOCs ([[re-ioc]])
  • [[re-firmware]]: firmware encryption layer/encrypted communication decryption — used with the encryption layer handling when [[re-fw-extract]] unpacking fails
  • [[re-crypto-id]] / [[re-crypto-keys]]: upstream — the input sources for the algorithm and key
  • [[re-memdump]]: location of ciphertext input points and decryption call sites (dump artifacts)
  • [[re-anti-analysis]]: if decryption is inside a packer, unpack first (see Pitfall 2); [[re-gdb]] / [[re-x64dbg]] dynamically assist in confirming function behavior
  • Convert the decrypted plaintext to [[re-proto-rev]] to reconstruct the state machine, or continue according to the [[re-firmware]] / [[re-malware]] workflow

Common pitfalls and traps

  • The restoration script is inconsistent with the sample behavior → recheck boundary conditions (length/padding): Symptom — the plaintext decrypted by the script differs from the sample's own output (more/fewer bytes, garbled tail); cause — boundary conditions are not aligned: padding method (PKCS7/zero), whether the length field includes padding, whether the IV changes per packet, whether the ciphertext tail is truncated; countermeasure — check the padding and length handling code item by item in the decompilation, implement them explicitly in the script, then return to step 4 and verify with known plaintext
  • Decryption is inside a packer → unpack first: Symptom — the decryption function cannot be found in a packed sample, or the function found is only the packer's decompression; cause — the ciphertext data/decryption logic is wrapped by the packer, and statically it appears as the packer's initial state (same as [[re-memdump]] Pitfall 2); countermeasure — first use [[re-anti-analysis]] to unpack (determine the dump timing according to plaintext/code materialization, and do not treat OEP as a universal criterion), then redo localization on the unpacked artifact
  • Multi-round decryption chain: Symptom — after decrypting the first layer, it is still garbled/high-entropy (entropy 7.0+); cause — multi-layer encryption (XOR first then AES, or nested compression + encryption, see [[re-crypto-id]] Pitfall 3); countermeasure — verify each layer separately (after decrypting one layer, test entropy and readability once), and restore layer by layer; "entropy decrease means one layer of progress" can be used as a stopping condition
  • Key/IV order used incorrectly results in garbled decryption: Symptom — known-plaintext verification fails, but the algorithm is determined to be AES; Cause — key/iv parameter order (whether the sample is key||iv concatenation or passed separately), key byte order, or IV reuse/fixed IV; Countermeasure — turn the candidates from [[re-crypto-keys]] (including big-endian/little-endian variants, common misimplementation variants such as IV=key prefix, etc.) into a parameter combination loop to brute-force decryption, and use readability/known-plaintext to determine a hit
  • Restricted symbol set = encoding is not encryption (pseudo-encryption): Symptom — entropy 7.9+ but the byte value range is clearly restricted (e.g., only 66 symbols, uniform distribution, no periodicity), xortool/IC is completely flat for all key lengths; Cause — this is "encoding + single-byte XOR":single-byte XOR does not change the frequency distribution or the symbol set(it only shifts values), only multi-byte XOR scatters the symbols across all 256 values; Countermeasure — collect the complete set of symbol values, and perform XOR matching against known character sets (compare sets against 256 keys, millisecond-level, don't brute-force): Base64 64 characters + \n + = = 66 symbols, Ascii85 85, hex 16, etc.; after a hit, XOR to restore → decode; cross-validation: padding characters (e.g., = appears only once at the end of the file after encryption), newline frequency, magic number after decoding
  • xortool/IC results all flat = a signal to change approach: Symptom — all key length candidates are ~10-20% and there is no prominent peak; Conclusion — statistically there is no periodic structure, multi-byte repeating-key XOR is basically ruled out, don't keep running with different parameters; Countermeasure — fall back to observing the data itself (value range/symbol set/entropy), usually it is an encoding layer or a one-time keystream (stream cipher)
  • Pseudo-ciphertext confirmed but there is more than one layer → layered peeling discipline: after peeling each layer, use magic numbers/readability to verify the integrity of the product before moving to the next layer; keep and name all intermediate products (rollback possible); entropy does not decrease / still high entropy after decoding = there are more outer layers (e.g., Base64 outside XOR, and then compression)
  • Pixel-level XOR (visual cryptography/image steganography): Symptom — two "random noise" images, the challenge hints at overlaying them; Cause — visual cryptography splits information into two shares, and XOR (or ADD) of the pixels makes it visible; Countermeasure —ImageChops.logical_xor or numpy a^b pixel-wise XOR the two images (all three RGB channels); the result is often "99.5% pure white background + extremely light gray text" (text 254 vs background 255) — neither direct viewing nor inversion makes it visible,use threshold enhancement:==255 → 黑、其余 → 白, the text appears immediately; for bitmap small text, further split it into a character grid (5x7) and read character by character, and verify repeated characters with a pairwise bitmap similarity matrix (e.g., d562333d repeated patterns)
  • ADD saturated addition is a close relative of XOR: Symptom — when the XOR result has no content; Cause — the shares may have been generated by additive secret sharing (XOR produces noise, and only ADD/SUB yields an image); Countermeasure — similarly try clip(a+b) saturated addition and a-b subtraction, and compare multiple operation combinations
  • Heavy obfuscation in new versions → backtrack and locate in old versions: Symptom — after the core functions in the latest version are obfuscated, the size is abnormally huge, IDA analysis lags, and grinding through it manually is unrealistic; Cause — early versions of the same SDK did not have heavy obfuscation, and the core logic is clearer; Countermeasure — install historical version packages (Wandoujia and other channels) according to behavioral characteristics (e.g., "collecting is triggered as soon as the privacy agreement is agreed to") → click agree → capture packets to confirm the earliest version in which that parameter appears; choose a version with regular behavior as the starting point for analysis (easier debugging: both spawn/attach can reproduce it); the logic recovered from the old version in turn guides confirmation of which parameters the new version collects
  • Segmented/layered encrypted reporting (different algorithm per segment): Symptom—the reported request fields captured all look like ciphertext on their own, but decrypting them uniformly with one algorithm yields only gibberish; Cause—the client-side encryption process is split into multiple segments:single-field encryption → segmented encryption → overall encryption, each segment uses a different algorithm/key (e.g., the first layer uses a random number as the key for byte-by-byte encryption, the second layer uses a fixed key, and the third layer concatenates and combines fields before encrypting again); moreover, the fields themselves may be risk features collected from the device (collected via svc instructions,r_1_0 type fields); Countermeasure—peel layer by layer following the discipline of "verify each layer separately" (entropy drop/readability means advance one layer; see the multi-round decryption chain pitfall); note that the "overall encryption" of the same-origin SDK may additionally wrap a VM-based algorithm (about 70 handles simulate basic instructions; when handles are not obfuscated, instruction semantics can be identified one by one); key sources fall into two categories: random (not reproducible via packet capture) and fixed (can be found statically); do fixed first, then random
  • Double AES + encrypted strings (key/iv also stored encrypted): Symptom—after confirming AES, direct decryption still fails, or the key/iv changes every session; Cause—a common implementation pattern: the first-layer key/iv is fixed (hardcoded in the so as encrypted strings, resolved at runtime via a decryption function), while the second-layer key/iv changes each time; between the two encryptions, a fixed prefix is concatenated (e.g., 03000001) before entering the same function; and encryption/decryption may be the same function (a flag bit controls the direction); Countermeasure—hook the return point of the decryption function to export all decrypted strings in bulk (the encrypted string memcpy'd before JNI_OnLoad → global variable → decryption function), and search directly within them for the key/iv; to find the encryption point, use findcrypt to scan for characteristic constants (AES S-box, etc.) → xref to locate the function that checks the key/iv length (16); dynamically hook to print the data before and after two calls for comparison and verification