SuperAPI AI Directory

re-script-deob

General & other

View repository

Updated 10-15 21:35

About this skill

Script/macro deobfuscation (PowerShell / VBA / JavaScript)

When to use / when not to use

  • Use: obfuscation restoration for scripts and macros such as .ps1 / .docm / .xlsm / .js / .jse / .hta (encoding, string concatenation, execution chain)
  • Use: phishing attachment macros, malicious downloader scripts ([[re-malware]] → [[re-managed]] path)
  • Don't use: compiled artifacts (native goes to [[re-binary-core]], .NET goes to [[re-dotnet]], Java goes to [[re-java]])
  • Don't use: scripts are fully readable (read the logic directly, no need for this skill)
  • Note:Dynamic execution defaults to sandbox ([[re-analyze/platform-tips]] highest principle)——static decoding without execution can skip the sandbox; any "run script to get the next layer" step must go into [[re-sandbox]]

Tool preparation

See [[re-analyze/platform-tips]]——this skill is mainly static decoding, sandbox-free; steps in execution chain tracking that require a runtime (running scripts/opening macros) go into the sandbox per the highest principle.

python3 (main decoding workhorse)

  • Linux: apt install python3 / dnf install python3 / pacman -S python
  • macOS: built-in; Windows: official installer or choco install python
  • Verify: python3 --version

oletools (VBA macro extraction/decoding, optional but strongly recommended)

  • pip install oletools(pyparsing dependency installed automatically)
  • Verify: olevba --help

xxd / sed (hex and text processing)

  • Debian/Ubuntu: apt install xxd(newer versions are a standalone package, older versions are in vim-common);Fedora: dnf install vim-common(xxd has traditionally been provided by vim-common, F38+ also has a standalone xxd subpackage); Arch: pacman -S xxd
  • macOS: built-in (bundled with vim)
  • Windows: no built-in xxd——use WSL or PowerShell Format-Hex
  • sed is built in on all platforms; verify: xxd -v && sed --version

PowerShell (built into Windows) / pwsh (Linux/macOS)

  • Windows: built-in powershell.exe(or PowerShell 7 pwsh)
  • Linux: apt install powershell(Microsoft repo) or snap install powershell;macOS: brew install --cask powershell
  • Verify: pwsh --version

js-beautify (JS beautification, optional)

  • pip install jsbeautifier; verify: js-beautify --version

Procedure

Execute in order; archive each layer's restored result with a number (layer_01.ps1 → layer_02.ps1 …), and in execution chain tracking all "run script to get the next layer" steps are always inside the sandbox.

  1. Identify script type and obfuscation style:

    file sample.ps1 && head -c 512 sample.ps1
    grep -oiE 'IEX|Invoke-Expression|-EncodedCommand|FromBase64String|eval\(|unescape|fromCharCode|Chr\(|StrReverse|Document_Open|AutoOpen' sample.ps1
    
    • PowerShell indicators: -EncodedCommand / IEX / FromBase64String / [Convert]::
    • VBA indicators: Sub AutoOpen / Document_Open / Chr( / StrReverse / Evaluate / Shell
    • JS indicators: eval( / unescape / fromCharCode / \xHH hex / document.write
    • Obfuscation style categories: pure encoding (base64/hex/char-code), string concatenation ('co'+'mmand'), execution chain (outer IEX/eval nesting inner payload), character reversal/substitution
  2. Restore layer by layer (decode first, then follow the execution chain):

    • Order: peel from the outermost layer——first decode the pure encoding layer, then follow the IEX/eval arguments down to the innermost payload
    • After decoding each layer, return to step 1 and re-identify (the new layer may use a different obfuscation technique)
    • Final goal: obtain readable "real logic" (URL, command, payload), archive the final layer
    • Steps in the execution chain that "output the next layer after execution" → run inside the sandbox
  3. PowerShell (-enc decoding, IEX chain):

    • -enc decoding (safe, no execution):
      pwsh -NoProfile -Command '$d=[Convert]::FromBase64String("BASE64"); [Text.Encoding]::Unicode.GetString($d)'
      
    • IEX chain (inside the sandbox, replace IEX with output to get the next layer): sed 's/IEX(/Write-Host (/g' layer_01.ps1 > layer_02.ps1
    • Structured extraction of string literals (AST, no execution):
      $ast = [System.Management.Automation.Language.Parser]::ParseFile('layer_01.ps1', [ref]$null, [ref]$null)
      $ast.FindAll({ $args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst] }, $true) | % { $_.Value }
      
    • Common techniques:-join/-split character concatenation,$env:XXX variable substitution, string reversal
  4. VBA (olevba macro extraction + restoration):

    olevba -c sample.docm > macro.txt                 # 提取宏源码
    olevba --decode -c sample.docm > macro_decoded.txt  # 自动解常见字符串混淆(--decode 自 oletools 0.24/2015 起就有,无版本门槛)
    
    • Manual restoration:Chr(65) concatenate and convert ASCII with python3;StrReverse(...) reverse;Evaluate("...") the execution string inside
    • Find auto-execution entry points:AutoOpen / Document_Open / Workbook_Open / Auto_Open
    • Dynamic validation (sandbox required): open the document in Office/LibreOffice in a sandbox (enable macros), observe behavior and the next-layer output
  5. JS (deobfuscation/decoding):

    • eval( → statically replace with console.log((or print() to observe the payload
    • char-code decoding: python3 -c "print(''.join(chr(int(c)) for c in '72,101,108,108,111'.split(',')))"
    • \xHH hexadecimal: python3 -c "print(bytes.fromhex('68656c6c6f').decode())"(first strip the \x prefix)
    • base64 string: python3 -c "import base64; print(base64.b64decode('...'))"
    • beautify: js-beautify layer_01.js -o pretty.js
    • dynamic (sandbox):node payload.js or execute in a browser sandbox to get the next layer

Specific deobfuscation chain recipes

Following the obfuscation style classification from step 1, execute by recipe (the method is the core, tools are replaceable):

eval / execution function replacement: replace eval / IEX / Invoke-Expression / bash with an output statement (in JS eval = console.log; in PowerShell replace IEX(...) with printing), after running it prints the underlying code; decode one layer, look at one layer, then decode the next layer, archive each layer (layer_01.ps1 → layer_02.ps1 …)。

Common JS encoding functions (seeing them means they are decoding targets):unescape()(URL decoding %XX),String.fromCharCode()(character code array),atob()(Base64); hexadecimal \xHH / Unicode \uHHHH batch unescape decoding (python3 -c "import sys; print(sys.argv[1].encode().decode('unicode_escape').decode(errors='replace'))" '<串>');document.write replace with printing.

PowerShell -enc / IEX chain:-EncodedCommand after base64 decoding note UTF-16LE:

[System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($encoded))

Junk code detection(five identifying features):

  • NOP sled
  • push/pop appear in pairs, cancel each other out
  • operation result is 0/identity (arithmetic junk)
  • dead write: register written but never read before the next write
  • unconditional jump to the next instruction

The real logic looks like junk, junk, junk, CALL target, junk, junk—extract all real call targets, ignore surrounding noise (can be implemented with a script filtering by call instructions + junk targets).

Hex encoded payload: after converting hex to bytes, first try common transformations: subtract 1 per byte, XOR single-byte key (try incrementally starting from 0x01).

Common decoding chain patterns:base64 decode → gzip decode → reverse → base64 decode; use file on each layer's output to determine the type and verify readability; if verification fails, the layer order or encoding determination is wrong, roll back one layer and retry.

Advanced obfuscation countermeasures (commercial obfuscators)

Techniques and countermeasures for JScrambler-type commercial obfuscators:

  • Feature identification: bootstrap loader (self-executing entry), hidden string table (array + index references),_0x variable renaming patterns, encrypted function bodies (decrypted at runtime)
  • Control flow flattening (CFF) countermeasures: dispatcher identification (switch dispatch center) → state variable tracking → restore branches to sequential/conditional structures (method links to [[re-deobfuscate]])
  • String encryption countermeasures: Hidden string table localization (decode function call sites) → runtime extraction (execute the decode function inside a sandbox to obtain values, see [[re-sandbox]]) or static restoration (script the decode loop)
  • Dead code injection countermeasure: Unreferenced function filtering (based on call relationships; if there is no caller, it is a deletion candidate)
  • Toolchain: js-beautify to prettify → choose a countermeasure path based on the technique; on the dynamic side, execute in a sandbox to obtain values (default sandbox principle)

Cross-domain joint

  • [[re-managed]]: gateway workflow steps ② (decompile) ③ (deobfuscate) always invoke this skill
  • [[re-malware]]: malicious script/phishing macro sample path (docm/ps1/js attachments → sandbox behavior + this skill restores the payload)
  • [[re-sandbox]]: all dynamic execution (running scripts, enabling macros) is mandatory as a prerequisite, see [[re-analyze/platform-tips]] default sandbox principle
  • Companion: [[re-triage]] (initial survey), [[re-ioc]] (extract C2 domains/URLs from scripts as IOCs), [[re-protocol]] (callback traffic downloaded by scripts)

Common pitfalls and traps

  • Obfuscation nested layer upon layer requires patience to decode layer by layer: Symptom — after decoding one layer there is another, and it feels endless; Cause — multi-layer obfuscation stacking (encoding + concatenation + execution chain) is the norm; Countermeasure — decode layer by layer, archive and number each layer, rerun recognition after each decode, do not skip layers and do not expect to get it done in one step
  • After olevba extraction it is still obfuscated: Symptom —olevba -c The extracted macro is still full of Chr(65) & Chr(66) concatenation; Cause — olevba is only responsible for extraction, not restoration; Countermeasure — use --decode to automatically solve common techniques, manually restore the rest, and finally dynamically verify that the restoration result is correct
  • Malicious scripts must be run inside a sandbox: Symptom — locally directly pwsh sample.ps1 afterward the system is modified/a connection is made outbound; Cause — the script may be a downloader or persistence implant (platform-tips a typical violation scenario of the default sandbox principle); Countermeasure — all dynamic execution goes into [[re-sandbox]] (network isolation + snapshot), static decoding can skip the sandbox
  • Blind regex modification easily breaks script structure: Symptom — after sed global replacement, syntax errors occur and the next layer cannot be decoded; Cause — the obfuscated string contains the replaced pattern, and string literals are accidentally damaged; Countermeasure — prefer AST/structured parsing (PowerShell AST, JS bracket matching), and after each replacement first perform a syntax check (pwsh -NoProfile -Command "[scriptblock]::Create((Get-Content layer.ps1 -Raw))" / node --check layer.js) before proceeding to the next step
  • Locating signature functions in webpack bundles: Symptom — searching "sign" returns too many results; Cause — bundled and minified variable names; Countermeasure — search for characteristic strings (sign=) or use the network panel initiator column to trace back the call stack that initiated the request (faster than searching the source code)
  • Local reproduction results are inconsistent: Symptom — the signature logic is correct but the server does not accept it; Cause — parameter ordering/timestamp precision is wrong; Countermeasure — check the source sort logic (by key alphabetical order + special character rules); for timestamps use Math.floor(Date.now() / 1000)(second-level)
  • The key is in another chunk: Symptom — the key cannot be found in the signature function; Cause — the key is introduced from another chunk via require; Countermeasure — console.log the key variable at a breakpoint in the signature function (Source: reverse-skill field-journal, MIT)
  • Restore transpiled artifacts before analysis: Symptom — in JS you see var state=0; while(true){switch(state){…}} a state machine loop or _asyncToGenerator(function*(){…}) Packaging, the logic is like a tangled mess; cause — generator/async is downgraded by transpilation chains such as Babel/Regenerator into state machines and wrappers, and source-level semantics are mechanically flattened out; countermeasure — after identifying the pattern of a "true constant condition while + single switch body", flatten it in case order into linear statements, and the artificial state variable disappears accordingly; for async, first scan the whole program for wrapped generator functions, then restore Yield in the function body to Await and inline the wrapper; first look at the helper function names to confirm the transpilation chain (Babel/Regenerator etc. each have characteristic fingerprints), and writing an inverse transform for a known toolchain is less effort than general restoration (Source: hermes-decomp (SymbioticSec), MIT)
  • Before patching, first calculate the change length clearly: symptom — the modified script/bundle cannot run or the structure table fails to parse; cause — a same-length change can be overwritten in place, while a variable-length change affects all subsequent offsets and structure sections, and if not corrected in coordination it will corrupt the file; countermeasure — same length -> overwrite in place, without touching the structure table; variable length -> rebuild the affected structure sections (such as the string table) and relocate subsequent sections, correcting all offsets in coordination; always reject patching data with shared storage/overlapping references; after patching, reparse the structure table once to perform a consistency check, and roll back if inconsistent — explicitly report an error for unsupported scenarios, and never stop work and corrupt the file (for general rules, see the corresponding pitfall entry in [[re-hybrid-app]]; for the specific Hermes layout, see that skill; source: hermes-decomp (SymbioticSec), MIT)