reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
Apache License 2.0 — see the repository root LICENSE file for the full text.
|
||||
|
||||
Copyright the AnalystAIPack contributors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
@@ -0,0 +1,94 @@
|
||||
---
|
||||
name: deobfuscating-malicious-javascript
|
||||
description: 'Deobfuscates malicious JavaScript from droppers, web pages, and HTA/scriptlets:
|
||||
unrolling string-array and char-code encodings, resolving eval/Function chains, and statically
|
||||
recovering payloads and URLs without executing untrusted code. Activates for requests to
|
||||
deobfuscate JavaScript, decode obfuscated JS, or analyze a malicious script dropper.'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware-analysis
|
||||
- javascript
|
||||
- deobfuscation
|
||||
- document-malware
|
||||
- droppers
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1059.007
|
||||
- T1027
|
||||
- T1140
|
||||
d3fend:
|
||||
- D3-FCR
|
||||
- D3-DA
|
||||
references:
|
||||
- 'ECMAScript language specification — https://tc39.es/ecma262/'
|
||||
- 'box-js malicious JavaScript sandbox — https://github.com/CapacitorSet/box-js'
|
||||
---
|
||||
|
||||
# Deobfuscating Malicious JavaScript
|
||||
|
||||
## When to Use
|
||||
|
||||
- You have obfuscated JS (from a phishing page, HTA, `.js` dropper, or scriptlet) and need its real
|
||||
behavior.
|
||||
- You must recover hidden URLs, dropped commands, or a next-stage payload.
|
||||
- You want to statically unroll common obfuscation rather than run untrusted code.
|
||||
|
||||
**Do not use** a real browser or `node` to execute the script for analysis — that runs the
|
||||
malware. Use a sandboxed interpreter (box-js) or static transformation only.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A safe analysis approach: static decoding, or a JS malware sandbox (box-js) in the lab.
|
||||
- Familiarity with common obfuscation: string arrays, `String.fromCharCode`, hex/unicode escapes,
|
||||
`eval`/`Function` chains, packers.
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Treat the script as live code; never execute it outside an isolated sandbox.
|
||||
- Defang recovered URLs and store dropped payloads password-protected.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Normalize and de-pack
|
||||
|
||||
Pretty-print the source and identify the obfuscation style (array-shuffle, eval-packer, charcode).
|
||||
Decode static encodings first: hex/unicode escapes and base64 literals.
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py decode dropper.js
|
||||
```
|
||||
|
||||
### Step 2: Unroll string arrays and char-code builds
|
||||
|
||||
Reconstruct strings built from arrays/`fromCharCode`/concatenation to reveal API names, URLs, and
|
||||
commands.
|
||||
|
||||
### Step 3: Resolve eval/Function indirection — safely
|
||||
|
||||
Replace `eval`/`new Function` with logging (or a sandbox) so the constructed code is captured as
|
||||
*data*, not executed, then recurse on the recovered layer.
|
||||
|
||||
### Step 4: Extract behavior and IOCs
|
||||
|
||||
Identify the dropper actions (WScript.Shell, ActiveX, fetch/XHR), recover URLs and dropped paths,
|
||||
defang, and route payloads onward.
|
||||
|
||||
## Validation
|
||||
|
||||
- Static encodings (hex/unicode/base64) are fully decoded.
|
||||
- String-array/charcode constructions are unrolled to readable strings.
|
||||
- `eval`/`Function` layers are captured as data and recursed, with no untrusted execution.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Running the script to "see what it does" and infecting the analysis host.
|
||||
- Stopping at one layer when the dropper nests several.
|
||||
- Missing environment-keyed branches (only acts on certain dates/locales) during static review.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the static JS decoder.
|
||||
- ECMAScript spec and box-js (linked in frontmatter).
|
||||
@@ -0,0 +1,40 @@
|
||||
# API Reference: Deobfuscating Malicious JavaScript
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` statically decodes common JS obfuscation: `\xHH`/`\uHHHH` escapes,
|
||||
`String.fromCharCode(...)` sequences, and standalone base64 string literals, then extracts and
|
||||
defangs URLs. It executes nothing; dynamic `eval`/`Function` layers require a sandbox (box-js).
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | base64, regex, JSON, file IO |
|
||||
|
||||
## External tools
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| box-js | Sandboxed execution of `eval`/`Function` layers without real browser/WScript |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `decode(source)`
|
||||
Applies escape, fromCharCode, and base64 decoding; returns a preview, decoded base64, and defanged
|
||||
URLs.
|
||||
|
||||
### `_decode_escapes` / `_decode_fromcharcode` / `_decode_b64_literals`
|
||||
The individual static-decoding passes.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py decode dropper.js
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- ECMAScript language specification — https://tc39.es/ecma262/
|
||||
- box-js malicious JavaScript sandbox — https://github.com/CapacitorSet/box-js
|
||||
- Common JS obfuscation families (string arrays, fromCharCode, eval-packers), established practice
|
||||
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Statically decode common JavaScript obfuscation layers.
|
||||
|
||||
Without executing anything, this resolves frequent static encodings: ``\\xHH`` and ``\\uHHHH``
|
||||
escapes, ``String.fromCharCode(...)`` sequences, and standalone base64 string literals. It also
|
||||
extracts URLs from the decoded text and defangs them. Intended as a first-pass static aid before
|
||||
a sandbox (box-js) for dynamic layers.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import binascii
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
RE_HEX = re.compile(r"\\x([0-9a-fA-F]{2})")
|
||||
RE_UNI = re.compile(r"\\u([0-9a-fA-F]{4})")
|
||||
RE_FROMCHARCODE = re.compile(r"String\.fromCharCode\(([^)]*)\)", re.I)
|
||||
RE_B64 = re.compile(r"['\"]([A-Za-z0-9+/]{24,}={0,2})['\"]")
|
||||
RE_URL = re.compile(r"https?://[^\s'\"<>)]+", re.I)
|
||||
|
||||
|
||||
def _decode_escapes(text: str) -> str:
|
||||
text = RE_HEX.sub(lambda m: chr(int(m.group(1), 16)), text)
|
||||
text = RE_UNI.sub(lambda m: chr(int(m.group(1), 16)), text)
|
||||
return text
|
||||
|
||||
|
||||
def _decode_fromcharcode(text: str) -> str:
|
||||
def repl(m: re.Match) -> str:
|
||||
parts = re.split(r"[,\s]+", m.group(1).strip())
|
||||
chars = []
|
||||
for p in parts:
|
||||
p = p.strip()
|
||||
try:
|
||||
chars.append(chr(int(p, 0)))
|
||||
except (ValueError, TypeError):
|
||||
return m.group(0)
|
||||
return '"' + "".join(chars) + '"'
|
||||
return RE_FROMCHARCODE.sub(repl, text)
|
||||
|
||||
|
||||
def _decode_b64_literals(text: str) -> list[str]:
|
||||
out = []
|
||||
for m in RE_B64.finditer(text):
|
||||
blob = m.group(1)
|
||||
try:
|
||||
dec = base64.b64decode(blob).decode("latin-1")
|
||||
if sum(c.isprintable() for c in dec) > len(dec) * 0.8:
|
||||
out.append(dec)
|
||||
except (binascii.Error, ValueError):
|
||||
continue
|
||||
return out
|
||||
|
||||
|
||||
def _defang(url: str) -> str:
|
||||
return url.replace("http", "hxxp").replace(".", "[.]")
|
||||
|
||||
|
||||
def decode(source: str) -> dict:
|
||||
stage1 = _decode_escapes(source)
|
||||
stage2 = _decode_fromcharcode(stage1)
|
||||
b64 = _decode_b64_literals(stage2)
|
||||
urls = sorted(set(RE_URL.findall(stage2)) | {u for d in b64 for u in RE_URL.findall(d)})
|
||||
return {
|
||||
"decoded_preview": stage2[:1500],
|
||||
"base64_decoded": b64[:20],
|
||||
"urls": [_defang(u) for u in urls],
|
||||
"note": "static decode only; use box-js sandbox for eval/Function layers",
|
||||
}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Statically decode obfuscated JS")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("decode")
|
||||
p.add_argument("file")
|
||||
args = parser.parse_args(argv)
|
||||
source = Path(args.file).read_text(encoding="utf-8-sig", errors="replace")
|
||||
print(json.dumps(decode(source), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user