SuperAPI AI Directory

re-crypto-keys

General & other

View repository

Updated 10-15 21:34

About this skill

Key and Password Extraction

When to Use / When Not to Use

  • Use: when data/traffic needs to be decrypted but the key is unknown (static first: hardcoding → resources → import table; dynamic: memory dump)
  • Use: to confirm whether the sample has a hardcoded key/password (configuration extraction)
  • Use: the key is derived at runtime (PBKDF/HKDF) and the derivation process needs to be recovered
  • Don't use: the algorithm itself hasn't been confirmed yet (do [[re-crypto-id]] first)
  • Don't use: the key is delivered via external configuration/server (there is no local key to extract—honestly tell the user, see Pitfall 2)
  • Don't use: only doing static format analysis ([[re-triage]] / [[re-format-pe]])

Tool Preparation

Verify all tools before use. Static search can skip the sandbox; memory dump/dynamic steps follow [[re-memdump]] default dump-first + [[re-analyze/platform-tips]] highest principle (run samples in a sandbox).

strings — quick scan for printable strings (all platforms)

  • Linux: apt install binutils / dnf install binutils / pacman -S binutils(most come with it)
  • macOS: bundled /usr/bin/strings
  • Windows/WSL: Linux version inside WSL; on native Windows use Ghidra or strings.exe(Sysinternals)
  • Verify: strings --version(the GNU version has --version)

ghidra / rizin — decompilation and cross-reference search (for installation see [[re-ghidra]] / [[re-radare2]])

  • Searching cross-references of constants/strings is the key to finding "who uses this key"
  • Verify: ghidra(GUI) or rz-ghidra plugins available;rizin -v

Dump artifacts — raw material for memory search ([[re-memdump]] default dump)

  • Follow [[re-memdump]] Step 1 and use gcore -o out <pid> to dump; the dump timing is determined by plaintext/code materialization (near the OEP for a single-layer packer is usually suitable; OEP is not a universal criterion, see [[re-analyze/platform-tips]] key experience)
  • Verify: file out confirm it is an ELF core,eu-stack -e out can run

python3 — entropy-block/pattern scanning script

  • For installation and verification see [[re-proto-rev]] tool preparation (python3)

Operating Steps

Execute in order, record the result of each step. Strategy order:static before dynamic(see Pitfall 3); record the artifact of each step (key/password + source evidence: offset, function name, dump path) for use by [[re-crypto-decrypt]].

  1. Static: strings / cross-references to find hardcoding:

    strings -n 6 sample.bin | grep -iE 'key|secret|pass|token|crypt|iv' | head -50
    strings -el sample.bin | grep -iE 'key|secret|pass' | head -20     # UTF-16LE(Windows 常见)
    strings -n 8 sample.bin | head -100                                # 全量扫描人工过一遍
    
    • For suspicious strings (fixed strings that look like keys), use the decompiler to check cross-references: [[re-ghidra]] / [[re-ida]] right-click Find References—see which function reads it and how it participates in computation (goes directly into encryption parameters → it's a key; participates in table lookup/comparison → it's a password or salt)
    • Search constants in the decompiler (Search > Memory / :> /v 0x...): 32-byte hex strings, repeated random data segments
    • Hex side: after using the [[re-crypto-id]] Step 1 script to find constant tables such as the AES S-box, the memory data near the table is often key material
  2. Memory: after dumping, search for key patterns (16/32-byte entropy blocks, printable password strings):

    gcore -o out <pid>                      # 默认转储([[re-memdump]]),按 materialization 定时机
    
    data = open('out','rb').read()
    import collections, math
    # 16/32 字节高熵块(AES-128/256 密钥候选)
    def ent(blk):
        c = collections.Counter(blk); n = len(blk)
        return -sum((v/n)*math.log2(v/n) for v in c.values())
    for base in range(0, len(data)-32, 32):
        blk = data[base:base+32]
        if ent(blk) > 7.0 and 16 <= len(set(blk)) <= 24:
            print(f"0x{base:x}: 32B 高熵块")
    # 口令/可打印串
    import re
    for m in re.finditer(rb'[ -~]{8,64}', data):
        s = m.group()
        if any(k in s.lower() for k in (b'key', b'pass', b'secret', b'pwd')):
            print(f"0x{m.start():x}: {s}")
    
    • Too many high-entropy block hits (the whole heap is one) → combine with [[re-memdump]]'s maps/offsets to narrow down to near the encryption context, or first use the Step 4 import table to locate the function and then retrieve the parameters
    • The key may be fragmented or XOR-obfuscated on the heap/stack (see Pitfall 1)—once found, first verify it in the decryption script ([[re-crypto-decrypt]] step 4)
  3. Resource files (.rsrc / embedded blob):

    • PE: use llvm-objdump -s -j .rsrc sample.exe or Ghidra to view the resource section;7z x sample.exe can extract embedded icon/version/custom resources
    • ELF: find the embedded blob in .rodata (objdump -s -j .rodata); combine with [[re-firmware]] experience—keys in firmware are often in configuration files/default certificates
    • The embedded blob may be serialized configuration (a JSON/INI-encoded key field); parse it as text first, then dig as binary; if the blob is encrypted (high entropy) → go back to [[re-crypto-id]], there may be an additional layer of decryption outside
  4. *Import table clues (Crypt near functions)**:

    • objdump -p sample.exe | grep Crypt / Ghidra Imports window to find CryptEncrypt/CryptDecrypt/BCrypt*/RSA*/EVP_*(OpenSSL)
    • Once found, decompile that function: the key parameter (handle/KEYEXCHANGE structure) usually comes from "a fixed value set somewhere earlier"—trace the data flow upward from the function: constant assignments, global variable initialization,CryptSetKeyParam the pbKeyData parameter of
    • Setting breakpoints to observe the parameters is more direct ([[re-gdb]] / [[re-x64dbg]]): after b CryptSetKeyParam look at the memory pointed to by pbKeyData —but note the highest principle of [[re-analyze/platform-tips]]: run inside a sandbox
  5. Recovering the key derivation function (PBKDF):

    • Recognize the PBKDF2/scrypt/bcrypt/EVP_BytesToKey call in the decompilation → key = KDF(password, salt, iteration count), extract parameter by parameter: password (hardcoded string or user input), salt (fixed bytes or context), iteration count (constant)
    • Write a recovery script (pip install cryptography / hashlib has built-in PBKDF2):
      import hashlib
      key = hashlib.pbkdf2_hmac('sha256', b'passphrase', b'<salt>', 100000, dklen=32)
      print(key.hex())
      
    • Verification: the derived key matches the high-entropy block in memory from step 2 (indicating the key after the KDF ran is right there), or directly use [[re-crypto-decrypt]] to try decrypting known ciphertext
    • Dynamic supplement: use Frida to hook the KDF function and observe the returned buffer (inside the sandbox, [[re-sandbox]]), and compare with the static recovery result

Cross-domain combination

  • [[re-protocol]]: step 3 (key) of this gateway workflow—the middle segment of the encrypted communication decryption chain (crypto-id → crypto-keys → crypto-decrypt)
  • [[re-malware]]: C2 configuration extraction (hardcoded C2 key/password)—the key stage of re-malware step 4; malware sample keys are often buried in the configuration ([[re-behavior]] behavior confirmation + extraction by this skill)
  • [[re-firmware]]: mining hardcoded passwords/keys inside firmware—re-firmware step 3 (rootfs configuration/default certificates)
  • [[re-memdump]]: the default dump is the raw material for the memory search in this skill ([[re-analyze/platform-tips]] direct-read vs dump decision table)
  • [[re-crypto-decrypt]]: downstream—the extracted key is handed to the decryption script for verification and use
  • [[re-anti-analysis]]: unpack the in-pack key first (determine the dump point by materialization, see [[re-memdump]] dump timing)
  • [[re-ioc]]: the extracted hardcoded keys/passwords can serve as YARA signatures and IOCs

Common pitfalls and traps

  • Key stored in fragments/XOR-obfuscated: Symptom — the extracted single-block "key" does not decrypt the plaintext, or no complete key can be found in strings/memory; Cause — the sample splits the key into multiple segments (shards) or stores it XORed with a constant and reassembles it at runtime; Countermeasure — decompile to find the reassembly logic: key bytes come from multiple offsets/multiple XORs (see the "check hardcoding first" flow in pitfall 3; when confirming hardcoding, examine the operations at the reference sites); after recovering candidates, try them one by one in [[re-crypto-decrypt]]
  • A truly random key ≠ statically extractable: Symptom — static/memory searches turn up nothing, yet the user still wants the key; Cause — the key is generated at startup RAND_bytes or delivered by a server, and simply is not in the sample (no solution short of white-box attacks); Countermeasure — report honestly: no locally extractable key exists; switch to key-derivation interception (hook RAND_bytes/KDF input), algorithm-side attacks (if the decryption result can be verified against known plaintext), or go back to [[re-malware]] to see whether the key is delivered by C2
  • Check for hardcoding before going dynamic: Symptom — the sample hardcodes the key, yet you first spend ages running the sandbox + Frida; Cause — the static-first order was not followed; Countermeasure — step 1, strings/cross-references, is a 30-second check; a static hit means skipping dynamic entirely; go dynamic only when static yields nothing and runtime material (KDF input, reassembly logic) is needed (see the static-first approach and top principle in [[re-analyze/platform-tips]])
  • Dumping too early fails to capture the runtime key: Symptom — memory searches find no high-entropy block at all, or the ones found do not match; Cause — dumping before the packer has decrypted/before key initialization (you get the packer's initial state, see pitfall 2 of [[re-memdump]]); Countermeasure — confirm the plaintext/key has materialized (for a classic single-layer packer, after the OEP and after an encryption call has executed; for multi-stage/on-demand decryption, judge by "write→execute transfer + payload header/import recovery") before gcore; if necessary, trigger a breakpoint in the encryption function before dumping (with [[re-gdb]])
  • Memory key candidate pattern misses AES-192: Symptom — high-entropy blocks are searched only at 16/32 bytes, so 24-byte keys are missed and decryption does not match; Cause — 16/32 bytes for AES-128/256 is a common assumption, but an AES-192 key is exactly 24 bytes (12 rounds); Countermeasure — make the high-entropy block search window cover all three sizes 16/24/32 (use the 10/12/14 rounds determination from [[re-crypto-id]] to pin down parameters), and once a candidate hits, verify it by trial decryption in [[re-crypto-decrypt]]
  • Ransomware-style samples use-and-destroy keys, so a post-hoc dump is necessarily empty: Symptom — the sample is confirmed to have executed encryption, but no key material can be found in any memory dump; Cause — ransomware zeroes (wipes) the key immediately after encryption completes; the key exists in memory only briefly during the encryption phase (NotPetya/BadRabbit/Phobos experiments can plot a key existence timeline); Countermeasure — take multiple snapshot dumps during the encryption phase (while the business logic is running) and compare them to build a timeline, or use [[re-gdb]] to break before the encryption function returns and grab the arguments; this is more reliable than a single post-hoc dump
  • The key plaintext appears only at a specific instant of execution, so a dump is bound to miss it: Symptom — the dump search finds nothing, but dynamic tracing shows the key appearing; Cause — for some samples the master key exists in memory as plaintext only in an extremely short window (e.g. the browser's v20_master_key appears only at the instant of decryption; VoidStealer relies on a hardware breakpoint to catch exactly this); Countermeasure — first locate the key's point of use (encryption function/derivation point), set a hardware breakpoint on it ([[re-gdb]]/[[re-x64dbg]], not relying on a breakpoint instruction) to intercept the plaintext in real time; this is more efficient than "dump first, then search for a needle in a haystack"