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,100 @@
|
||||
---
|
||||
name: analyzing-android-dex-malware
|
||||
description: 'Reverses Android malware: unpacking APKs, decompiling DEX bytecode to readable
|
||||
Java, auditing the manifest for abused permissions and components, and locating dynamically
|
||||
loaded or native payloads. Activates for requests to analyze an APK, decompile DEX, or
|
||||
investigate a suspicious Android app.'
|
||||
domain: cybersecurity
|
||||
subdomain: reverse-engineering
|
||||
tags:
|
||||
- reverse-engineering
|
||||
- android
|
||||
- dex
|
||||
- apk
|
||||
- mobile
|
||||
- decompilation
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1059
|
||||
- T1406
|
||||
- T1577
|
||||
d3fend:
|
||||
- D3-FCR
|
||||
- D3-DA
|
||||
references:
|
||||
- 'Android DEX bytecode format — https://source.android.com/docs/core/runtime/dex-format'
|
||||
- 'jadx DEX-to-Java decompiler — https://github.com/skylot/jadx'
|
||||
---
|
||||
|
||||
# Analyzing Android DEX Malware
|
||||
|
||||
## When to Use
|
||||
|
||||
- You have a suspicious `.apk` (or bare `.dex`) and need to understand its behavior.
|
||||
- You need to audit the manifest for dangerous permissions, exported components, and the
|
||||
declared entry points.
|
||||
- The app loads code dynamically (`DexClassLoader`) or ships a native `.so` you must locate.
|
||||
|
||||
**Do not use** this workflow for iOS apps — DEX/APK tooling does not apply to Mach-O/IPA.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- jadx (or apktool + a decompiler) and `unzip`; an Android emulator/sandbox for dynamic runs.
|
||||
- `aapt`/manifest parsing for permissions and components.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Unpack the APK
|
||||
|
||||
An APK is a ZIP. Extract and inventory the DEX files, native libraries, and assets:
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py inspect sample.apk
|
||||
```
|
||||
|
||||
```text
|
||||
dex : classes.dex, classes2.dex
|
||||
native : lib/arm64-v8a/libpayload.so
|
||||
assets : assets/config.enc (possible encrypted payload)
|
||||
manifest : AndroidManifest.xml (binary)
|
||||
```
|
||||
|
||||
### Step 2: Audit the manifest
|
||||
|
||||
Decode `AndroidManifest.xml` and review requested permissions (SMS, accessibility, device
|
||||
admin, `REQUEST_INSTALL_PACKAGES`), exported components, and the launcher/`BOOT_COMPLETED`
|
||||
receivers.
|
||||
|
||||
### Step 3: Decompile DEX
|
||||
|
||||
Run jadx to recover Java. Start at the launcher activity and any `BroadcastReceiver`/`Service`
|
||||
declared in the manifest.
|
||||
|
||||
### Step 4: Find dynamic and native code
|
||||
|
||||
Search for `DexClassLoader`/`loadDex`, asset decryption, and `System.loadLibrary`. Dump and
|
||||
recurse on dynamically loaded DEX; analyze native `.so` separately if needed.
|
||||
|
||||
### Step 5: Extract behavior and IOCs
|
||||
|
||||
Recover C2 URLs, overlay/accessibility abuse, SMS interception, and config; map to ATT&CK for
|
||||
mobile in the report.
|
||||
|
||||
## Validation
|
||||
|
||||
- Every DEX and native library in the APK is accounted for.
|
||||
- Dangerous permissions are tied to concrete code paths (not just declared).
|
||||
- Dynamically loaded payloads are dumped and analyzed, not just noted.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Reading only `classes.dex` and missing `classes2.dex`/`classes3.dex`.
|
||||
- Trusting the manifest alone; behavior may hide behind dynamic loading.
|
||||
- Ignoring encrypted assets that become the real payload at runtime.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the APK inspector.
|
||||
- Android DEX format and jadx (linked in frontmatter).
|
||||
@@ -0,0 +1,39 @@
|
||||
# API Reference: Analyzing Android DEX Malware
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` treats an APK as a ZIP and inventories its DEX files, native libraries,
|
||||
assets, and manifest presence, flagging multidex and likely encrypted payload assets. It does
|
||||
not decompile or execute — use jadx/apktool for decompilation.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | zipfile, file IO |
|
||||
|
||||
## External tools
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| jadx | Decompile DEX bytecode to Java |
|
||||
| apktool | Decode resources and binary AndroidManifest.xml |
|
||||
| Android emulator | Dynamic analysis of dynamic/native payloads |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `inspect(path)`
|
||||
Returns the DEX list, native libraries, assets, manifest presence, multidex flag, and triage
|
||||
notes.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py inspect sample.apk
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- Android DEX bytecode format — https://source.android.com/docs/core/runtime/dex-format
|
||||
- APK packaging (ZIP container, AndroidManifest.xml) — Android documentation
|
||||
- jadx DEX-to-Java decompiler — https://github.com/skylot/jadx
|
||||
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Inspect an APK (ZIP) and inventory its DEX, native libraries, and assets.
|
||||
|
||||
Lists the components relevant to triage — DEX files, native ``.so`` libraries, the manifest,
|
||||
and notable assets — without decompiling or executing anything. Use jadx/apktool for the
|
||||
actual decompilation step.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def inspect(path: str) -> dict:
|
||||
if not zipfile.is_zipfile(path):
|
||||
return {"error": "not a ZIP/APK"}
|
||||
dex, native, assets, other = [], [], [], []
|
||||
with zipfile.ZipFile(path) as zf:
|
||||
for name in zf.namelist():
|
||||
if name.endswith(".dex"):
|
||||
dex.append(name)
|
||||
elif name.startswith("lib/") and name.endswith(".so"):
|
||||
native.append(name)
|
||||
elif name.startswith("assets/"):
|
||||
assets.append(name)
|
||||
elif name == "AndroidManifest.xml":
|
||||
other.append(name)
|
||||
return {
|
||||
"dex": sorted(dex),
|
||||
"native_libs": sorted(native),
|
||||
"assets": sorted(assets),
|
||||
"has_manifest": "AndroidManifest.xml" in other,
|
||||
"multidex": len(dex) > 1,
|
||||
"notes": _notes(dex, native, assets),
|
||||
}
|
||||
|
||||
|
||||
def _notes(dex, native, assets) -> list[str]:
|
||||
notes = []
|
||||
if len(dex) > 1:
|
||||
notes.append("multidex: analyze all classesN.dex")
|
||||
if native:
|
||||
notes.append("native libraries present: analyze .so separately")
|
||||
enc = [a for a in assets if a.endswith((".enc", ".dat", ".bin"))]
|
||||
if enc:
|
||||
notes.append(f"possible encrypted payload assets: {enc}")
|
||||
return notes
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Inspect an APK")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("inspect")
|
||||
p.add_argument("file")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(inspect(args.file), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -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,83 @@
|
||||
---
|
||||
name: analyzing-api-call-traces
|
||||
description: 'Analyzes API call traces from a sandbox or API monitor (JSON) to group calls by
|
||||
category, reconstruct high-level behaviors (process injection, file drops, network, crypto), and
|
||||
flag suspicious call sequences. Activates for requests to analyze an API trace, interpret sandbox
|
||||
API logs, or identify behavior from Win32/Native API calls.'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware-analysis
|
||||
- dynamic-analysis
|
||||
- api-trace
|
||||
- winapi
|
||||
- sandbox
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1106
|
||||
- T1055
|
||||
- T1027
|
||||
d3fend:
|
||||
- D3-PSA
|
||||
- D3-SDA
|
||||
references:
|
||||
- 'MITRE ATT&CK T1106 Native API — https://attack.mitre.org/techniques/T1106/'
|
||||
- 'Windows API Index — https://learn.microsoft.com/windows/win32/apiindex/windows-api-list'
|
||||
---
|
||||
|
||||
# Analyzing API Call Traces
|
||||
|
||||
## When to Use
|
||||
|
||||
- You have a JSON API trace (CAPE/Cuckoo "apistats"/calls, API Monitor export) from a detonation.
|
||||
- You need to translate raw API calls into high-level behaviors and flag suspicious sequences
|
||||
(e.g., `VirtualAllocEx` → `WriteProcessMemory` → `CreateRemoteThread`).
|
||||
|
||||
**Do not use** this as a substitute for detonation — it consumes an existing trace produced in an
|
||||
isolated sandbox.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A JSON API trace with at least an API name per call (and optionally arguments).
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Consume traces only; never execute the sample to generate one outside a sandbox.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Group calls by category
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py classify trace.json
|
||||
```
|
||||
|
||||
Buckets calls into process, memory, file, registry, network, crypto, and synchronization.
|
||||
|
||||
### Step 2: Detect behavior signatures
|
||||
|
||||
Matches ordered API sequences that indicate techniques — remote injection, hollowing,
|
||||
self-deletion, dynamic API resolution (`LoadLibrary`/`GetProcAddress` bursts).
|
||||
|
||||
### Step 3: Prioritize and report
|
||||
|
||||
Rank detected behaviors by severity and map each to ATT&CK.
|
||||
|
||||
## Validation
|
||||
|
||||
- Each API call is categorized; unknown APIs are reported, not dropped silently.
|
||||
- Injection-style sequences are detected only when the ordered prerequisites are present.
|
||||
- Detected behaviors map to ATT&CK techniques.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Flagging individual benign APIs out of context — the sequence matters.
|
||||
- Assuming call order in the JSON equals execution order without a timestamp/index.
|
||||
- Ignoring failed calls (non-zero error) that never actually executed the behavior.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the classifier.
|
||||
- ATT&CK T1106 and the Windows API Index (linked in frontmatter).
|
||||
@@ -0,0 +1,42 @@
|
||||
# API Reference: Analyzing API Call Traces
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` classifies a JSON API trace into behavioral categories and detects ordered
|
||||
API sequences indicative of process injection, hollowing, dynamic API resolution, and crypto
|
||||
routines. It parses JSON only and executes nothing.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | json, argparse |
|
||||
|
||||
## Accepted input shapes
|
||||
|
||||
- A list of strings (API names) or call objects with `api`/`name`/`function` keys.
|
||||
- A dict containing `calls`, `apis`, or a CAPE-style `apistats` mapping.
|
||||
|
||||
## Core functions
|
||||
|
||||
### `classify(path)`
|
||||
Returns total call count, per-category counts, detected behaviors (with ATT&CK IDs), and unknown
|
||||
APIs.
|
||||
|
||||
## Sequence signatures
|
||||
|
||||
`VirtualAllocEx → WriteProcessMemory → CreateRemoteThread` (T1055.002), hollowing (T1055.012),
|
||||
`LoadLibrary → GetProcAddress` (T1027.007), `CryptAcquireContext → CryptDecrypt` (T1027). Matching
|
||||
respects order via a sliding cursor.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py classify trace.json
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- MITRE ATT&CK T1106 Native API; T1055 Process Injection — https://attack.mitre.org/
|
||||
- Windows API Index — https://learn.microsoft.com/windows/win32/apiindex/windows-api-list
|
||||
- CAPE/Cuckoo report `behavior.apistats` structure (public sandbox schema)
|
||||
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Classify a JSON API call trace and detect behavior signatures.
|
||||
|
||||
Accepts a flexible JSON trace: a list of call objects, or a dict with a "calls"/"apis" list, or a
|
||||
CAPE-style apistats mapping. Categorizes API names and matches ordered sequences indicative of
|
||||
process injection, dynamic API resolution, and self-deletion. Parses JSON only; executes nothing.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
|
||||
CATEGORIES = {
|
||||
"process": ["createprocess", "openprocess", "createremotethread", "ntcreatethreadex",
|
||||
"resumethread", "terminateprocess"],
|
||||
"memory": ["virtualalloc", "virtualallocex", "virtualprotect", "writeprocessmemory",
|
||||
"readprocessmemory", "ntmapviewofsection", "ntunmapviewofsection"],
|
||||
"file": ["createfile", "writefile", "readfile", "deletefile", "copyfile", "movefile"],
|
||||
"registry": ["regopenkey", "regsetvalue", "regcreatekey", "regqueryvalue"],
|
||||
"network": ["internetopen", "internetconnect", "httpsendrequest", "wininet", "winhttp",
|
||||
"send", "recv", "connect", "getaddrinfo"],
|
||||
"crypto": ["cryptencrypt", "cryptdecrypt", "cryptacquirecontext", "bcryptencrypt",
|
||||
"cryptstringtobinary"],
|
||||
"resolve": ["loadlibrary", "getprocaddress", "ldrloaddll", "ldrgetprocedureaddress"],
|
||||
"sync": ["createmutex", "openmutex", "createevent", "sleep", "waitforsingleobject"],
|
||||
}
|
||||
|
||||
SIGNATURES = [
|
||||
("remote_process_injection", ["virtualallocex", "writeprocessmemory", "createremotethread"],
|
||||
"T1055.002"),
|
||||
("process_hollowing", ["createprocess", "ntunmapviewofsection", "writeprocessmemory"],
|
||||
"T1055.012"),
|
||||
("dynamic_api_resolution", ["loadlibrary", "getprocaddress"], "T1027.007"),
|
||||
("crypto_routine", ["cryptacquirecontext", "cryptdecrypt"], "T1027"),
|
||||
]
|
||||
|
||||
|
||||
def _extract_calls(obj) -> list[str]:
|
||||
if isinstance(obj, list):
|
||||
out = []
|
||||
for c in obj:
|
||||
if isinstance(c, str):
|
||||
out.append(c)
|
||||
elif isinstance(c, dict):
|
||||
out.append(c.get("api") or c.get("name") or c.get("function") or "")
|
||||
return [c for c in out if c]
|
||||
if isinstance(obj, dict):
|
||||
for key in ("calls", "apis", "apistats"):
|
||||
if key in obj:
|
||||
val = obj[key]
|
||||
if isinstance(val, dict):
|
||||
return list(val.keys())
|
||||
return _extract_calls(val)
|
||||
return []
|
||||
|
||||
|
||||
def _category(api: str) -> str:
|
||||
low = api.lower()
|
||||
for cat, names in CATEGORIES.items():
|
||||
if any(n in low for n in names):
|
||||
return cat
|
||||
return "other"
|
||||
|
||||
|
||||
def classify(path: str) -> dict:
|
||||
data = json.loads(open(path, encoding="utf-8-sig").read())
|
||||
calls = _extract_calls(data)
|
||||
lowered = [c.lower() for c in calls]
|
||||
cats: dict[str, int] = {}
|
||||
unknown = set()
|
||||
for api in calls:
|
||||
cat = _category(api)
|
||||
cats[cat] = cats.get(cat, 0) + 1
|
||||
if cat == "other":
|
||||
unknown.add(api)
|
||||
detected = []
|
||||
for name, seq, tech in SIGNATURES:
|
||||
pos = 0
|
||||
for needle in seq:
|
||||
found = next((i for i in range(pos, len(lowered)) if needle in lowered[i]), -1)
|
||||
if found == -1:
|
||||
break
|
||||
pos = found + 1
|
||||
else:
|
||||
detected.append({"behavior": name, "attack": tech})
|
||||
return {
|
||||
"total_calls": len(calls),
|
||||
"categories": cats,
|
||||
"behaviors": detected,
|
||||
"unknown_apis": sorted(unknown)[:50],
|
||||
}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Classify an API call trace")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("classify")
|
||||
p.add_argument("trace")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(classify(args.trace), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -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,91 @@
|
||||
---
|
||||
name: analyzing-authenticode-signatures
|
||||
description: 'Analyzes Windows Authenticode signatures on PE files: checking for a signature,
|
||||
reading the signer certificate chain, detecting revoked/expired/stolen certs, and recognizing
|
||||
signature-stripping and catalog-signing abuse. Activates for requests to analyze a code
|
||||
signature, verify Authenticode, or assess signer trust on a PE.'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware-analysis
|
||||
- authenticode
|
||||
- code-signing
|
||||
- pe
|
||||
- certificates
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1553.002
|
||||
- T1036.001
|
||||
d3fend:
|
||||
- D3-FCR
|
||||
references:
|
||||
- 'Windows Authenticode PE signature format — https://learn.microsoft.com/windows-hardware/drivers/install/authenticode'
|
||||
- 'Microsoft PE/COFF specification (security directory) — https://learn.microsoft.com/windows/win32/debug/pe-format'
|
||||
---
|
||||
|
||||
# Analyzing Authenticode Signatures
|
||||
|
||||
## When to Use
|
||||
|
||||
- You need to know whether a PE is signed and whether the signer is trustworthy.
|
||||
- You suspect a stolen/abused code-signing certificate or a forged/invalid signature.
|
||||
- You are distinguishing legitimately signed software from signed malware.
|
||||
|
||||
**Do not use** "is signed" as "is safe" — adversaries use stolen certs, abuse catalog signing,
|
||||
and append invalid signatures; a valid signature only attests the signer, not benignity.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Access to the PE security directory (Python `struct`/`pefile`); on Windows, `signtool`/PowerShell
|
||||
`Get-AuthenticodeSignature` for authoritative verification.
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Parse the signature statically; never execute the sample.
|
||||
- Keep the sample password-protected at rest and reference it by hash.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Check for a signature
|
||||
|
||||
Read the PE security directory (certificate table). Absent entry = unsigned; present = embedded
|
||||
PKCS#7. Note that valid signing can also be via a catalog file (not embedded).
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py check sample.exe
|
||||
```
|
||||
|
||||
### Step 2: Extract signer details
|
||||
|
||||
Parse the PKCS#7 to read the signer certificate: subject, issuer, validity dates, and serial.
|
||||
Cross-reference against known-abused/stolen certs.
|
||||
|
||||
### Step 3: Verify, do not just read
|
||||
|
||||
On Windows, verify the signature cryptographically and check revocation
|
||||
(`Get-AuthenticodeSignature`). A present-but-invalid signature is a strong red flag.
|
||||
|
||||
### Step 4: Assess trust in context
|
||||
|
||||
Weigh signer reputation, certificate age, and whether the signed file's behavior matches the
|
||||
purported publisher. Treat anomalies (recently issued cert, mismatched publisher) as suspicious.
|
||||
|
||||
## Validation
|
||||
|
||||
- Signature presence is determined from the security directory, and verified (not just read) on
|
||||
Windows.
|
||||
- Signer certificate details are extracted and checked against abuse intel.
|
||||
- The trust conclusion accounts for revocation/validity, not mere presence.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Equating a present signature with safety; stolen-cert malware is common.
|
||||
- Missing catalog-signed files by only checking embedded signatures.
|
||||
- Reading the signature without cryptographically verifying it.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the signature checker.
|
||||
- Authenticode format and the PE/COFF security directory (linked in frontmatter).
|
||||
@@ -0,0 +1,39 @@
|
||||
# API Reference: Analyzing Authenticode Signatures
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` parses the PE security data directory (index 4) to detect an embedded PKCS#7
|
||||
Authenticode signature (WIN_CERTIFICATE), reporting presence, size, revision, and type. It is a
|
||||
dependency-free presence check; cryptographic verification requires OS tooling. It parses bytes
|
||||
statically and never executes the sample.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | struct parsing, file IO |
|
||||
|
||||
## External tools
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| signtool / Get-AuthenticodeSignature | Cryptographically verify the signature and revocation |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `check(path)`
|
||||
Returns whether an embedded signature exists and the WIN_CERTIFICATE header fields.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py check sample.exe
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- Windows Authenticode PE signature format —
|
||||
https://learn.microsoft.com/windows-hardware/drivers/install/authenticode
|
||||
- PE/COFF security data directory (index 4, file-offset semantics) —
|
||||
https://learn.microsoft.com/windows/win32/debug/pe-format
|
||||
- Stolen/abused signing certificates (T1553.002), MITRE ATT&CK
|
||||
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check a PE for an embedded Authenticode signature and summarize the certificate blob.
|
||||
|
||||
Parses the PE optional-header security data directory (index 4) to detect an embedded PKCS#7
|
||||
signature (WIN_CERTIFICATE), reporting its presence, size, and revision/type. Deep certificate
|
||||
parsing and cryptographic verification require a PKCS#7 library / OS tooling; this gives a fast,
|
||||
dependency-free presence check. It parses bytes statically and never executes the sample.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import struct
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
WIN_CERT_TYPE = {0x0001: "X509", 0x0002: "PKCS_SIGNED_DATA", 0x0003: "RESERVED",
|
||||
0x0004: "TS_STACK_SIGNED"}
|
||||
|
||||
|
||||
def check(path: str) -> dict:
|
||||
data = Path(path).read_bytes()
|
||||
if data[:2] != b"MZ":
|
||||
return {"error": "not a PE"}
|
||||
e_lfanew = struct.unpack_from("<I", data, 0x3C)[0]
|
||||
if data[e_lfanew:e_lfanew + 4] != b"PE\x00\x00":
|
||||
return {"error": "no PE signature"}
|
||||
opt = e_lfanew + 24
|
||||
magic = struct.unpack_from("<H", data, opt)[0]
|
||||
pe32_plus = magic == 0x20B
|
||||
dd_offset = opt + (112 if pe32_plus else 96)
|
||||
sec_dir = dd_offset + 4 * 8 # security directory is index 4
|
||||
rva, size = struct.unpack_from("<II", data, sec_dir)
|
||||
if not rva or not size:
|
||||
return {"signed_embedded": False,
|
||||
"note": "no embedded signature (may still be catalog-signed)"}
|
||||
# For the security directory, 'rva' is a file offset (not an RVA)
|
||||
dwlen, wrev, wtype = struct.unpack_from("<IHH", data, rva)
|
||||
return {
|
||||
"signed_embedded": True,
|
||||
"certificate_table_offset": rva,
|
||||
"certificate_table_size": size,
|
||||
"win_cert_length": dwlen,
|
||||
"revision": hex(wrev),
|
||||
"type": WIN_CERT_TYPE.get(wtype, hex(wtype)),
|
||||
"note": "presence only; verify cryptographically with signtool / "
|
||||
"Get-AuthenticodeSignature",
|
||||
}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Check Authenticode signature presence")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("check")
|
||||
p.add_argument("file")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(check(args.file), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -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,88 @@
|
||||
---
|
||||
name: analyzing-banking-trojan-webinjects
|
||||
description: 'Analyzes banking trojan webinject configurations to extract targeted institutions,
|
||||
injected JavaScript/HTML, and data-theft hooks, mapping the fraud workflow. Activates for
|
||||
requests to analyze banking trojan webinjects, parse a webinject config, or map targeted banks
|
||||
and credential-theft injections.'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware-analysis
|
||||
- banking-trojan
|
||||
- webinject
|
||||
- fraud
|
||||
- credential-theft
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1185
|
||||
- T1557
|
||||
- T1056.001
|
||||
d3fend:
|
||||
- D3-SDA
|
||||
- D3-DA
|
||||
references:
|
||||
- 'MITRE ATT&CK T1185 Browser Session Hijacking — https://attack.mitre.org/techniques/T1185/'
|
||||
- 'Zeus/SpyEye webinject format (public malware research) — https://attack.mitre.org/software/S0259/'
|
||||
---
|
||||
|
||||
# Analyzing Banking Trojan Webinjects
|
||||
|
||||
## When to Use
|
||||
|
||||
- You have a banking trojan webinject config (Zeus/Gozi/Dridex-style `set_url`/`data_before`/
|
||||
`data_inject` blocks) and want to extract targeted institutions and injected code.
|
||||
- You are mapping the web-fraud workflow (form-grabbing, fake fields, MFA interception).
|
||||
|
||||
**Do not use** this to deploy or test injects against live banking sites. This skill parses the
|
||||
config statically and executes nothing.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- The decoded webinject config text (decrypt the config first if needed).
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Treat injected JS/HTML as malicious; do not render it. Defang any exfil URLs.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Parse webinject blocks
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py parse webinjects.txt
|
||||
```
|
||||
|
||||
Parses `set_url`/`data_before`/`data_inject`/`data_after` blocks into structured entries with the
|
||||
targeted URL pattern and the injected snippet.
|
||||
|
||||
### Step 2: Extract targets and exfil endpoints
|
||||
|
||||
List targeted institution URL patterns and any exfil endpoints referenced by the injected code.
|
||||
|
||||
### Step 3: Classify the injection technique
|
||||
|
||||
Identify form-grabbing, fake additional fields (SSN, PIN, card), and MFA/OTP interception from the
|
||||
injected markup/JS.
|
||||
|
||||
### Step 4: Defang and report
|
||||
|
||||
Defang URLs and summarize targeted banks and theft techniques.
|
||||
|
||||
## Validation
|
||||
|
||||
- Each webinject block is parsed into target URL + injection.
|
||||
- Exfil endpoints in injected code are extracted and defanged.
|
||||
- Injection techniques are classified from the snippet content.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Encrypted/packed configs needing decryption before parsing.
|
||||
- Wildcard URL patterns matching broadly — note the scope.
|
||||
- Obfuscated injected JS hiding the true exfil endpoint.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the parser.
|
||||
- ATT&CK T1185 and webinject format references (linked in frontmatter).
|
||||
@@ -0,0 +1,43 @@
|
||||
# API Reference: Analyzing Banking Trojan Webinjects
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` parses Zeus/SpyEye-style webinject configs (`set_url` + `data_before`/
|
||||
`data_inject`/`data_after` blocks), associates each target URL pattern with its injected snippet,
|
||||
classifies the injection technique, and extracts/defangs exfil endpoints. It parses config text
|
||||
only, executes nothing, and never renders injected code.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | regex, JSON, file IO |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `parse(path)`
|
||||
Returns webinject count, targeted URL patterns, and per-entry technique/exfil/preview details.
|
||||
|
||||
## Technique classes
|
||||
|
||||
- form_grabbing (onsubmit/form hooks)
|
||||
- fake_fields (SSN/PIN/card/CVV/DOB additions)
|
||||
- mfa_interception (OTP/2FA/token capture)
|
||||
- balance_manipulation (innerHTML/balance rewriting)
|
||||
|
||||
## Webinject format
|
||||
|
||||
`set_url <pattern> <flags>` followed by `data_before … data_end`, `data_inject … data_end`,
|
||||
`data_after … data_end`.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py parse webinjects.txt
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- MITRE ATT&CK T1185 Browser Session Hijacking — https://attack.mitre.org/techniques/T1185/
|
||||
- Zeus/SpyEye webinject grammar (set_url/data_before/data_inject) from public malware research
|
||||
- Web-fraud injection techniques (form-grab, fake fields, OTP capture) from banking-trojan analyses
|
||||
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Parse banking trojan webinject configs (Zeus/SpyEye-style) into structured entries.
|
||||
|
||||
Parses set_url / data_before / data_inject / data_after blocks, extracts the targeted URL pattern
|
||||
and injected snippet, classifies the injection technique, and extracts/defangs exfil endpoints.
|
||||
Parses config text only; executes nothing and does not render injected code.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
RE_SETURL = re.compile(r"set_url\s+(\S+)(?:\s+(\S+))?", re.I)
|
||||
RE_BLOCK = re.compile(
|
||||
r"data_(before|inject|after)\b(.*?)data_end", re.I | re.S)
|
||||
RE_URL = re.compile(r"https?://[^\s\"'<>]+", re.I)
|
||||
|
||||
TECHNIQUE_HINTS = {
|
||||
"form_grabbing": ["onsubmit", "form", "addeventlistener", "password"],
|
||||
"fake_fields": ["ssn", "social security", "pin", "card number", "cvv", "mother",
|
||||
"date of birth", "atm"],
|
||||
"mfa_interception": ["otp", "one-time", "token", "2fa", "verification code", "sms code"],
|
||||
"balance_manipulation": ["balance", "innerhtml", "replace"],
|
||||
}
|
||||
|
||||
|
||||
def parse(path: str) -> dict:
|
||||
text = Path(path).read_text(encoding="utf-8-sig", errors="replace")
|
||||
entries = []
|
||||
# Split on set_url boundaries to associate each target with its following blocks.
|
||||
chunks = re.split(r"(?=set_url\s)", text, flags=re.I)
|
||||
for chunk in chunks:
|
||||
m = RE_SETURL.search(chunk)
|
||||
if not m:
|
||||
continue
|
||||
target = m.group(1)
|
||||
inject = ""
|
||||
for bname, body in RE_BLOCK.findall(chunk):
|
||||
if bname.lower() == "inject":
|
||||
inject = body.strip()
|
||||
low = inject.lower()
|
||||
techniques = [t for t, needles in TECHNIQUE_HINTS.items()
|
||||
if any(n in low for n in needles)]
|
||||
exfil = sorted({u.replace("http", "hxxp").replace(".", "[.]")
|
||||
for u in RE_URL.findall(inject)})
|
||||
entries.append({
|
||||
"target_url": target,
|
||||
"techniques": techniques,
|
||||
"exfil_endpoints": exfil,
|
||||
"inject_preview": inject[:160],
|
||||
})
|
||||
targets = sorted({e["target_url"] for e in entries})
|
||||
return {
|
||||
"webinject_count": len(entries),
|
||||
"targeted_url_patterns": targets,
|
||||
"entries": entries,
|
||||
}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Parse banking trojan webinjects")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("parse")
|
||||
p.add_argument("file")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(parse(args.file), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -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,89 @@
|
||||
---
|
||||
name: analyzing-compiled-python-malware
|
||||
description: 'Analyzes Python-based malware packaged as PyInstaller/py2exe executables by detecting
|
||||
the packer, locating the embedded archive, and identifying bundled .pyc modules for extraction and
|
||||
decompilation. Activates for requests to analyze compiled Python malware, unpack a PyInstaller
|
||||
binary, or extract pyc modules from a frozen Python executable.'
|
||||
domain: cybersecurity
|
||||
subdomain: reverse-engineering
|
||||
tags:
|
||||
- reverse-engineering
|
||||
- python
|
||||
- pyinstaller
|
||||
- pyc
|
||||
- unpacking
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1027
|
||||
- T1059.006
|
||||
- T1587.001
|
||||
d3fend:
|
||||
- D3-SDA
|
||||
- D3-DA
|
||||
references:
|
||||
- 'PyInstaller archive format (CArchive/PKG) — https://pyinstaller.org/en/stable/advanced-topics.html'
|
||||
- 'Python bytecode (dis module / .pyc magic) — https://docs.python.org/3/library/dis.html'
|
||||
---
|
||||
|
||||
# Analyzing Compiled Python Malware
|
||||
|
||||
## When to Use
|
||||
|
||||
- You have an executable that is actually a frozen Python app (PyInstaller, py2exe, cx_Freeze) and
|
||||
need to identify the packer and locate the embedded Python modules.
|
||||
- You want to extract `.pyc` files for decompilation.
|
||||
|
||||
**Do not use** this to run the executable — it identifies and locates the embedded archive
|
||||
statically. Decompile extracted `.pyc` in an isolated environment.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- The frozen executable (read inertly).
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Read bytes statically; treat extracted modules as malicious until reviewed.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Detect the packer
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py detect sample.exe
|
||||
```
|
||||
|
||||
Looks for PyInstaller markers (`pyi-`, `PYZ-00.pyz`, the `MEI` CArchive cookie `MEI\014\013\012\013\016`),
|
||||
py2exe (`PYTHONSCRIPT`, `zipfile.zip`), and embedded `python3x.dll` references.
|
||||
|
||||
### Step 2: Locate the embedded archive
|
||||
|
||||
Find the CArchive cookie near the end of the file and report the offset and the embedded Python
|
||||
version string (`python3.x`).
|
||||
|
||||
### Step 3: Extract and decompile
|
||||
|
||||
Use a PyInstaller extractor to dump the archive, then decompile `.pyc` (matching the detected
|
||||
Python version) for source recovery.
|
||||
|
||||
### Step 4: Analyze the source
|
||||
|
||||
Review the recovered Python for C2, persistence, and capability; map to ATT&CK.
|
||||
|
||||
## Validation
|
||||
|
||||
- The packer is identified by its specific marker, not just the presence of Python strings.
|
||||
- The CArchive cookie offset and Python version are reported when PyInstaller is present.
|
||||
- Findings distinguish the bootloader stub from the embedded Python payload.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- `.pyc` version mismatch breaking decompilation — match the interpreter version.
|
||||
- Stripped/obfuscated bytecode (e.g., custom magic) needing header repair before decompiling.
|
||||
- Encrypted PYZ archives (PyInstaller `--key`) requiring the key.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the detector.
|
||||
- PyInstaller archive and Python bytecode references (linked in frontmatter).
|
||||
@@ -0,0 +1,37 @@
|
||||
# API Reference: Analyzing Compiled Python Malware
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` detects frozen-Python packers (PyInstaller, py2exe, cx_Freeze) by their markers,
|
||||
locates the PyInstaller CArchive magic cookie, and extracts embedded Python version strings. It
|
||||
reads bytes statically and executes nothing.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | regex, JSON, file IO |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `detect(path)`
|
||||
Returns the detected packer, PyInstaller cookie offset, and Python version strings, plus the
|
||||
recommended next step.
|
||||
|
||||
## Markers
|
||||
|
||||
- PyInstaller: `_MEIPASS`, `PYZ-00.pyz`, `pyiboot01_bootstrap`, CArchive cookie `MEI\\014\\013\\012\\013\\016`.
|
||||
- py2exe: `PYTHONSCRIPT`.
|
||||
- cx_Freeze: `cx_Freeze` strings.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py detect sample.exe
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- PyInstaller archive format (CArchive/PKG, MEI cookie) — https://pyinstaller.org/en/stable/advanced-topics.html
|
||||
- Python bytecode / .pyc magic (dis module) — https://docs.python.org/3/library/dis.html
|
||||
- pyinstxtractor extraction workflow (public PyInstaller-unpacking tooling)
|
||||
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Detect frozen-Python packers and locate the embedded archive.
|
||||
|
||||
Scans an executable for PyInstaller, py2exe, and cx_Freeze markers, locates the PyInstaller
|
||||
CArchive magic cookie, and extracts embedded Python version strings. Reads bytes statically;
|
||||
executes nothing.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
PYI_COOKIE = b"MEI\014\013\012\013\016"
|
||||
PYI_MARKERS = [b"pyi-windows-manifest-filename", b"PYZ-00.pyz", b"pyiboot01_bootstrap",
|
||||
b"_MEIPASS"]
|
||||
PY2EXE_MARKERS = [b"PYTHONSCRIPT", b"py2exe"]
|
||||
CXFREEZE_MARKERS = [b"cx_Freeze", b"cx_freeze__init__"]
|
||||
RE_PYVER = re.compile(rb"python(\d)\.?(\d{1,2})")
|
||||
RE_PYDLL = re.compile(rb"python(\d)(\d{1,2})\.dll", re.I)
|
||||
|
||||
|
||||
def detect(path: str) -> dict:
|
||||
data = Path(path).read_bytes()
|
||||
pyi = any(m in data for m in PYI_MARKERS) or PYI_COOKIE in data
|
||||
py2exe = any(m in data for m in PY2EXE_MARKERS)
|
||||
cxfreeze = any(m in data for m in CXFREEZE_MARKERS)
|
||||
cookie_off = data.rfind(PYI_COOKIE)
|
||||
versions = sorted({f"{m.group(1).decode()}.{m.group(2).decode()}"
|
||||
for m in RE_PYVER.finditer(data)})
|
||||
dll_versions = sorted({f"{m.group(1).decode()}.{m.group(2).decode()}"
|
||||
for m in RE_PYDLL.finditer(data)})
|
||||
packer = "PyInstaller" if pyi else "py2exe" if py2exe else \
|
||||
"cx_Freeze" if cxfreeze else "unknown"
|
||||
return {
|
||||
"is_frozen_python": pyi or py2exe or cxfreeze,
|
||||
"packer": packer,
|
||||
"pyinstaller_cookie_offset": cookie_off if cookie_off != -1 else None,
|
||||
"python_versions": versions[:5],
|
||||
"python_dll_versions": dll_versions[:5],
|
||||
"next_step": "extract the archive (e.g. pyinstxtractor) then decompile matching .pyc",
|
||||
}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Detect frozen-Python packers")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("detect")
|
||||
p.add_argument("file")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(detect(args.file), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -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,88 @@
|
||||
---
|
||||
name: analyzing-cryptominer-malware
|
||||
description: 'Analyzes cryptojacking/coinminer malware by extracting mining pool endpoints
|
||||
(stratum), wallet addresses, algorithm and miner identifiers (XMRig and forks), and persistence
|
||||
or resource-control settings from static strings and config. Activates for requests to analyze a
|
||||
cryptominer, extract mining pool and wallet IOCs, or identify coinminer configuration.'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware-analysis
|
||||
- cryptominer
|
||||
- cryptojacking
|
||||
- xmrig
|
||||
- resource-hijacking
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1496
|
||||
- T1543
|
||||
- T1027
|
||||
d3fend:
|
||||
- D3-PSA
|
||||
- D3-DA
|
||||
references:
|
||||
- 'MITRE ATT&CK T1496 Resource Hijacking — https://attack.mitre.org/techniques/T1496/'
|
||||
- 'XMRig configuration documentation — https://xmrig.com/docs/miner/config'
|
||||
---
|
||||
|
||||
# Analyzing Cryptominer Malware
|
||||
|
||||
## When to Use
|
||||
|
||||
- You have a suspected coinminer (often an XMRig fork) and need to extract its pool, wallet,
|
||||
algorithm, and resource-control configuration for IOCs and attribution.
|
||||
- You are confirming a host's high CPU/GPU usage is malicious mining.
|
||||
|
||||
**Do not use** this to run the miner — extract its configuration statically.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- The miner sample or its extracted config/strings, read inertly.
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Read bytes statically; defang pool endpoints; never start the miner.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Extract mining configuration
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py profile sample.bin
|
||||
```
|
||||
|
||||
Extracts `stratum+tcp://`/`stratum+ssl://` pool URLs, wallet addresses (Monero and common
|
||||
formats), algorithm names (`rx/0`, `cn/r`, etc.), and the XMRig/miner identifier strings.
|
||||
|
||||
### Step 2: Identify resource and stealth controls
|
||||
|
||||
Surface `max-cpu-usage`, `--background`, idle-detection, and `donate-level` settings, plus
|
||||
watchdog/persistence indicators.
|
||||
|
||||
### Step 3: Build IOCs
|
||||
|
||||
Record defanged pool endpoints, wallet addresses, and the miner family for detection and takedown
|
||||
referral.
|
||||
|
||||
### Step 4: Document
|
||||
|
||||
Summarize the campaign's pool/wallet and the resource-control posture.
|
||||
|
||||
## Validation
|
||||
|
||||
- Pool URLs use a recognized `stratum` scheme and resolve to plausible hosts.
|
||||
- Wallet addresses match a known coin's format/length.
|
||||
- The miner family is identified from its config keys/strings.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Legitimate mining software shares the same config keys — context (deployment, stealth) matters.
|
||||
- Encrypted/packed config requiring decryption first.
|
||||
- Proxy pools masking the true upstream pool.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the profiler.
|
||||
- ATT&CK T1496 and the XMRig config reference (linked in frontmatter).
|
||||
@@ -0,0 +1,39 @@
|
||||
# API Reference: Analyzing Cryptominer Malware
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` extracts a coinminer's configuration from static strings: stratum pool
|
||||
endpoints, wallet addresses (Monero/Bitcoin/Ethereum formats), algorithm identifiers, miner
|
||||
family strings, and resource-control/stealth keys. It reads bytes statically, defangs pool
|
||||
endpoints, and executes nothing.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | regex, JSON, file IO |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `profile(path)`
|
||||
Returns pool endpoints, wallet addresses (by coin), algorithms, miner family, resource controls,
|
||||
and a cryptominer likelihood flag.
|
||||
|
||||
## Extracted indicators
|
||||
|
||||
- **Pools**: `stratum+tcp://` / `stratum+ssl://`.
|
||||
- **Wallets**: Monero (`4…`, 95 chars), Bitcoin (legacy + bech32), Ethereum (`0x…40 hex`).
|
||||
- **Algorithms**: `rx/0`, `cn/r`, `randomx`, `kawpow`, `ethash`, etc.
|
||||
- **Resource controls**: `max-cpu-usage`, `donate-level`, `background`, `idle`.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py profile sample.bin
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- MITRE ATT&CK T1496 Resource Hijacking — https://attack.mitre.org/techniques/T1496/
|
||||
- XMRig configuration keys — https://xmrig.com/docs/miner/config
|
||||
- Coin address formats from each coin's public address specification
|
||||
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Profile a cryptominer's configuration from static strings.
|
||||
|
||||
Extracts stratum pool endpoints, wallet addresses, algorithm identifiers, miner family strings,
|
||||
and resource-control/stealth settings. Reads bytes statically, defangs pool endpoints, and
|
||||
executes nothing.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ASCII_RE = re.compile(rb"[\x20-\x7e]{4,}")
|
||||
RE_STRATUM = re.compile(r"stratum\+(?:tcp|ssl)://[^\s\"'<>]+", re.I)
|
||||
RE_MONERO = re.compile(r"\b4[0-9AB][0-9A-Za-z]{93}\b")
|
||||
RE_BTC = re.compile(r"\b(?:bc1[0-9a-z]{20,60}|[13][a-km-zA-HJ-NP-Z1-9]{25,34})\b")
|
||||
RE_ETH = re.compile(r"\b0x[a-fA-F0-9]{40}\b")
|
||||
ALGOS = ["rx/0", "rx/wow", "cn/r", "cn/2", "cn-heavy", "argon2", "kawpow", "ethash", "randomx",
|
||||
"cryptonight"]
|
||||
MINER_IDS = ["xmrig", "xmr-stak", "minerd", "cpuminer", "nbminer", "phoenixminer", "t-rex",
|
||||
"lolminer", "nanominer"]
|
||||
RESOURCE_KEYS = ["max-cpu-usage", "cpu-priority", "donate-level", "background", "max-threads-hint",
|
||||
"huge-pages", "idle", "watchdog"]
|
||||
|
||||
|
||||
def _strings(data: bytes) -> str:
|
||||
return "\n".join(m.decode("latin-1", "ignore") for m in ASCII_RE.findall(data))
|
||||
|
||||
|
||||
def _defang(s: str) -> str:
|
||||
return s.replace("stratum", "stratum-x").replace("://", "[://]").replace(".", "[.]")
|
||||
|
||||
|
||||
def profile(path: str) -> dict:
|
||||
blob = _strings(Path(path).read_bytes())
|
||||
low = blob.lower()
|
||||
pools = sorted({_defang(p) for p in RE_STRATUM.findall(blob)})
|
||||
wallets = {
|
||||
"monero": sorted(set(RE_MONERO.findall(blob)))[:10],
|
||||
"bitcoin": sorted(set(RE_BTC.findall(blob)))[:10],
|
||||
"ethereum": sorted(set(RE_ETH.findall(blob)))[:10],
|
||||
}
|
||||
wallets = {k: v for k, v in wallets.items() if v}
|
||||
algos = sorted({a for a in ALGOS if a in low})
|
||||
miners = sorted({m for m in MINER_IDS if m in low})
|
||||
resource = sorted({r for r in RESOURCE_KEYS if r in low})
|
||||
return {
|
||||
"pool_endpoints": pools,
|
||||
"wallet_addresses": wallets,
|
||||
"algorithms": algos,
|
||||
"miner_family": miners,
|
||||
"resource_controls": resource,
|
||||
"is_likely_cryptominer": bool(pools or (miners and algos)),
|
||||
}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Profile a cryptominer")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("profile")
|
||||
p.add_argument("file")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(profile(args.file), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -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,96 @@
|
||||
---
|
||||
name: analyzing-dotnet-malware-internals
|
||||
description: 'Reverses .NET/managed malware: decompiling MSIL back to C#, defeating common
|
||||
.NET protectors and string encryptors, and tracing reflection-based loaders to recover the
|
||||
real payload. Activates for requests to analyze a .NET sample, decompile MSIL, or unpack a
|
||||
managed loader.'
|
||||
domain: cybersecurity
|
||||
subdomain: reverse-engineering
|
||||
tags:
|
||||
- reverse-engineering
|
||||
- dotnet
|
||||
- msil
|
||||
- decompilation
|
||||
- deobfuscation
|
||||
- managed-code
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1027
|
||||
- T1620
|
||||
- T1055
|
||||
d3fend:
|
||||
- D3-FCR
|
||||
- D3-DA
|
||||
references:
|
||||
- 'ECMA-335 Common Language Infrastructure (CIL/MSIL) — https://ecma-international.org/publications-and-standards/standards/ecma-335/'
|
||||
- 'ILSpy decompiler — https://github.com/icsharpcode/ILSpy'
|
||||
---
|
||||
|
||||
# Analyzing .NET Malware Internals
|
||||
|
||||
## When to Use
|
||||
|
||||
- A sample is a managed (.NET) assembly — confirmed by a CLR header / `mscoree` import or
|
||||
`BSJB` metadata signature.
|
||||
- You need readable C# from MSIL and want to defeat .NET-specific obfuscation.
|
||||
- A loader uses reflection (`Assembly.Load`) to run an in-memory payload you must recover.
|
||||
|
||||
**Do not use** native disassembly workflows (Ghidra for x86) as the primary tool — managed
|
||||
code decompiles far more cleanly with a .NET decompiler.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- ILSpy / dnSpyEx for decompilation and (with dnSpyEx) managed debugging.
|
||||
- de4dot or equivalent for known protectors; familiarity with common .NET obfuscators.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Confirm it is managed
|
||||
|
||||
Check for the CLR runtime header and the `BSJB` metadata magic:
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py identify sample.exe
|
||||
```
|
||||
|
||||
### Step 2: Decompile
|
||||
|
||||
Open in ILSpy/dnSpyEx and review the entry point, `Main`, and module initializer
|
||||
(`<Module>.cctor`), which protectors often abuse.
|
||||
|
||||
### Step 3: Handle obfuscation
|
||||
|
||||
Recognize and undo common schemes:
|
||||
|
||||
- **String encryption** — a decryptor method called everywhere; run/trace it to recover
|
||||
plaintext (de4dot can often static-decrypt).
|
||||
- **Control-flow flattening** — follow the dispatcher state machine.
|
||||
- **Proxy methods / renaming** — rely on decompiler analysis rather than names.
|
||||
|
||||
### Step 4: Trace reflection loaders
|
||||
|
||||
Find `Assembly.Load(byte[])` / `Activator.CreateInstance`; dump the byte array argument at
|
||||
runtime (managed debugger breakpoint) to recover the real second-stage assembly, then recurse.
|
||||
|
||||
### Step 5: Analyze the payload
|
||||
|
||||
Decompile the recovered stage; extract C2, configuration, and capabilities for the report.
|
||||
|
||||
## Validation
|
||||
|
||||
- Decompiled C# is coherent (named or recovered) and the entry path is traced.
|
||||
- Encrypted strings are recovered to plaintext.
|
||||
- The reflection-loaded stage is dumped and itself decompiles.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Treating the loader as the payload — managed malware is frequently multi-stage.
|
||||
- Ignoring the module initializer where protectors install hooks.
|
||||
- Static-decrypting strings when the scheme is runtime-keyed; debug and dump instead.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the CLR identifier.
|
||||
- ECMA-335 and ILSpy (linked in frontmatter).
|
||||
@@ -0,0 +1,39 @@
|
||||
# API Reference: Reverse Engineering .NET Malware
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` parses a PE to detect the CLR runtime header (COM descriptor data directory
|
||||
index 14) and the `BSJB` metadata-root signature, confirming whether a sample is managed. It
|
||||
parses bytes only and never executes the sample.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | struct parsing, file IO |
|
||||
|
||||
## External tools
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| ILSpy / dnSpyEx | Decompile MSIL to C#; managed debugging |
|
||||
| de4dot | Static deobfuscation of known protectors |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `identify(path)`
|
||||
Returns whether the sample is managed, whether the `BSJB` metadata signature is present, and
|
||||
diagnostic notes (mscoree/_CorExeMain references).
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py identify sample.exe
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- ECMA-335 Common Language Infrastructure (CIL/MSIL metadata, BSJB root) —
|
||||
https://ecma-international.org/publications-and-standards/standards/ecma-335/
|
||||
- PE COM descriptor data directory (index 14) — PE/COFF specification
|
||||
- ILSpy decompiler — https://github.com/icsharpcode/ILSpy
|
||||
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Identify a .NET/managed assembly and surface basic CLR metadata signals.
|
||||
|
||||
Parses the PE far enough to detect the COM descriptor (CLR) data directory and searches for
|
||||
the ``BSJB`` metadata-root signature. Static parsing only; the sample is never executed.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import struct
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def identify(path: str) -> dict:
|
||||
data = Path(path).read_bytes()
|
||||
result = {"managed": False, "bsjb_metadata": False, "notes": []}
|
||||
|
||||
if data[:2] != b"MZ":
|
||||
result["notes"].append("not a PE (no MZ)")
|
||||
return result
|
||||
|
||||
e_lfanew = struct.unpack_from("<I", data, 0x3C)[0]
|
||||
if data[e_lfanew:e_lfanew + 4] != b"PE\x00\x00":
|
||||
result["notes"].append("no PE signature")
|
||||
return result
|
||||
|
||||
coff = e_lfanew + 4
|
||||
opt = coff + 20
|
||||
magic = struct.unpack_from("<H", data, opt)[0]
|
||||
pe32_plus = magic == 0x20B
|
||||
# data directories start after the standard+windows optional header fields
|
||||
dd_offset = opt + (112 if pe32_plus else 96)
|
||||
# COM descriptor is data directory index 14
|
||||
com_dir = dd_offset + 14 * 8
|
||||
rva, size = struct.unpack_from("<II", data, com_dir)
|
||||
if rva and size:
|
||||
result["managed"] = True
|
||||
result["notes"].append("CLR runtime header (COM descriptor) present")
|
||||
|
||||
if b"BSJB" in data:
|
||||
result["bsjb_metadata"] = True
|
||||
result["notes"].append("BSJB metadata root signature found")
|
||||
|
||||
if b"mscoree.dll" in data or b"_CorExeMain" in data:
|
||||
result["notes"].append("mscoree / _CorExeMain reference")
|
||||
|
||||
result["managed"] = result["managed"] or result["bsjb_metadata"]
|
||||
return result
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Identify managed .NET assembly")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("identify")
|
||||
p.add_argument("file")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(identify(args.file), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -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,99 @@
|
||||
---
|
||||
name: analyzing-elf-binaries-on-linux
|
||||
description: 'Statically analyzes Linux ELF malware: ELF header and sections, dynamic symbols
|
||||
and imports, segment permissions, embedded strings, and packing indicators to infer
|
||||
capability without execution. Activates for requests to analyze an ELF binary, Linux
|
||||
malware, or shared object.'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware
|
||||
- static-analysis
|
||||
- elf
|
||||
- linux
|
||||
- symbols
|
||||
- entropy
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1027
|
||||
- T1059.004
|
||||
- T1129
|
||||
d3fend:
|
||||
- D3-FA
|
||||
- D3-FCR
|
||||
references:
|
||||
- 'ELF specification (System V ABI) — https://refspecs.linuxfoundation.org/elf/elf.pdf'
|
||||
- 'pyelftools — https://github.com/eliben/pyelftools'
|
||||
---
|
||||
|
||||
# Analyzing ELF Binaries on Linux
|
||||
|
||||
## When to Use
|
||||
|
||||
- You have a Linux executable or shared object and need to assess it statically.
|
||||
- You want to read the ELF header, segments, dynamic symbols, and strings to infer behavior.
|
||||
- You are checking for static linking, stripped symbols, or packing.
|
||||
|
||||
**Do not use** symbol absence as proof of nothing — stripped or statically linked Go/Rust
|
||||
binaries hide structure; switch to disassembly when static metadata is thin.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- `readelf`/`nm`/`strings` (binutils) or `pyelftools` (`pip install pyelftools`).
|
||||
- The sample in neutralized form inside the lab.
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Static only: parse the file; do not set it executable or run it.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Read the ELF header
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py analyze sample.elf
|
||||
```
|
||||
|
||||
Note class (ELF32/64), endianness, type (EXEC/DYN/REL), machine (x86-64, ARM, MIPS), and
|
||||
whether it is stripped.
|
||||
|
||||
### Step 2: Examine segments and section permissions
|
||||
|
||||
Writable+executable segments, or a single large segment, suggest packing or self-modifying
|
||||
code. Compare `PT_LOAD` permissions against expectations.
|
||||
|
||||
### Step 3: Inspect dynamic symbols and needed libraries
|
||||
|
||||
Imported functions hint at capability: `socket`/`connect` (network), `ptrace` (anti-debug or
|
||||
injection), `fork`/`execve` (process control), `crypt`/`EVP_*` (encryption).
|
||||
|
||||
### Step 4: Detect static linking and packing
|
||||
|
||||
Static binaries lack a dynamic symbol table and `NEEDED` entries. High whole-file entropy and
|
||||
a tiny section table suggest a packer (e.g. UPX leaves `UPX!` markers).
|
||||
|
||||
### Step 5: Skim strings and constructors
|
||||
|
||||
Check `.init_array`/constructors (code before `main`), and strings for paths, URLs, and shell
|
||||
commands.
|
||||
|
||||
## Validation
|
||||
|
||||
- Header `type` and segment permissions are consistent with the inferred behavior.
|
||||
- Imported symbols correspond to plausible capabilities.
|
||||
- Packing call (entropy + missing sections) matches the strings observed.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Assuming dynamic symbols are complete — they only cover imported/exported names, not
|
||||
internal functions.
|
||||
- Treating a stripped Go binary as "empty"; its structure lives in runtime metadata, not the
|
||||
symbol table.
|
||||
- Ignoring constructors/`.init_array`, which run before `main`.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the ELF analyzer.
|
||||
- ELF/System V ABI spec and pyelftools (linked in frontmatter).
|
||||
@@ -0,0 +1,43 @@
|
||||
# API Reference: Analyzing ELF Binaries on Linux
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` reports ELF header facts, dynamic symbols and `NEEDED` libraries, a strip
|
||||
check, and capability groupings (network/process/crypto). It uses `pyelftools` when present
|
||||
and falls back to a minimal header/entropy parse otherwise.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| pyelftools | >= 0.30 | Full ELF parsing (optional) |
|
||||
| (stdlib) | — | Header/entropy fallback |
|
||||
|
||||
## External tools
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| readelf / nm (binutils) | Authoritative ELF inspection |
|
||||
| strings | Embedded string review |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `analyze(path)`
|
||||
Dispatches to the pyelftools or minimal parser; returns header, symbols, needed libs, and
|
||||
capability groupings.
|
||||
|
||||
### `entropy(data)`
|
||||
Byte-level Shannon entropy for packing assessment.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
pip install pyelftools # optional
|
||||
python scripts/analyst.py analyze sample.elf
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- ELF / System V ABI specification — https://refspecs.linuxfoundation.org/elf/elf.pdf
|
||||
- pyelftools — https://github.com/eliben/pyelftools
|
||||
- UPX section/marker conventions (vendor docs)
|
||||
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Static ELF analysis: header, sections, dynamic symbols, and packing hints.
|
||||
|
||||
Uses ``pyelftools`` when available; otherwise parses the ELF header and section table
|
||||
directly so basic facts (class, type, machine, sections, entropy) are still reported.
|
||||
Never executes the sample.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
import struct
|
||||
import sys
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
_NETWORK = {"socket", "connect", "send", "recv", "bind", "listen", "gethostbyname"}
|
||||
_PROC = {"fork", "execve", "ptrace", "clone", "kill", "system", "popen"}
|
||||
_CRYPTO = {"crypt", "evp_encryptinit_ex", "aes_encrypt", "rc4"}
|
||||
|
||||
|
||||
def entropy(data: bytes) -> float:
|
||||
if not data:
|
||||
return 0.0
|
||||
counts = Counter(data)
|
||||
n = len(data)
|
||||
return -sum((c / n) * math.log2(c / n) for c in counts.values())
|
||||
|
||||
|
||||
def analyze_with_pyelftools(path: str) -> dict:
|
||||
from elftools.elf.elffile import ELFFile # type: ignore
|
||||
from elftools.elf.dynamic import DynamicSection # type: ignore
|
||||
with open(path, "rb") as fh:
|
||||
elf = ELFFile(fh)
|
||||
header = {
|
||||
"class": elf.elfclass,
|
||||
"type": elf["e_type"],
|
||||
"machine": elf["e_machine"],
|
||||
"entry": hex(elf["e_entry"]),
|
||||
}
|
||||
symbols: list[str] = []
|
||||
needed: list[str] = []
|
||||
dynsym = elf.get_section_by_name(".dynsym")
|
||||
if dynsym:
|
||||
symbols = [s.name for s in dynsym.iter_symbols() if s.name]
|
||||
for sec in elf.iter_sections():
|
||||
if isinstance(sec, DynamicSection):
|
||||
for tag in sec.iter_tags():
|
||||
if tag.entry.d_tag == "DT_NEEDED":
|
||||
needed.append(tag.needed)
|
||||
stripped = elf.get_section_by_name(".symtab") is None
|
||||
low = {s.lower() for s in symbols}
|
||||
return {
|
||||
"header": header,
|
||||
"stripped": stripped,
|
||||
"needed": needed,
|
||||
"dynamic_symbol_count": len(symbols),
|
||||
"capabilities": {
|
||||
"network": sorted(low & _NETWORK),
|
||||
"process": sorted(low & _PROC),
|
||||
"crypto": sorted(low & _CRYPTO),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def analyze_minimal(path: str) -> dict:
|
||||
data = Path(path).read_bytes()
|
||||
if data[:4] != b"\x7fELF":
|
||||
raise ValueError("not an ELF")
|
||||
is64 = data[4] == 2
|
||||
le = data[5] == 1
|
||||
endian = "<" if le else ">"
|
||||
e_type = struct.unpack_from(endian + "H", data, 16)[0]
|
||||
e_machine = struct.unpack_from(endian + "H", data, 18)[0]
|
||||
upx = b"UPX!" in data
|
||||
return {
|
||||
"header": {"class": 64 if is64 else 32, "type": e_type, "machine": e_machine},
|
||||
"whole_file_entropy": round(entropy(data), 3),
|
||||
"upx_marker": upx,
|
||||
"note": "pyelftools not installed; minimal parse",
|
||||
}
|
||||
|
||||
|
||||
def analyze(path: str) -> dict:
|
||||
try:
|
||||
return analyze_with_pyelftools(path)
|
||||
except ImportError:
|
||||
return analyze_minimal(path)
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Static ELF analysis")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("analyze")
|
||||
p.add_argument("file")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(analyze(args.file), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -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,89 @@
|
||||
---
|
||||
name: analyzing-excel-4-macro-malware
|
||||
description: 'Analyzes legacy Excel 4.0 (XLM) macro malware by parsing extracted macro-sheet
|
||||
formulas for auto-executing names, obfuscation (FORMULA.FILL, CHAR concatenation), and download
|
||||
or execution primitives (EXEC, CALL, REGISTER). Activates for requests to analyze XLM macros,
|
||||
examine Excel 4.0 macro sheets, or deobfuscate legacy spreadsheet macros.'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware-analysis
|
||||
- xlm
|
||||
- excel4
|
||||
- macros
|
||||
- document-malware
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1059.005
|
||||
- T1204.002
|
||||
- T1027
|
||||
d3fend:
|
||||
- D3-FCR
|
||||
- D3-DA
|
||||
references:
|
||||
- 'MITRE ATT&CK T1059.005 Visual Basic / macros — https://attack.mitre.org/techniques/T1059/005/'
|
||||
- 'Microsoft Excel 4.0 macro functions reference — https://learn.microsoft.com/office/'
|
||||
---
|
||||
|
||||
# Analyzing Excel 4.0 Macro Malware
|
||||
|
||||
## When to Use
|
||||
|
||||
- You have an extracted Excel 4.0 (XLM) macro sheet (e.g., from a `.xls`/`.xlsm` dumped with
|
||||
oletools/XLMMacroDeobfuscator) and need to find auto-run cells and execution primitives.
|
||||
- You are triaging maldocs that hide logic in legacy macro sheets rather than VBA.
|
||||
|
||||
**Do not use** Excel to open the document to "see" the macro — opening triggers the auto-run
|
||||
cells. Work from the extracted formula text statically.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- The extracted XLM formula text (cell address → formula). Optional: `oletools` to extract it.
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Never open the workbook in Excel; analyze the extracted formulas inertly. Defang URLs.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Find auto-executing entry points
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py analyze macros.txt
|
||||
```
|
||||
|
||||
Flags defined-name triggers (`Auto_Open`, `Auto_Close`) and the cells they point to, plus
|
||||
`=HALT()`/`=RETURN()` flow markers.
|
||||
|
||||
### Step 2: Identify execution and download primitives
|
||||
|
||||
Detects `EXEC(`, `CALL(`, `REGISTER(` (Win32 imports), and URLDownload-style `CALL` patterns that
|
||||
fetch and run a payload.
|
||||
|
||||
### Step 3: Unwind obfuscation
|
||||
|
||||
Surfaces `CHAR()`/`&` string-building, `FORMULA.FILL`/`FORMULA` self-writing, and base/`MID`
|
||||
slicing used to hide strings; reconstructs concatenated literals where possible.
|
||||
|
||||
### Step 4: Extract IOCs
|
||||
|
||||
Pull URLs/paths and defang them; record the execution method (regsvr32, rundll32, mshta).
|
||||
|
||||
## Validation
|
||||
|
||||
- Auto-run entry cells are identified, not just the presence of macros.
|
||||
- Execution primitives (EXEC/CALL/REGISTER) are reported with their arguments.
|
||||
- Reconstructed strings and URLs are defanged.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Heavily obfuscated sheets that self-write cells at runtime — static reconstruction is partial.
|
||||
- Macro sheets hidden as `Very Hidden` that simple viewers miss.
|
||||
- Confusing benign legacy spreadsheets that legitimately use XLM.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the analyzer.
|
||||
- ATT&CK T1059.005 and the Excel 4.0 macro reference (linked in frontmatter).
|
||||
@@ -0,0 +1,41 @@
|
||||
# API Reference: Analyzing Excel 4.0 Macro Malware
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` parses an extracted XLM macro-sheet formula dump and flags auto-run triggers,
|
||||
execution/download primitives, executors, and obfuscation, reconstructing `CHAR()` string chains
|
||||
and defanging URLs. It parses text only, never opens the workbook, and executes nothing.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | regex, JSON, file IO |
|
||||
| oletools | optional | extract the formulas upstream (not required by this script) |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `analyze(path)`
|
||||
Returns auto-run triggers, execution primitives, download indicators, executors, obfuscation
|
||||
techniques, defanged URLs, reconstructed `CHAR()` strings, and a maliciousness flag.
|
||||
|
||||
### `_reconstruct_char(line)`
|
||||
Rebuilds a string from a `CHAR(n)&CHAR(n)…` concatenation chain.
|
||||
|
||||
## Indicators
|
||||
|
||||
- Auto-run: `Auto_Open`, `Auto_Close`, `Auto_Activate`.
|
||||
- Execution: `EXEC(`, `CALL(`, `REGISTER(` (Win32 import declaration).
|
||||
- Download: `URLDownloadToFile`, `MSXML2`, `WinHTTP`.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py analyze macros.txt
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- MITRE ATT&CK T1059.005 — https://attack.mitre.org/techniques/T1059/005/
|
||||
- Excel 4.0 macro function semantics (EXEC/CALL/REGISTER/FORMULA.FILL) from Microsoft Office docs
|
||||
- XLM obfuscation patterns from public maldoc analysis (XLMMacroDeobfuscator methodology)
|
||||
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Analyze extracted Excel 4.0 (XLM) macro formulas for malicious behavior.
|
||||
|
||||
Reads a text dump of macro-sheet formulas and flags auto-run entry points, execution/download
|
||||
primitives (EXEC/CALL/REGISTER), and obfuscation (CHAR concatenation, FORMULA.FILL self-writing).
|
||||
Parses text only; executes nothing and never opens the workbook. Defangs recovered URLs.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
AUTO_NAMES = ["auto_open", "auto_close", "auto_activate", "auto_deactivate"]
|
||||
EXEC_PRIMS = ["exec(", "call(", "register(", "fopen(", "fwrite("]
|
||||
DOWNLOAD_HINTS = ["urlmon", "urldownloadtofile", "http", "msxml2", "winhttp", "shellexecute"]
|
||||
EXECUTORS = ["regsvr32", "rundll32", "mshta", "powershell", "cmd.exe", "wscript", "cscript"]
|
||||
OBFUSCATION = ["char(", "formula.fill", "formula(", "mid(", "&", "concatenate("]
|
||||
RE_URL = re.compile(r"https?://[^\s\"'<>)]+", re.I)
|
||||
RE_CHAR = re.compile(r"CHAR\((\d{1,3})\)", re.I)
|
||||
|
||||
|
||||
def _defang(s: str) -> str:
|
||||
return s.replace("http", "hxxp").replace(".", "[.]")
|
||||
|
||||
|
||||
def _reconstruct_char(line: str) -> str:
|
||||
chars = RE_CHAR.findall(line)
|
||||
if len(chars) < 3:
|
||||
return ""
|
||||
try:
|
||||
return "".join(chr(int(c)) for c in chars if 0 <= int(c) < 256)
|
||||
except ValueError:
|
||||
return ""
|
||||
|
||||
|
||||
def analyze(path: str) -> dict:
|
||||
text = Path(path).read_text(encoding="utf-8-sig", errors="replace")
|
||||
low = text.lower()
|
||||
auto = sorted({n for n in AUTO_NAMES if n in low})
|
||||
exec_prims = sorted({p.rstrip("(") for p in EXEC_PRIMS if p in low})
|
||||
downloads = sorted({d for d in DOWNLOAD_HINTS if d in low})
|
||||
executors = sorted({e for e in EXECUTORS if e in low})
|
||||
obf = sorted({o.rstrip("(") for o in OBFUSCATION if o in low})
|
||||
urls = sorted({_defang(u) for u in RE_URL.findall(text)})
|
||||
reconstructed = sorted({r for line in text.splitlines()
|
||||
if (r := _reconstruct_char(line))})[:25]
|
||||
return {
|
||||
"auto_run_triggers": auto,
|
||||
"execution_primitives": exec_prims,
|
||||
"download_indicators": downloads,
|
||||
"executors": executors,
|
||||
"obfuscation_techniques": obf,
|
||||
"urls": urls[:50],
|
||||
"reconstructed_char_strings": reconstructed,
|
||||
"is_likely_malicious_xlm": bool(auto and (exec_prims or downloads)),
|
||||
}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Analyze Excel 4.0 (XLM) macros")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("analyze")
|
||||
p.add_argument("file")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(analyze(args.file), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -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,88 @@
|
||||
---
|
||||
name: analyzing-golang-malware-internals
|
||||
description: 'Analyzes Go-compiled malware by recovering function names from the pclntab, detecting
|
||||
the Go build/version string, and listing embedded package paths to overcome stripped symbols.
|
||||
Activates for requests to analyze Go malware, recover Go function names, or identify a Golang
|
||||
binary build.'
|
||||
domain: cybersecurity
|
||||
subdomain: reverse-engineering
|
||||
tags:
|
||||
- reverse-engineering
|
||||
- golang
|
||||
- symbol-recovery
|
||||
- pclntab
|
||||
- stripped-binaries
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1027
|
||||
- T1587.001
|
||||
- T1059
|
||||
d3fend:
|
||||
- D3-SDA
|
||||
- D3-DA
|
||||
references:
|
||||
- 'Go runtime pclntab format (Go source: runtime/symtab.go) — https://go.dev/src/runtime/symtab.go'
|
||||
- 'Go binary build info (debug/buildinfo) — https://pkg.go.dev/debug/buildinfo'
|
||||
---
|
||||
|
||||
# Analyzing Golang Malware Internals
|
||||
|
||||
## When to Use
|
||||
|
||||
- You have a Go-compiled binary (often large, statically linked) and standard symbol recovery
|
||||
fails because user symbols are stripped.
|
||||
- You need function names from the pclntab, the Go version, and embedded package/module paths.
|
||||
|
||||
**Do not use** this for non-Go binaries — confirm the Go build signature first. This skill reads
|
||||
the binary statically and executes nothing.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- The Go binary (read inertly).
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Read bytes statically; treat strings as untrusted data.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Confirm it is Go and get the version
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py info sample.bin
|
||||
```
|
||||
|
||||
Looks for the `Go build ID`, the `go1.x` version string, and `runtime.` references.
|
||||
|
||||
### Step 2: Recover function names from pclntab
|
||||
|
||||
The pclntab (preceded by a magic like `\xfb\xff\xff\xff`/`\xf0\xff\xff\xff` across Go versions)
|
||||
contains function name strings; extract `main.*`, package-qualified, and `runtime.*` names.
|
||||
|
||||
### Step 3: Enumerate package paths
|
||||
|
||||
List embedded import/module paths (`github.com/...`, vendored deps) to fingerprint capabilities
|
||||
and third-party libraries.
|
||||
|
||||
### Step 4: Map and document
|
||||
|
||||
Map suspicious packages (networking, crypto, exec) to behavior and ATT&CK.
|
||||
|
||||
## Validation
|
||||
|
||||
- The Go build signature/version is confirmed before deeper parsing.
|
||||
- Recovered names include plausible `main.*`/package-qualified functions.
|
||||
- Package paths are real module paths, not random strings.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- pclntab magic differs across Go versions; try the known set.
|
||||
- Obfuscators (garble) rename symbols and strip build info — names may be hashed.
|
||||
- Confusing vendored library code with the author's `main` package.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the analyzer.
|
||||
- Go pclntab/buildinfo references (linked in frontmatter).
|
||||
@@ -0,0 +1,38 @@
|
||||
# API Reference: Analyzing Golang Malware Internals
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` confirms a Go build (build ID / `go1.x` / `runtime.main`), detects pclntab
|
||||
magic markers, and recovers Go function names and package import paths from printable strings,
|
||||
mapping notable packages to capabilities. It reads bytes statically and executes nothing. This is
|
||||
a heuristic scan, not a full pclntab structure parser.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | regex, JSON, file IO |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `info(path)`
|
||||
Returns Go detection, versions, pclntab magics present, recovered functions, package paths, and
|
||||
capabilities.
|
||||
|
||||
## Heuristics
|
||||
|
||||
- Go detection: `Go build ID`, `go1.x`, `runtime.main`.
|
||||
- pclntab magics: `\xfb\xff\xff\xff`, `\xfa…`, `\xf0…`, `\xf1…` (across Go versions).
|
||||
- Functions: `main.*`, `runtime.*`, `net.*`, `crypto.*`, `os/exec.*`, `syscall.*`.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py info sample.bin
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- Go runtime pclntab format (runtime/symtab.go) — https://go.dev/src/runtime/symtab.go
|
||||
- Go build info (debug/buildinfo) — https://pkg.go.dev/debug/buildinfo
|
||||
- Go RE techniques (pclntab name recovery, garble obfuscation) from public Go-malware research
|
||||
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Analyze a Go-compiled binary: confirm Go build, recover names, list package paths.
|
||||
|
||||
Detects the Go build signature/version, scans printable strings for Go function/package names
|
||||
(main.*, runtime.*, github.com/... import paths), and reports likely capabilities. Reads bytes
|
||||
statically; executes nothing. This is a heuristic string/pclntab-marker scan, not a full pclntab
|
||||
parser.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ASCII_RE = re.compile(rb"[\x20-\x7e]{3,}")
|
||||
RE_GOVER = re.compile(rb"go1\.\d+(?:\.\d+)?")
|
||||
PCLNTAB_MAGICS = [b"\xfb\xff\xff\xff", b"\xfa\xff\xff\xff", b"\xf0\xff\xff\xff",
|
||||
b"\xf1\xff\xff\xff"]
|
||||
RE_FUNC = re.compile(r"^(?:main|runtime|net|crypto|os/exec|syscall)[./][\w./*()]+")
|
||||
RE_PKG = re.compile(r"(?:github\.com|gitlab\.com|golang\.org|gopkg\.in)/[\w./-]+")
|
||||
|
||||
CAP_PKGS = {
|
||||
"net/http": "HTTP networking", "net": "raw networking", "crypto/aes": "AES crypto",
|
||||
"os/exec": "process execution", "syscall": "direct syscalls",
|
||||
"golang.org/x/sys": "low-level OS access",
|
||||
}
|
||||
|
||||
|
||||
def info(path: str) -> dict:
|
||||
data = Path(path).read_bytes()
|
||||
strings = [m.decode("latin-1", "ignore") for m in ASCII_RE.findall(data)]
|
||||
blob = "\n".join(strings)
|
||||
is_go = b"Go build ID" in data or bool(RE_GOVER.search(data)) or b"runtime.main" in data
|
||||
versions = sorted({m.decode() for m in RE_GOVER.findall(data)})
|
||||
funcs = sorted({s for s in strings if RE_FUNC.match(s)})[:200]
|
||||
pkgs = sorted(set(RE_PKG.findall(blob)))[:100]
|
||||
caps = sorted({desc for pkg, desc in CAP_PKGS.items() if pkg in blob})
|
||||
pcln = [m.hex() for m in PCLNTAB_MAGICS if m in data]
|
||||
return {
|
||||
"is_go_binary": is_go,
|
||||
"go_versions": versions,
|
||||
"pclntab_magics_present": pcln,
|
||||
"recovered_functions": funcs,
|
||||
"package_paths": pkgs,
|
||||
"capabilities": caps,
|
||||
}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Analyze a Go binary")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("info")
|
||||
p.add_argument("file")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(info(args.file), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -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,88 @@
|
||||
---
|
||||
name: analyzing-infostealer-credential-theft
|
||||
description: 'Analyzes infostealer samples by mapping the browser, credential store, wallet, and
|
||||
application paths they target, and the exfiltration channel used, from static strings and
|
||||
behavior. Activates for requests to analyze an infostealer, identify targeted credential stores,
|
||||
or map stealer collection and exfiltration behavior.'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware-analysis
|
||||
- infostealer
|
||||
- credential-theft
|
||||
- exfiltration
|
||||
- triage
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1555.003
|
||||
- T1539
|
||||
- T1005
|
||||
d3fend:
|
||||
- D3-FAPA
|
||||
- D3-DA
|
||||
references:
|
||||
- 'MITRE ATT&CK T1555.003 Credentials from Web Browsers — https://attack.mitre.org/techniques/T1555/003/'
|
||||
- 'MITRE ATT&CK T1539 Steal Web Session Cookie — https://attack.mitre.org/techniques/T1539/'
|
||||
---
|
||||
|
||||
# Analyzing Infostealer Credential Theft
|
||||
|
||||
## When to Use
|
||||
|
||||
- You have an infostealer sample and need to enumerate what it targets: browser credential/cookie
|
||||
stores, crypto wallets, FTP/VPN/messaging app configs, and the exfil channel.
|
||||
- You are building detections from the file paths and endpoints a stealer references.
|
||||
|
||||
**Do not use** this to recover credentials yourself — it characterizes what the malware targets
|
||||
from inert static analysis.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- The sample (read inertly), optionally with extracted strings.
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Read bytes statically; defang exfil endpoints; never run the stealer.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Map targeted artifacts
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py profile sample.bin
|
||||
```
|
||||
|
||||
Matches references to known browser paths (`Login Data`, `Cookies`, `Web Data`), wallet
|
||||
directories, app config paths, and credential APIs, grouped by category.
|
||||
|
||||
### Step 2: Identify exfiltration channel
|
||||
|
||||
Detects HTTP(S) POST endpoints, Telegram bot tokens, Discord webhooks, and FTP/SMTP usage in
|
||||
strings.
|
||||
|
||||
### Step 3: Build the target/exfil profile
|
||||
|
||||
Summarize targeted stores and the exfil channel, mapping to ATT&CK.
|
||||
|
||||
### Step 4: Defang and report
|
||||
|
||||
Defang endpoints and produce IOCs for detection.
|
||||
|
||||
## Validation
|
||||
|
||||
- Targeted artifacts are grouped (browsers, wallets, apps, system credential stores).
|
||||
- The exfil channel is identified with a defanged endpoint where present.
|
||||
- Findings map to ATT&CK credential-access/collection/exfiltration techniques.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Generic browser paths can appear in benign tools — corroborate with theft behavior.
|
||||
- Missing wallet/app targets that use obfuscated path strings.
|
||||
- Reporting live exfil endpoints (webhooks, bot tokens) without defanging.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the profiler.
|
||||
- ATT&CK T1555.003 and T1539 (linked in frontmatter).
|
||||
@@ -0,0 +1,39 @@
|
||||
# API Reference: Analyzing Infostealer Credential Theft
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` extracts strings from a sample and maps them to targeted artifacts (browser
|
||||
credential stores, crypto wallets, application configs, credential APIs) and exfiltration channels
|
||||
(Telegram, Discord webhooks, HTTP POST, FTP, SMTP). It reads bytes statically, defangs endpoints,
|
||||
and executes nothing.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | regex, JSON, file IO |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `profile(path)`
|
||||
Returns targeted artifacts (grouped), exfiltration channels (defanged), and an infostealer
|
||||
likelihood flag (≥2 target categories).
|
||||
|
||||
## Target categories
|
||||
|
||||
- **browser_credentials**: Chromium `Login Data`/`Cookies`/`Local State`, Firefox `key4.db`/`logins.json`.
|
||||
- **crypto_wallets**: `wallet.dat`, Electrum/Exodus/MetaMask/Monero/Ledger.
|
||||
- **applications**: FileZilla, OpenVPN, Telegram `tdata`, Discord, WinSCP, AnyDesk.
|
||||
- **credential_api**: `CryptUnprotectData`, `CredEnumerate`, vault/LSA APIs.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py profile sample.bin
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- MITRE ATT&CK T1555.003 Credentials from Web Browsers — https://attack.mitre.org/techniques/T1555/003/
|
||||
- MITRE ATT&CK T1539 Steal Web Session Cookie — https://attack.mitre.org/techniques/T1539/
|
||||
- Browser/wallet artifact paths from public stealer family analyses
|
||||
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Profile an infostealer's targeted artifacts and exfiltration channel from static strings.
|
||||
|
||||
Extracts ASCII/UTF-16LE strings and matches them against curated lists of browser credential
|
||||
stores, crypto wallets, application configs, and credential APIs, plus exfil-channel indicators
|
||||
(HTTP POST, Telegram, Discord webhooks, FTP/SMTP). Reads bytes statically; executes nothing.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ASCII_RE = re.compile(rb"[\x20-\x7e]{4,}")
|
||||
UTF16_RE = re.compile(rb"(?:[\x20-\x7e]\x00){4,}")
|
||||
|
||||
TARGETS = {
|
||||
"browser_credentials": ["login data", "web data", "cookies", "logins.json", "key4.db",
|
||||
"signons.sqlite", "\\user data\\", "local state"],
|
||||
"crypto_wallets": ["wallet.dat", "\\electrum\\", "\\exodus\\", "\\ethereum\\keystore",
|
||||
"metamask", "\\monero\\", "ledger live"],
|
||||
"applications": ["filezilla\\recentservers.xml", "\\openvpn\\", "\\telegram desktop\\tdata",
|
||||
"\\discord\\local storage", "winscp.ini", "\\pidgin\\", "anydesk"],
|
||||
"credential_api": ["cryptunprotectdata", "vaultcli", "credenumerate", "lsadump"],
|
||||
}
|
||||
|
||||
EXFIL = {
|
||||
"telegram": [r"api\.telegram\.org", r"bot\d{6,}:[a-z0-9_-]{30,}"],
|
||||
"discord_webhook": [r"discord(?:app)?\.com/api/webhooks/"],
|
||||
"http_post": [r"https?://[^\s\"']+"],
|
||||
"ftp": [r"ftp://[^\s\"']+"],
|
||||
"smtp": [r"smtp\.[a-z0-9.-]+"],
|
||||
}
|
||||
|
||||
|
||||
def _strings(data: bytes) -> str:
|
||||
parts = [m.decode("ascii", "ignore") for m in ASCII_RE.findall(data)]
|
||||
parts += [m.decode("utf-16-le", "ignore") for m in UTF16_RE.findall(data)]
|
||||
return "\n".join(parts)
|
||||
|
||||
|
||||
def _defang(s: str) -> str:
|
||||
return s.replace("http", "hxxp").replace(".", "[.]")
|
||||
|
||||
|
||||
def profile(path: str) -> dict:
|
||||
blob = _strings(Path(path).read_bytes())
|
||||
low = blob.lower()
|
||||
targets = {}
|
||||
for cat, needles in TARGETS.items():
|
||||
hits = sorted({n for n in needles if n in low})
|
||||
if hits:
|
||||
targets[cat] = hits
|
||||
exfil = {}
|
||||
for chan, patterns in EXFIL.items():
|
||||
found = set()
|
||||
for pat in patterns:
|
||||
for m in re.findall(pat, blob, re.I):
|
||||
found.add(_defang(m))
|
||||
if found:
|
||||
exfil[chan] = sorted(found)[:10]
|
||||
return {
|
||||
"targeted_artifacts": targets,
|
||||
"exfiltration_channels": exfil,
|
||||
"is_likely_infostealer": len(targets) >= 2,
|
||||
}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Profile infostealer targets/exfil")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("profile")
|
||||
p.add_argument("file")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(profile(args.file), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -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,90 @@
|
||||
---
|
||||
name: analyzing-java-jar-malware
|
||||
description: 'Analyzes Java/JAR malware (such as Adwind/jRAT-class cross-platform RATs) by
|
||||
inventorying the archive, reading the manifest entry point, detecting obfuscators and string
|
||||
encryption, and flagging suspicious runtime, reflection, and networking class usage. Activates
|
||||
for requests to analyze a malicious JAR, inspect Java malware, or identify a Java RAT.'
|
||||
domain: cybersecurity
|
||||
subdomain: reverse-engineering
|
||||
tags:
|
||||
- reverse-engineering
|
||||
- java
|
||||
- jar
|
||||
- rat
|
||||
- decompilation
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1059
|
||||
- T1027
|
||||
- T1620
|
||||
d3fend:
|
||||
- D3-SDA
|
||||
- D3-DA
|
||||
references:
|
||||
- 'JAR/ZIP and Java class file format — https://docs.oracle.com/javase/specs/jvms/se17/html/'
|
||||
- 'MITRE ATT&CK T1027 Obfuscated Files or Information — https://attack.mitre.org/techniques/T1027/'
|
||||
---
|
||||
|
||||
# Analyzing Java/JAR Malware
|
||||
|
||||
## When to Use
|
||||
|
||||
- You have a malicious or suspicious `.jar` (often a cross-platform RAT) and need to map its
|
||||
structure, entry point, obfuscation, and capability surface before decompiling.
|
||||
- You want to triage a Java payload without running the JVM.
|
||||
|
||||
**Do not use** `java -jar` to run it — that executes the malware. Treat the JAR as a ZIP and read
|
||||
its contents statically.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- The JAR file, read inertly. Optional: a Java decompiler (CFR, Procyon) for the next stage.
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Read the archive statically; never launch the JVM on the sample. Defang any URLs found.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Inventory the archive and entry point
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py inspect sample.jar
|
||||
```
|
||||
|
||||
Lists `.class` files, embedded resources/payloads (nested JARs, scripts, encrypted blobs), and
|
||||
reads `META-INF/MANIFEST.MF` for `Main-Class`/`Premain-Class`.
|
||||
|
||||
### Step 2: Detect obfuscation and packers
|
||||
|
||||
Flags obfuscator fingerprints (Allatori, ProGuard, Zelix), single-character class/package names,
|
||||
and string-decryption indicators.
|
||||
|
||||
### Step 3: Flag capability classes
|
||||
|
||||
Surface dangerous API usage in strings/constant pools: `Runtime.exec`/`ProcessBuilder`,
|
||||
`java.lang.reflect`, `URLClassLoader`, `javax.crypto`, `java.net.Socket`, registry/persistence
|
||||
helpers.
|
||||
|
||||
### Step 4: Route to decompilation
|
||||
|
||||
Hand the key classes to a decompiler (CFR/Procyon) for source recovery; record IOCs.
|
||||
|
||||
## Validation
|
||||
|
||||
- The manifest entry point is read and reported.
|
||||
- Embedded payloads/nested archives are enumerated.
|
||||
- Capability flags are backed by concrete class/string evidence.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- String-encrypted samples where capability strings appear only after decryption.
|
||||
- Multi-stage droppers that unpack a second JAR at runtime.
|
||||
- Benign obfuscated commercial JARs — corroborate with capability and delivery context.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the inspector.
|
||||
- JVM/JAR format and ATT&CK T1027 references (linked in frontmatter).
|
||||
@@ -0,0 +1,38 @@
|
||||
# API Reference: Analyzing Java/JAR Malware
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` treats a JAR as a ZIP and inventories `.class` files and embedded payloads,
|
||||
reads the manifest entry point, detects obfuscator fingerprints and name obfuscation, and scans
|
||||
class constant pools for dangerous API usage. It reads the archive statically, never launches the
|
||||
JVM, and defangs URLs.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | zipfile, regex, JSON, file IO |
|
||||
| CFR / Procyon | optional | decompile recovered classes downstream (not required here) |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `inspect(path)`
|
||||
Returns class count, `Main-Class`, embedded payloads, resources, obfuscators, a name-obfuscation
|
||||
flag, capability APIs, and defanged URLs.
|
||||
|
||||
## Capability APIs flagged
|
||||
|
||||
`Runtime`/`ProcessBuilder` (exec), `java.lang.reflect` + `URLClassLoader` (reflective loading),
|
||||
`javax.crypto` (encryption), `java.net.Socket`/`URL` (networking), `java.awt.Robot` (surveillance).
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py inspect sample.jar
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- JVM specification (class file format, constant pool) — https://docs.oracle.com/javase/specs/jvms/se17/html/
|
||||
- JAR/ZIP structure and `MANIFEST.MF` `Main-Class` from the Java platform documentation
|
||||
- Obfuscator fingerprints (Allatori/ProGuard/Zelix) from public Java-malware analyses
|
||||
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Inspect a Java/JAR sample statically: structure, manifest, obfuscation, capabilities.
|
||||
|
||||
Treats the JAR as a ZIP, inventories .class files and embedded payloads, reads the manifest entry
|
||||
point, and scans class bytes for obfuscator fingerprints and dangerous API usage. Reads the archive
|
||||
statically; never launches the JVM. Defangs URLs.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
EMBED_SIGS = [(b"PK\x03\x04", "nested ZIP/JAR"), (b"MZ", "PE executable"),
|
||||
(b"#!", "script"), (b"\x7fELF", "ELF")]
|
||||
OBFUSCATORS = [b"allatori", b"proguard", b"zelix", b"klassmaster", b"stringer", b"dasho"]
|
||||
CAPABILITY_APIS = [b"java/lang/Runtime", b"java/lang/ProcessBuilder", b"java/lang/reflect",
|
||||
b"java/net/URLClassLoader", b"javax/crypto", b"java/net/Socket",
|
||||
b"java/net/URL", b"java/awt/Robot", b"java/util/Base64"]
|
||||
RE_URL = re.compile(rb"https?://[\x21-\x7e]{4,}")
|
||||
|
||||
|
||||
def _defang(s: str) -> str:
|
||||
return s.replace("http", "hxxp").replace(".", "[.]")
|
||||
|
||||
|
||||
def inspect(path: str) -> dict:
|
||||
data = Path(path).read_bytes()
|
||||
result: dict = {"is_jar": False}
|
||||
if not zipfile.is_zipfile(path):
|
||||
return {"error": "not a ZIP/JAR archive"}
|
||||
result["is_jar"] = True
|
||||
classes = []
|
||||
resources = []
|
||||
manifest = ""
|
||||
obf = set()
|
||||
caps = set()
|
||||
urls = set()
|
||||
nested = []
|
||||
with zipfile.ZipFile(path) as zf:
|
||||
for name in zf.namelist():
|
||||
if name.endswith(".class"):
|
||||
classes.append(name)
|
||||
try:
|
||||
body = zf.read(name)
|
||||
except Exception:
|
||||
continue
|
||||
for o in OBFUSCATORS:
|
||||
if o in body.lower():
|
||||
obf.add(o.decode())
|
||||
for api in CAPABILITY_APIS:
|
||||
if api in body:
|
||||
caps.add(api.decode().replace("/", "."))
|
||||
for u in RE_URL.findall(body):
|
||||
urls.add(_defang(u.decode("latin-1", "ignore")))
|
||||
elif name == "META-INF/MANIFEST.MF":
|
||||
manifest = zf.read(name).decode("latin-1", "ignore")
|
||||
else:
|
||||
resources.append(name)
|
||||
try:
|
||||
head = zf.read(name)[:8]
|
||||
except Exception:
|
||||
head = b""
|
||||
for sig, label in EMBED_SIGS:
|
||||
if head.startswith(sig):
|
||||
nested.append({"name": name, "type": label})
|
||||
main_class = ""
|
||||
for line in manifest.splitlines():
|
||||
if line.lower().startswith(("main-class:", "premain-class:")):
|
||||
main_class = line.split(":", 1)[1].strip()
|
||||
break
|
||||
# single-character class names suggest name obfuscation
|
||||
short_names = sum(1 for c in classes if len(Path(c).stem) <= 2)
|
||||
return {
|
||||
"is_jar": True,
|
||||
"class_count": len(classes),
|
||||
"main_class": main_class,
|
||||
"embedded_payloads": nested[:50],
|
||||
"resources": resources[:50],
|
||||
"obfuscators": sorted(obf),
|
||||
"name_obfuscation_suspected": short_names > max(3, len(classes) // 5),
|
||||
"capability_apis": sorted(caps),
|
||||
"urls": sorted(urls)[:50],
|
||||
}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Inspect a Java/JAR sample")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("inspect")
|
||||
p.add_argument("file")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(inspect(args.file), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -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,90 @@
|
||||
---
|
||||
name: analyzing-loaders-and-droppers
|
||||
description: 'Analyzes loader and dropper samples by identifying staging behavior — embedded or
|
||||
downloaded next-stage payloads, decode/decrypt routines, and the execution mechanism used to run
|
||||
the next stage. Activates for requests to analyze a loader or dropper, find a staged payload, or
|
||||
map multi-stage delivery behavior.'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware-analysis
|
||||
- loader
|
||||
- dropper
|
||||
- multi-stage
|
||||
- delivery
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1105
|
||||
- T1140
|
||||
- T1574
|
||||
d3fend:
|
||||
- D3-DA
|
||||
- D3-FCR
|
||||
references:
|
||||
- 'MITRE ATT&CK T1105 Ingress Tool Transfer — https://attack.mitre.org/techniques/T1105/'
|
||||
- 'MITRE ATT&CK T1140 Deobfuscate/Decode Files or Information — https://attack.mitre.org/techniques/T1140/'
|
||||
---
|
||||
|
||||
# Analyzing Loaders and Droppers
|
||||
|
||||
## When to Use
|
||||
|
||||
- You have a first-stage loader/dropper and need to find the next stage: an embedded resource, an
|
||||
appended overlay, or a download URL.
|
||||
- You need to identify the decode/decrypt routine and the execution mechanism (process creation,
|
||||
injection, scheduled task, DLL side-loading).
|
||||
|
||||
**Do not use** this to run the loader and fetch live stages — characterize staging statically and
|
||||
retrieve next stages only in an isolated sandbox.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- The loader/dropper sample (read inertly).
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Read bytes statically; defang download URLs; never execute to pull live payloads.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Locate staged payloads
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py stage sample.bin
|
||||
```
|
||||
|
||||
Detects embedded PE/archive signatures, an appended overlay beyond the PE's mapped size, large
|
||||
high-entropy blobs, and download URLs.
|
||||
|
||||
### Step 2: Identify decode/execution mechanism
|
||||
|
||||
Scan imports/strings for decode APIs (`CryptDecrypt`, base64), download APIs (`URLDownloadToFile`,
|
||||
`WinHttp`, `InternetReadFile`), and execution (`CreateProcess`, `ShellExecute`, `WinExec`,
|
||||
side-loading hints).
|
||||
|
||||
### Step 3: Map the delivery chain
|
||||
|
||||
Document stage-1 → decode → stage-2 → execution and map each step to ATT&CK.
|
||||
|
||||
### Step 4: Defang and report
|
||||
|
||||
Defang URLs and produce IOCs for the staging infrastructure.
|
||||
|
||||
## Validation
|
||||
|
||||
- Embedded/overlay stages are confirmed by signature or entropy, with offsets recorded.
|
||||
- Download URLs and execution mechanism are identified from imports/strings.
|
||||
- The multi-stage chain maps cleanly to ATT&CK techniques.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Treating a benign overlay (installer data, signature) as a payload without corroboration.
|
||||
- Missing stages fetched at runtime when no URL is in plaintext (obfuscated/encoded).
|
||||
- Confusing the decode routine for the payload itself.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the stager.
|
||||
- ATT&CK T1105 and T1140 (linked in frontmatter).
|
||||
@@ -0,0 +1,41 @@
|
||||
# API Reference: Analyzing Loaders and Droppers
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` analyzes a loader/dropper: it finds embedded PE/archive signatures (skipping
|
||||
the loader's own header), counts high-entropy windows (packed/encrypted stages), and scans strings
|
||||
for download, decode, and execution APIs plus URLs. It reads bytes statically, defangs URLs, and
|
||||
executes nothing.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | math, regex, Counter, JSON, file IO |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `stage(path)`
|
||||
Returns embedded signatures, high-entropy window count, download/decode/execution API hits,
|
||||
defanged URLs, and a loader likelihood flag.
|
||||
|
||||
### `_entropy(chunk)`
|
||||
Shannon entropy (bits/byte) over a window; >7.2 suggests compressed/encrypted data.
|
||||
|
||||
## API categories
|
||||
|
||||
- **download**: `URLDownloadToFile`, `InternetReadFile`, WinHTTP/WinINet.
|
||||
- **decode**: `CryptDecrypt`, `CryptStringToBinary`, base64/RC4/XOR.
|
||||
- **execution**: `CreateProcess`, `ShellExecute`, `WinExec`, `CreateRemoteThread`, `LoadLibrary`.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py stage sample.bin
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- MITRE ATT&CK T1105 Ingress Tool Transfer — https://attack.mitre.org/techniques/T1105/
|
||||
- MITRE ATT&CK T1140 Deobfuscate/Decode Information — https://attack.mitre.org/techniques/T1140/
|
||||
- Shannon entropy definition; overlay/staging patterns from public loader analyses
|
||||
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Analyze a loader/dropper: locate staged payloads and identify decode/download/execution APIs.
|
||||
|
||||
Detects embedded PE/archive signatures, scans strings for download/decode/execution APIs and
|
||||
URLs, and computes Shannon entropy over windows to flag large high-entropy (packed/encrypted)
|
||||
blobs. Reads bytes statically; executes nothing.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
import sys
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
ASCII_RE = re.compile(rb"[\x20-\x7e]{4,}")
|
||||
UTF16_RE = re.compile(rb"(?:[\x20-\x7e]\x00){4,}")
|
||||
RE_URL = re.compile(r"https?://[^\s\"'<>]+", re.I)
|
||||
|
||||
EMBED_SIGS = [(b"MZ", "PE"), (b"PK\x03\x04", "ZIP/Office"), (b"Rar!\x1a\x07", "RAR"),
|
||||
(b"\x1f\x8b", "GZIP"), (b"7z\xbc\xaf\x27\x1c", "7z")]
|
||||
|
||||
DOWNLOAD_API = ["urldownloadtofile", "internetreadfile", "internetopenurl", "winhttp",
|
||||
"httpopenrequest", "wininet"]
|
||||
DECODE_API = ["cryptdecrypt", "cryptstringtobinary", "base64", "rc4", "xor"]
|
||||
EXEC_API = ["createprocess", "shellexecute", "winexec", "createremotethread", "loadlibrary",
|
||||
"ntcreateuserprocess", "wow64"]
|
||||
|
||||
|
||||
def _strings(data: bytes) -> str:
|
||||
parts = [m.decode("ascii", "ignore") for m in ASCII_RE.findall(data)]
|
||||
parts += [m.decode("utf-16-le", "ignore") for m in UTF16_RE.findall(data)]
|
||||
return "\n".join(parts)
|
||||
|
||||
|
||||
def _entropy(chunk: bytes) -> float:
|
||||
if not chunk:
|
||||
return 0.0
|
||||
counts = Counter(chunk)
|
||||
total = len(chunk)
|
||||
return -sum((c / total) * math.log2(c / total) for c in counts.values())
|
||||
|
||||
|
||||
def stage(path: str) -> dict:
|
||||
data = Path(path).read_bytes()
|
||||
embedded = []
|
||||
for sig, label in EMBED_SIGS:
|
||||
idx = data.find(sig, 1) # skip offset 0 (the loader's own header)
|
||||
if idx != -1:
|
||||
embedded.append({"offset": idx, "type": label})
|
||||
win = 4096
|
||||
high_entropy = sum(1 for i in range(0, len(data), win)
|
||||
if _entropy(data[i:i + win]) > 7.2)
|
||||
blob = _strings(data)
|
||||
low = blob.lower()
|
||||
urls = sorted({u.replace("http", "hxxp").replace(".", "[.]") for u in RE_URL.findall(blob)})
|
||||
return {
|
||||
"embedded_signatures": embedded,
|
||||
"high_entropy_windows": high_entropy,
|
||||
"download_apis": sorted({a for a in DOWNLOAD_API if a in low}),
|
||||
"decode_apis": sorted({a for a in DECODE_API if a in low}),
|
||||
"execution_apis": sorted({a for a in EXEC_API if a in low}),
|
||||
"urls": urls[:50],
|
||||
"is_likely_loader": bool(embedded) or bool(urls and any(a in low for a in DOWNLOAD_API)),
|
||||
}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Analyze loader/dropper staging")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("stage")
|
||||
p.add_argument("file")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(stage(args.file), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -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,90 @@
|
||||
---
|
||||
name: analyzing-mach-o-binaries-on-macos
|
||||
description: 'Statically analyzes macOS Mach-O malware: parsing the header and load commands,
|
||||
handling fat/universal binaries, reading linked dylibs and entitlements, and checking code
|
||||
signatures to infer capability and trust. Activates for requests to analyze a Mach-O binary,
|
||||
inspect macOS malware, or parse load commands and entitlements.'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware-analysis
|
||||
- mach-o
|
||||
- macos
|
||||
- static-analysis
|
||||
- code-signing
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1027
|
||||
- T1059.002
|
||||
d3fend:
|
||||
- D3-FCR
|
||||
- D3-DA
|
||||
references:
|
||||
- 'Apple Mach-O file format reference — https://github.com/apple-oss-distributions/xnu/blob/main/EXTERNAL_HEADERS/mach-o/loader.h'
|
||||
- 'macOS code signing overview — https://developer.apple.com/documentation/security/code_signing_services'
|
||||
---
|
||||
|
||||
# Analyzing Mach-O Binaries on macOS
|
||||
|
||||
## When to Use
|
||||
|
||||
- You have a macOS sample (Mach-O) and need a static capability and trust read.
|
||||
- You must handle a fat/universal binary containing multiple architecture slices.
|
||||
- You need to inspect linked dylibs, entitlements, and code-signing status.
|
||||
|
||||
**Do not use** Windows PE tooling on Mach-O — the formats differ entirely; use Mach-O-aware
|
||||
parsers.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A Mach-O parser (Python stdlib `struct`, or `macholib`/LIEF); the sample handled inertly.
|
||||
- For signing/entitlements on macOS, `codesign`/`otool` are authoritative.
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Parse statically; never execute the sample, especially on a real macOS host.
|
||||
- Keep the sample password-protected at rest and reference it by hash.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Detect fat vs. thin and architecture
|
||||
|
||||
Check the magic: `0xCAFEBABE` (fat/universal) vs. `0xFEEDFACE`/`0xFEEDFACF` (Mach-O 32/64). For
|
||||
fat binaries, enumerate and analyze each slice.
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py header sample.macho
|
||||
```
|
||||
|
||||
### Step 2: Parse load commands
|
||||
|
||||
Read load commands for linked dylibs (`LC_LOAD_DYLIB`), entry point (`LC_MAIN`), and signing
|
||||
(`LC_CODE_SIGNATURE`). The dylib list hints at capability (networking, crypto).
|
||||
|
||||
### Step 3: Inspect entitlements and signing
|
||||
|
||||
On macOS, use `codesign`/`otool` to read entitlements and verify the signature. Ad-hoc or absent
|
||||
signatures and suspicious entitlements are risk indicators.
|
||||
|
||||
### Step 4: Infer capability and route
|
||||
|
||||
Map linked frameworks/symbols to behaviors and route to disassembly/RE for deeper analysis.
|
||||
|
||||
## Validation
|
||||
|
||||
- Fat binaries are decomposed and each slice is analyzed, not just the first.
|
||||
- Load commands, dylibs, and signing status are enumerated correctly.
|
||||
- Capability inferences are corroborated by linked frameworks/symbols.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Analyzing only one slice of a universal binary.
|
||||
- Trusting a present signature without verifying it (ad-hoc signatures verify but aren't trusted).
|
||||
- Applying PE assumptions (sections/imports) to Mach-O structures.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the Mach-O header parser.
|
||||
- Apple Mach-O loader.h and code signing docs (linked in frontmatter).
|
||||
@@ -0,0 +1,42 @@
|
||||
# API Reference: Analyzing Mach-O Binaries on macOS
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` parses Mach-O headers with stdlib `struct`: it detects fat/universal vs. thin
|
||||
binaries, enumerates slices, and reads load commands for linked dylibs, the entry point, and the
|
||||
presence of a code signature. It parses statically and never executes the sample.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | struct parsing, file IO |
|
||||
|
||||
## External tools
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| codesign | Verify signature and read entitlements on macOS |
|
||||
| otool | Inspect load commands and linked libraries |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `parse(path)`
|
||||
Dispatches on magic to handle fat vs. thin binaries; returns format, slices, and per-slice data.
|
||||
|
||||
### `parse_thin(data, base)`
|
||||
Parses one Mach-O image's load commands (dylibs, LC_MAIN, LC_CODE_SIGNATURE).
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py header sample.macho
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- Apple Mach-O `loader.h` (header and load-command structures) —
|
||||
https://github.com/apple-oss-distributions/xnu/blob/main/EXTERNAL_HEADERS/mach-o/loader.h
|
||||
- macOS code signing overview —
|
||||
https://developer.apple.com/documentation/security/code_signing_services
|
||||
- Fat/universal binary layout (FAT_MAGIC + slice table), Mach-O format documentation
|
||||
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Parse a Mach-O (or fat/universal) binary header and key load commands.
|
||||
|
||||
Detects fat vs. thin Mach-O, enumerates architecture slices, and parses load commands for linked
|
||||
dylibs, the main entry point, and the presence of a code signature. Uses stdlib ``struct`` only.
|
||||
It parses bytes statically and never executes the sample.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import struct
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
MH_MAGIC = 0xFEEDFACE
|
||||
MH_MAGIC_64 = 0xFEEDFACF
|
||||
MH_CIGAM = 0xCEFAEDFE
|
||||
MH_CIGAM_64 = 0xCFFAEDFE
|
||||
FAT_MAGIC = 0xCAFEBABE
|
||||
FAT_CIGAM = 0xBEBAFECF
|
||||
|
||||
LC_LOAD_DYLIB = 0x0C
|
||||
LC_MAIN = 0x80000028
|
||||
LC_CODE_SIGNATURE = 0x1D
|
||||
|
||||
|
||||
def _u32(data: bytes, off: int, be: bool) -> int:
|
||||
return struct.unpack_from(">I" if be else "<I", data, off)[0]
|
||||
|
||||
|
||||
def parse_thin(data: bytes, base: int) -> dict:
|
||||
magic = struct.unpack_from(">I", data, base)[0]
|
||||
be = magic in (MH_MAGIC, MH_MAGIC_64)
|
||||
is64 = magic in (MH_MAGIC_64, MH_CIGAM_64)
|
||||
hdr = base
|
||||
ncmds = _u32(data, hdr + 16, be)
|
||||
off = hdr + (32 if is64 else 28)
|
||||
dylibs, signed, entry = [], False, None
|
||||
for _ in range(ncmds):
|
||||
cmd = _u32(data, off, be)
|
||||
cmdsize = _u32(data, off + 4, be)
|
||||
if cmdsize == 0:
|
||||
break
|
||||
if cmd == LC_LOAD_DYLIB:
|
||||
name_off = _u32(data, off + 8, be)
|
||||
raw = data[off + name_off:off + cmdsize].split(b"\x00", 1)[0]
|
||||
dylibs.append(raw.decode("latin-1"))
|
||||
elif cmd == (LC_MAIN & 0xFFFFFFFF) or cmd == LC_MAIN:
|
||||
entry = "LC_MAIN"
|
||||
elif cmd == LC_CODE_SIGNATURE:
|
||||
signed = True
|
||||
off += cmdsize
|
||||
return {"arch": "64-bit" if is64 else "32-bit", "dylibs": dylibs,
|
||||
"has_code_signature": signed, "entry": entry}
|
||||
|
||||
|
||||
def parse(path: str) -> dict:
|
||||
data = Path(path).read_bytes()
|
||||
magic = struct.unpack_from(">I", data, 0)[0]
|
||||
if magic in (FAT_MAGIC, FAT_CIGAM):
|
||||
nfat = struct.unpack_from(">I", data, 4)[0]
|
||||
slices = []
|
||||
for i in range(nfat):
|
||||
entry_off = 8 + i * 20
|
||||
offset = struct.unpack_from(">I", data, entry_off + 8)[0]
|
||||
slices.append(parse_thin(data, offset))
|
||||
return {"format": "fat/universal", "slice_count": nfat, "slices": slices}
|
||||
if magic in (MH_MAGIC, MH_MAGIC_64, MH_CIGAM, MH_CIGAM_64):
|
||||
return {"format": "thin Mach-O", **parse_thin(data, 0)}
|
||||
return {"error": "not a Mach-O binary"}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Parse Mach-O header/load commands")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("header")
|
||||
p.add_argument("file")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(parse(args.file), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -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,89 @@
|
||||
---
|
||||
name: analyzing-malicious-iso-and-container-files
|
||||
description: 'Analyzes malicious ISO, IMG, VHD, and similar container files used to smuggle payloads
|
||||
past email and mark-of-the-web defenses by enumerating contents and flagging hidden executables,
|
||||
LNKs, and scripts. Activates for requests to analyze a malicious ISO/IMG, inspect a container
|
||||
delivery file, or find payloads hidden inside a disk image attachment.'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware-analysis
|
||||
- iso
|
||||
- container
|
||||
- delivery
|
||||
- mark-of-the-web
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1553.005
|
||||
- T1204.002
|
||||
- T1027
|
||||
d3fend:
|
||||
- D3-FCR
|
||||
- D3-FA
|
||||
references:
|
||||
- 'MITRE ATT&CK T1553.005 Mark-of-the-Web Bypass — https://attack.mitre.org/techniques/T1553/005/'
|
||||
- 'ISO 9660 file system — https://wiki.osdev.org/ISO_9660'
|
||||
---
|
||||
|
||||
# Analyzing Malicious ISO and Container Files
|
||||
|
||||
## When to Use
|
||||
|
||||
- You have an ISO/IMG/VHD attachment suspected of smuggling a payload (LNK + hidden DLL/EXE/script)
|
||||
past MOTW and scanners.
|
||||
- You need to enumerate contents and flag hidden/executable entries without mounting the image.
|
||||
|
||||
**Do not use** mounting/auto-run to inspect — that can trigger the lure. Parse the container's
|
||||
directory records statically.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- The container file (read inertly). Optional: `pycdlib` for full ISO parsing (the script falls
|
||||
back to signature/string carving if absent).
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Never mount or open the container interactively; carve statically. Defang any URLs found.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Identify the container type
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py inspect sample.iso
|
||||
```
|
||||
|
||||
Detects ISO 9660 (`CD001`), UDF, FAT/VHD signatures and reports the format.
|
||||
|
||||
### Step 2: Enumerate entries and flag payloads
|
||||
|
||||
Lists embedded file signatures (`MZ`, `PK`, LNK `\x4C\x00\x00\x00`, script headers) and flags
|
||||
hidden/executable/decoy entries.
|
||||
|
||||
### Step 3: Assess the lure chain
|
||||
|
||||
A typical chain is a visible LNK pointing at a hidden DLL/EXE/script in the same image — document
|
||||
the relationship.
|
||||
|
||||
### Step 4: Extract and route
|
||||
|
||||
Carve the hidden payload and route it to the appropriate analysis workflow; defang URLs.
|
||||
|
||||
## Validation
|
||||
|
||||
- The container format is identified by signature.
|
||||
- Hidden executables/LNKs/scripts are enumerated with offsets.
|
||||
- The lure→payload relationship is documented.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Mounting the image and triggering the lure.
|
||||
- Missing payloads in less-common container formats (VHDX, UDF) without the right parser.
|
||||
- Overlooking hidden-attribute files that the file manager would not show.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the inspector.
|
||||
- ATT&CK T1553.005 and ISO 9660 references (linked in frontmatter).
|
||||
@@ -0,0 +1,38 @@
|
||||
# API Reference: Analyzing Malicious ISO and Container Files
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` identifies a container's format (ISO 9660, VHD/VHDX, FAT/IMG) by signature and
|
||||
carves embedded file signatures (PE, ZIP/Office, LNK, scripts) with offsets. With pycdlib it adds a
|
||||
root directory listing. It reads bytes statically and never mounts the image.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| pycdlib | latest | full ISO directory listing (optional) |
|
||||
| (stdlib) | — | JSON, file IO |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `inspect(path)`
|
||||
Returns format, ISO directory (if pycdlib), embedded signatures, and a hidden-payload flag.
|
||||
|
||||
### `_format(data)`
|
||||
Signature-based container format detection (`CD001`, `conectix`, `vhdxfile`, FAT markers).
|
||||
|
||||
## Carved signatures
|
||||
|
||||
PE (`MZ`), ZIP/Office (`PK`), LNK header, shebang/WSF/batch scripts.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py inspect sample.iso
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- MITRE ATT&CK T1553.005 Mark-of-the-Web Bypass — https://attack.mitre.org/techniques/T1553/005/
|
||||
- ISO 9660 (`CD001` at sector 16) — https://wiki.osdev.org/ISO_9660
|
||||
- VHD `conectix` / VHDX `vhdxfile` signatures from Microsoft VHD/VHDX specifications
|
||||
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Inspect ISO/IMG/VHD container files for hidden/smuggled payloads.
|
||||
|
||||
Identifies the container format by signature and carves embedded file signatures (PE, archives,
|
||||
LNK, scripts) with offsets. Uses pycdlib for full ISO directory listing when available; otherwise
|
||||
falls back to signature carving. Reads bytes statically; never mounts the image.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
import pycdlib # type: ignore
|
||||
HAVE_PYCDLIB = True
|
||||
except ImportError:
|
||||
HAVE_PYCDLIB = False
|
||||
|
||||
EMBED_SIGS = [
|
||||
(b"MZ", "PE executable"),
|
||||
(b"PK\x03\x04", "ZIP/Office"),
|
||||
(b"\x4c\x00\x00\x00\x01\x14\x02\x00", "LNK shortcut"),
|
||||
(b"#!", "script (shebang)"),
|
||||
(b"<job", "WSF job"),
|
||||
(b"@echo", "batch script"),
|
||||
]
|
||||
|
||||
|
||||
def _format(data: bytes) -> str:
|
||||
if data[0x8001:0x8006] == b"CD001" or b"CD001" in data[:0x10000]:
|
||||
return "ISO 9660"
|
||||
if data[:8] == b"conectix":
|
||||
return "VHD"
|
||||
if data[:8] == b"vhdxfile":
|
||||
return "VHDX"
|
||||
if data[0x36:0x3b] == b"FAT16" or data[0x52:0x57] == b"FAT32":
|
||||
return "FAT (IMG)"
|
||||
return "unknown"
|
||||
|
||||
|
||||
def _carve(data: bytes) -> list[dict]:
|
||||
found = []
|
||||
for sig, label in EMBED_SIGS:
|
||||
start = 0
|
||||
while len(found) < 100:
|
||||
idx = data.find(sig, start)
|
||||
if idx == -1:
|
||||
break
|
||||
found.append({"offset": idx, "type": label})
|
||||
start = idx + 1
|
||||
return found
|
||||
|
||||
|
||||
def _iso_listing(path: str) -> list[str]:
|
||||
if not HAVE_PYCDLIB:
|
||||
return []
|
||||
names = []
|
||||
try:
|
||||
iso = pycdlib.PyCdlib()
|
||||
iso.open(path)
|
||||
for child in iso.list_children(iso_path="/"):
|
||||
ident = getattr(child.file_identifier(), "decode", lambda *_: "")("utf-8", "ignore")
|
||||
if ident and ident not in (".", ".."):
|
||||
names.append(ident)
|
||||
iso.close()
|
||||
except Exception:
|
||||
return []
|
||||
return names
|
||||
|
||||
|
||||
def inspect(path: str) -> dict:
|
||||
data = Path(path).read_bytes()
|
||||
carved = _carve(data)
|
||||
return {
|
||||
"format": _format(data),
|
||||
"pycdlib_available": HAVE_PYCDLIB,
|
||||
"iso_directory": _iso_listing(path),
|
||||
"embedded_signatures": carved,
|
||||
"hidden_payload_suspected": any(c["type"].startswith(("PE", "LNK", "script"))
|
||||
for c in carved),
|
||||
}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Inspect ISO/container files")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("inspect")
|
||||
p.add_argument("file")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(inspect(args.file), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -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,96 @@
|
||||
---
|
||||
name: analyzing-malicious-lnk-files
|
||||
description: 'Analyzes weaponized Windows shortcut (.lnk) files: parsing the shell link structure
|
||||
for the target command, arguments, icon, and working directory, and recovering hidden
|
||||
PowerShell/cmd payloads and embedded content used in phishing. Activates for requests to
|
||||
analyze a malicious LNK, parse a shortcut file, or extract a command from a .lnk.'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware-analysis
|
||||
- lnk
|
||||
- shortcut
|
||||
- document-malware
|
||||
- static-analysis
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1204.002
|
||||
- T1059.001
|
||||
- T1059.003
|
||||
d3fend:
|
||||
- D3-FCR
|
||||
- D3-DA
|
||||
references:
|
||||
- 'Microsoft [MS-SHLLINK] Shell Link Binary File Format — https://learn.microsoft.com/openspecs/windows_protocols/ms-shllink/'
|
||||
- 'LNK forensic structure references — https://github.com/libyal/liblnk/blob/main/documentation/Windows%20Shortcut%20File%20(LNK)%20format.asciidoc'
|
||||
---
|
||||
|
||||
# Analyzing Malicious LNK Files
|
||||
|
||||
## When to Use
|
||||
|
||||
- You have a `.lnk` delivered via phishing (often in an archive or ISO) and need its real action.
|
||||
- You must recover the target command line, arguments, and any embedded/hidden payload.
|
||||
- You are extracting IOCs (commands, URLs, file paths) from a shortcut.
|
||||
|
||||
**Do not use** Windows Explorer to inspect or double-click the shortcut — that executes its
|
||||
target. Parse the binary structure statically.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- An LNK parser (Python `struct`, or `lnkparse`/`LECmd`); the sample handled inertly.
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Never double-click or hover-load the shortcut in Explorer; parse raw bytes only.
|
||||
- Defang recovered URLs and store any embedded payload password-protected.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Confirm and parse the header
|
||||
|
||||
Verify the LNK magic (`4C 00 00 00` + the link CLSID) and read `LinkFlags` to know which optional
|
||||
structures are present.
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py parse sample.lnk
|
||||
```
|
||||
|
||||
### Step 2: Recover the target and arguments
|
||||
|
||||
Extract the relative/local path and, crucially, the `COMMAND_LINE_ARGUMENTS` string — malicious
|
||||
LNKs hide `powershell -enc ...` or `cmd /c ...` here.
|
||||
|
||||
### Step 3: Inspect icon and metadata
|
||||
|
||||
Read the icon location (often spoofed to look like a document) and metadata (machine ID,
|
||||
timestamps) useful for attribution/clustering.
|
||||
|
||||
### Step 4: Find appended/embedded payloads
|
||||
|
||||
Check for data appended after the LNK structure (some campaigns embed scripts/binaries past the
|
||||
parsed end) and extract it.
|
||||
|
||||
### Step 5: Decode and extract IOCs
|
||||
|
||||
Decode encoded commands (UTF-16LE base64), defang URLs, and route extracted payloads to the right
|
||||
analysis workflow.
|
||||
|
||||
## Validation
|
||||
|
||||
- The full command line, including arguments, is recovered (not just the target path).
|
||||
- Encoded commands are decoded to cleartext.
|
||||
- Appended/embedded payloads are detected and extracted, and IOCs are defanged.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Reading only the target path and missing the malicious arguments.
|
||||
- Inspecting in Explorer and executing the shortcut.
|
||||
- Ignoring data appended past the LNK structure.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the LNK parser.
|
||||
- MS-SHLLINK and the liblnk format notes (linked in frontmatter).
|
||||
@@ -0,0 +1,42 @@
|
||||
# API Reference: Analyzing Malicious LNK Files
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` is a minimal [MS-SHLLINK] parser: it validates the LNK header and CLSID, reads
|
||||
LinkFlags, skips the IDList/LinkInfo, and extracts the StringData fields (name, relative path,
|
||||
working dir, arguments, icon), plus the count of any appended data. It parses statically and never
|
||||
executes the shortcut.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | struct parsing, file IO |
|
||||
|
||||
## External tools
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| LECmd / lnkparse | Full-featured LNK parsing including extra data blocks |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `parse(path)`
|
||||
Returns LinkFlags and the recovered StringData fields, appended-data size, and a suspicious flag.
|
||||
|
||||
### `_read_stringdata(data, off, unicode_)`
|
||||
Reads a CountedString (ANSI or UTF-16LE) per the IsUnicode flag.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py parse sample.lnk
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- Microsoft [MS-SHLLINK] Shell Link Binary File Format —
|
||||
https://learn.microsoft.com/openspecs/windows_protocols/ms-shllink/
|
||||
- liblnk LNK format documentation —
|
||||
https://github.com/libyal/liblnk/blob/main/documentation/Windows%20Shortcut%20File%20(LNK)%20format.asciidoc
|
||||
- Malicious-argument hiding in COMMAND_LINE_ARGUMENTS, established document-analysis practice
|
||||
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Parse a Windows shell link (.lnk) and recover its command and arguments.
|
||||
|
||||
Implements a minimal [MS-SHLLINK] parser using stdlib ``struct``: it validates the header,
|
||||
reads LinkFlags, and walks the optional StringData section to extract the name, relative path,
|
||||
working directory, command-line arguments, and icon location. It also reports any data appended
|
||||
past the structure. It parses bytes statically and never executes the shortcut.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import struct
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
HEADER_SIZE = 0x4C
|
||||
LINK_CLSID = b"\x01\x14\x02\x00\x00\x00\x00\x00\xc0\x00\x00\x00\x00\x00\x00\x46"
|
||||
|
||||
# LinkFlags bits relevant to StringData (MS-SHLLINK 2.1.1)
|
||||
F_HAS_LINK_TARGET_IDLIST = 0x00000001
|
||||
F_HAS_NAME = 0x00000004
|
||||
F_HAS_RELATIVE_PATH = 0x00000008
|
||||
F_HAS_WORKING_DIR = 0x00000010
|
||||
F_HAS_ARGUMENTS = 0x00000020
|
||||
F_HAS_ICON_LOCATION = 0x00000040
|
||||
F_IS_UNICODE = 0x00000080
|
||||
|
||||
|
||||
def _read_stringdata(data: bytes, off: int, unicode_: bool) -> tuple[str, int]:
|
||||
count = struct.unpack_from("<H", data, off)[0]
|
||||
off += 2
|
||||
if unicode_:
|
||||
raw = data[off:off + count * 2]
|
||||
off += count * 2
|
||||
return raw.decode("utf-16-le", "replace"), off
|
||||
raw = data[off:off + count]
|
||||
off += count
|
||||
return raw.decode("latin-1", "replace"), off
|
||||
|
||||
|
||||
def parse(path: str) -> dict:
|
||||
data = Path(path).read_bytes()
|
||||
if len(data) < HEADER_SIZE or struct.unpack_from("<I", data, 0)[0] != HEADER_SIZE:
|
||||
return {"error": "not a LNK (bad HeaderSize)"}
|
||||
if data[4:20] != LINK_CLSID:
|
||||
return {"error": "not a LNK (bad CLSID)"}
|
||||
flags = struct.unpack_from("<I", data, 20)[0]
|
||||
unicode_ = bool(flags & F_IS_UNICODE)
|
||||
off = HEADER_SIZE
|
||||
|
||||
# Skip LinkTargetIDList if present
|
||||
if flags & F_HAS_LINK_TARGET_IDLIST:
|
||||
idlist_size = struct.unpack_from("<H", data, off)[0]
|
||||
off += 2 + idlist_size
|
||||
|
||||
# Skip LinkInfo if present (HasLinkInfo = 0x2)
|
||||
if flags & 0x00000002:
|
||||
link_info_size = struct.unpack_from("<I", data, off)[0]
|
||||
off += link_info_size
|
||||
|
||||
result = {"flags": hex(flags), "unicode": unicode_}
|
||||
for bit, key in [(F_HAS_NAME, "name"), (F_HAS_RELATIVE_PATH, "relative_path"),
|
||||
(F_HAS_WORKING_DIR, "working_dir"), (F_HAS_ARGUMENTS, "arguments"),
|
||||
(F_HAS_ICON_LOCATION, "icon_location")]:
|
||||
if flags & bit and off < len(data):
|
||||
try:
|
||||
value, off = _read_stringdata(data, off, unicode_)
|
||||
result[key] = value
|
||||
except struct.error:
|
||||
break
|
||||
|
||||
result["appended_data_bytes"] = max(0, len(data) - off)
|
||||
args = result.get("arguments", "")
|
||||
result["suspicious"] = any(t in args.lower() for t in
|
||||
("powershell", "-enc", "cmd", "mshta", "rundll32", "frombase64"))
|
||||
return result
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Parse a malicious LNK")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("parse")
|
||||
p.add_argument("file")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(parse(args.file), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -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,108 @@
|
||||
---
|
||||
name: analyzing-malicious-office-macros
|
||||
description: 'Analyzes malicious Office documents by extracting and reviewing VBA macros and
|
||||
OLE/OOXML structure: auto-exec triggers, obfuscation, dropped payloads, and shell/PowerShell
|
||||
invocation. Activates for requests to analyze a malicious Word/Excel document, VBA macro, or
|
||||
maldoc.'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware
|
||||
- maldoc
|
||||
- vba
|
||||
- office
|
||||
- olevba
|
||||
- phishing
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1566.001
|
||||
- T1059.005
|
||||
- T1204.002
|
||||
d3fend:
|
||||
- D3-FCR
|
||||
- D3-DA
|
||||
references:
|
||||
- 'oletools (olevba) — https://github.com/decalage2/oletools'
|
||||
- 'MS-OVBA VBA file format — https://learn.microsoft.com/openspecs/office_file_formats/ms-ovba/'
|
||||
---
|
||||
|
||||
# Analyzing Malicious Office Macros
|
||||
|
||||
## When to Use
|
||||
|
||||
- A Word/Excel/PowerPoint document is suspected of delivering malware via macros.
|
||||
- You need to extract VBA, identify auto-exec triggers, and see what the macro does.
|
||||
- You are determining the next-stage payload or URL a maldoc fetches.
|
||||
|
||||
**Do not use** the document's "Enable Content" path to analyze it — never open a maldoc in
|
||||
Office. Extract and read the macro statically; detonate only in the isolated lab if needed.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- `oletools` (`pip install oletools`) for `olevba`/`oleid`, or the bundled OLE/OOXML extractor.
|
||||
- The document in neutralized form inside the lab.
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Do **not** open the document in Microsoft Office. Macros may auto-run on open/close.
|
||||
- Static extraction only; if dynamic analysis is required, detonate in the isolated victim VM.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Identify the container and locate macros
|
||||
|
||||
OOXML (`.docm`, `.xlsm`) is a ZIP; legacy (`.doc`, `.xls`) is OLE2. Macros live in a
|
||||
`vbaProject.bin` OLE stream. Extract it:
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py extract maldoc.docm
|
||||
```
|
||||
|
||||
### Step 2: Flag auto-exec triggers
|
||||
|
||||
Look for entry points that run without user action beyond enabling macros:
|
||||
|
||||
```text
|
||||
AutoOpen, Document_Open, AutoClose, Workbook_Open, Auto_Open, Document_Close
|
||||
```
|
||||
|
||||
### Step 3: Find suspicious calls
|
||||
|
||||
Identify execution and download primitives:
|
||||
|
||||
```text
|
||||
Shell, WScript.Shell, CreateObject, powershell, cmd /c
|
||||
URLDownloadToFile, MSXML2.XMLHTTP, ADODB.Stream (drop to disk)
|
||||
Environ, GetObject("winmgmts:") (WMI)
|
||||
```
|
||||
|
||||
### Step 4: Deobfuscate
|
||||
|
||||
Maldocs commonly use Chr()/string concatenation, base64, and split-and-join. Resolve these to
|
||||
recover the real command and any URL or dropped path.
|
||||
|
||||
### Step 5: Extract IOCs and the next stage
|
||||
|
||||
Pull URLs, dropped file paths, and the spawned command line. These become the report's IOCs
|
||||
and pivot points.
|
||||
|
||||
## Validation
|
||||
|
||||
- The recovered command line and URL make sense together (download → drop → execute).
|
||||
- Auto-exec triggers explain how the macro starts.
|
||||
- Deobfuscated strings match what dynamic detonation would reveal (if you confirm in the lab).
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Opening the document in Office "just to see" — this executes the macro.
|
||||
- Stopping at obfuscated source without resolving it; the IOCs are inside the obfuscation.
|
||||
- Missing stomped VBA (p-code present, source removed) — check for VBA stomping and analyze
|
||||
p-code if the source looks empty.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the extractor and
|
||||
indicator scanner.
|
||||
- oletools and MS-OVBA (linked in frontmatter).
|
||||
@@ -0,0 +1,44 @@
|
||||
# API Reference: Analyzing Malicious Office Macros
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` extracts VBA macros and flags auto-exec triggers, suspicious calls, and
|
||||
URLs. It prefers `oletools`/olevba and falls back to locating `vbaProject.bin` in OOXML ZIP
|
||||
containers (or scanning OLE2 bytes) when oletools is unavailable.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| oletools | >= 0.60 | VBA extraction (olevba), OLE inspection (optional but recommended) |
|
||||
| (stdlib) | — | zipfile/regex fallback |
|
||||
|
||||
## External tools
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| olevba / ol0id (oletools) | Macro extraction and document triage |
|
||||
| oledump.py | Manual OLE stream inspection |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `analyze(path)`
|
||||
Dispatches to olevba or the minimal extractor; returns macro presence, auto-exec triggers,
|
||||
suspicious calls, and URLs.
|
||||
|
||||
### `_scan_text(text)`
|
||||
Flags auto-exec entry points, suspicious VBA calls, and extracts URLs from macro source.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
pip install oletools
|
||||
python scripts/analyst.py extract maldoc.docm
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- oletools (olevba) — https://github.com/decalage2/oletools
|
||||
- MS-OVBA VBA file format —
|
||||
https://learn.microsoft.com/openspecs/office_file_formats/ms-ovba/
|
||||
- VBA auto-exec trigger names (Office VBA object model documentation)
|
||||
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Extract and triage VBA macros from Office documents.
|
||||
|
||||
Prefers ``oletools`` (olevba) for robust extraction; falls back to locating ``vbaProject.bin``
|
||||
inside OOXML (ZIP) containers and flagging suspicious indicators in the raw bytes. Never opens
|
||||
the document in Office and never executes anything.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
AUTOEXEC = ["AutoOpen", "Document_Open", "AutoClose", "Document_Close",
|
||||
"Workbook_Open", "Auto_Open", "Auto_Close"]
|
||||
SUSPICIOUS = ["Shell", "WScript.Shell", "CreateObject", "powershell", "cmd",
|
||||
"URLDownloadToFile", "XMLHTTP", "ADODB.Stream", "Environ",
|
||||
"winmgmts", "Base64", "Chr(", "GetObject"]
|
||||
|
||||
|
||||
def _scan_text(text: str) -> dict:
|
||||
found_auto = [k for k in AUTOEXEC if k.lower() in text.lower()]
|
||||
found_sus = [k for k in SUSPICIOUS if k.lower() in text.lower()]
|
||||
urls = sorted(set(re.findall(r"https?://[^\s\"'<>)]+", text)))
|
||||
return {"auto_exec": found_auto, "suspicious": found_sus, "urls": urls}
|
||||
|
||||
|
||||
def extract_with_olevba(path: str) -> dict:
|
||||
from oletools.olevba import VBA_Parser # type: ignore
|
||||
vp = VBA_Parser(path)
|
||||
macros = []
|
||||
if vp.detect_vba_macros():
|
||||
for _, _, name, code in vp.extract_macros():
|
||||
macros.append({"name": name, **_scan_text(code),
|
||||
"lines": code.count("\n") + 1})
|
||||
vp.close()
|
||||
return {"engine": "olevba", "has_macros": bool(macros), "macros": macros}
|
||||
|
||||
|
||||
def extract_minimal(path: str) -> dict:
|
||||
p = Path(path)
|
||||
data = p.read_bytes()
|
||||
indicators = {}
|
||||
if zipfile.is_zipfile(path):
|
||||
with zipfile.ZipFile(path) as zf:
|
||||
vba = [n for n in zf.namelist() if n.lower().endswith("vbaproject.bin")]
|
||||
if not vba:
|
||||
return {"engine": "minimal", "has_macros": False,
|
||||
"note": "no vbaProject.bin in OOXML container"}
|
||||
blob = zf.read(vba[0])
|
||||
# vbaProject.bin is OLE/compressed; scan recoverable ASCII for indicators
|
||||
text = blob.decode("latin-1", "replace")
|
||||
indicators = _scan_text(text)
|
||||
return {"engine": "minimal", "has_macros": True,
|
||||
"stream": vba[0], "indicators": indicators,
|
||||
"note": "install oletools for full VBA source extraction"}
|
||||
# legacy OLE2: scan whole file bytes for indicator strings
|
||||
text = data.decode("latin-1", "replace")
|
||||
return {"engine": "minimal", "has_macros": b"Attribut" in data,
|
||||
"indicators": _scan_text(text),
|
||||
"note": "legacy OLE2; install oletools for reliable extraction"}
|
||||
|
||||
|
||||
def analyze(path: str) -> dict:
|
||||
try:
|
||||
return extract_with_olevba(path)
|
||||
except ImportError:
|
||||
return extract_minimal(path)
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Analyze Office macros")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("extract")
|
||||
p.add_argument("file")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(analyze(args.file), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -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,93 @@
|
||||
---
|
||||
name: analyzing-malicious-onenote-and-html-smuggling
|
||||
description: 'Analyzes two modern delivery techniques: malicious OneNote (.one) attachments with
|
||||
embedded payloads behind fake buttons, and HTML smuggling pages that reconstruct payloads in the
|
||||
browser from embedded blobs. Activates for requests to analyze a malicious OneNote file, detect
|
||||
HTML smuggling, or extract a smuggled/embedded payload.'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware-analysis
|
||||
- onenote
|
||||
- html-smuggling
|
||||
- document-malware
|
||||
- delivery
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1027.006
|
||||
- T1204.002
|
||||
- T1566.001
|
||||
d3fend:
|
||||
- D3-FCR
|
||||
- D3-DA
|
||||
references:
|
||||
- 'MITRE ATT&CK T1027.006 HTML Smuggling — https://attack.mitre.org/techniques/T1027/006/'
|
||||
- 'OneNote file format (MS-ONESTORE) — https://learn.microsoft.com/openspecs/office_file_formats/ms-onestore/'
|
||||
---
|
||||
|
||||
# Analyzing Malicious OneNote and HTML Smuggling
|
||||
|
||||
## When to Use
|
||||
|
||||
- You have a OneNote (`.one`) attachment suspected of hiding an embedded executable/script behind a
|
||||
"click to view" lure.
|
||||
- You have an HTML/SVG page that reconstructs and auto-downloads a payload from an embedded blob
|
||||
(HTML smuggling).
|
||||
- You need to extract the hidden payload without triggering the lure.
|
||||
|
||||
**Do not use** OneNote or a browser to open these files for analysis — that is exactly the
|
||||
delivery mechanism. Carve the embedded data statically.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A static carver (Python) for embedded file signatures; the sample handled inertly.
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Never open the `.one` in OneNote or the HTML in a browser.
|
||||
- Defang URLs and store carved payloads password-protected.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: For OneNote — carve embedded files
|
||||
|
||||
OneNote stores attached files in the document. Scan for embedded file signatures (`MZ`, script
|
||||
headers, archives) and the `FileDataStoreObject` GUIDs, and carve them out.
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py carve sample.one
|
||||
```
|
||||
|
||||
### Step 2: For HTML smuggling — find the embedded blob
|
||||
|
||||
Look for large base64/`Blob`/`Uint8Array` constructions, `data:` URIs, and a JS routine that
|
||||
builds a Blob and triggers a download (`msSaveOrOpenBlob`, anchor `download`, `createObjectURL`).
|
||||
|
||||
### Step 3: Reconstruct the payload statically
|
||||
|
||||
Decode the embedded base64/byte array (and any XOR/char-code layer) to recover the payload as
|
||||
data — without executing the page.
|
||||
|
||||
### Step 4: Analyze and extract IOCs
|
||||
|
||||
Hash carved payloads, identify their type, defang any URLs, and route executables/scripts to the
|
||||
appropriate analysis workflow.
|
||||
|
||||
## Validation
|
||||
|
||||
- Embedded payloads are carved/reconstructed from the inert file, not by opening it.
|
||||
- The recovered payload's type is identified and hashed.
|
||||
- The lure/trigger mechanism (fake button, Blob download) is documented.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Opening the OneNote/HTML to "see" the lure and executing the payload.
|
||||
- Decoding only the first layer when the blob is additionally XOR/char-code encoded.
|
||||
- Missing multiple embedded objects in a single OneNote page.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the carver/decoder.
|
||||
- ATT&CK T1027.006 and MS-ONESTORE (linked in frontmatter).
|
||||
@@ -0,0 +1,34 @@
|
||||
# API Reference: Analyzing Malicious OneNote and HTML Smuggling
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` carves embedded file signatures (PE, archives, scripts) from OneNote/binary
|
||||
files by offset, and detects HTML smuggling by locating large base64 blobs that decode to
|
||||
executables/archives alongside Blob-download hints. It reads statically and executes nothing.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | base64, regex, JSON, file IO |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `carve(data)`
|
||||
Returns offsets and types of embedded file signatures.
|
||||
|
||||
### `html_smuggle(data)`
|
||||
Returns smuggling hints (Blob/Uint8Array/download) and any base64 blobs that decode to payloads.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py carve sample.one
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- MITRE ATT&CK T1027.006 HTML Smuggling — https://attack.mitre.org/techniques/T1027/006/
|
||||
- OneNote file format (MS-ONESTORE, embedded FileDataStoreObject) —
|
||||
https://learn.microsoft.com/openspecs/office_file_formats/ms-onestore/
|
||||
- Blob/createObjectURL/msSaveOrOpenBlob smuggling pattern, established delivery-analysis practice
|
||||
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Carve embedded payloads from OneNote files and reconstruct HTML-smuggling blobs.
|
||||
|
||||
For OneNote (and any binary), it scans for embedded file signatures (PE, archives, scripts) and
|
||||
reports their offsets. For HTML, it locates large base64/byte-array blobs and decodes them to
|
||||
recover a smuggled payload. It reads bytes/text statically and executes nothing.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import binascii
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
EMBED_SIGS = [
|
||||
(b"MZ", "PE executable"),
|
||||
(b"PK\x03\x04", "ZIP/Office/JAR"),
|
||||
(b"\x1f\x8b", "GZIP"),
|
||||
(b"Rar!\x1a\x07", "RAR"),
|
||||
(b"%PDF", "PDF"),
|
||||
(b"#!", "script (shebang)"),
|
||||
(b"<job", "WSF job"),
|
||||
]
|
||||
|
||||
RE_B64_BLOB = re.compile(rb"[A-Za-z0-9+/]{200,}={0,2}")
|
||||
SMUGGLE_HINTS = [b"msSaveOrOpenBlob", b"createObjectURL", b"Uint8Array", b"new Blob",
|
||||
b"download=", b"base64"]
|
||||
|
||||
|
||||
def carve(data: bytes) -> list[dict]:
|
||||
found = []
|
||||
for sig, label in EMBED_SIGS:
|
||||
start = 0
|
||||
while True:
|
||||
idx = data.find(sig, start)
|
||||
if idx == -1:
|
||||
break
|
||||
found.append({"offset": idx, "type": label})
|
||||
start = idx + 1
|
||||
if len(found) > 50:
|
||||
return found
|
||||
return found
|
||||
|
||||
|
||||
def html_smuggle(data: bytes) -> dict:
|
||||
hints = sorted({h.decode() for h in SMUGGLE_HINTS if h in data})
|
||||
decoded = []
|
||||
for m in RE_B64_BLOB.findall(data):
|
||||
try:
|
||||
raw = base64.b64decode(m)
|
||||
except (binascii.Error, ValueError):
|
||||
continue
|
||||
if raw[:2] == b"MZ" or raw[:4] == b"PK\x03\x04" or raw[:4] == b"%PDF":
|
||||
decoded.append({"size": len(raw), "carved_type": carve(raw)[:1]})
|
||||
return {"smuggling_hints": hints, "decoded_blobs": decoded,
|
||||
"likely_smuggling": bool(hints and decoded)}
|
||||
|
||||
|
||||
def analyze(path: str) -> dict:
|
||||
data = Path(path).read_bytes()
|
||||
return {
|
||||
"embedded_signatures": carve(data),
|
||||
"html_smuggling": html_smuggle(data),
|
||||
}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Carve OneNote / HTML-smuggling payloads")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("carve")
|
||||
p.add_argument("file")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(analyze(args.file), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -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,91 @@
|
||||
---
|
||||
name: analyzing-malicious-pdfs
|
||||
description: 'Analyzes malicious PDF documents: parsing the object structure for JavaScript,
|
||||
OpenAction/launch triggers, embedded files, and URIs, then extracting and decoding suspicious
|
||||
streams without opening the document in a reader. Activates for requests to analyze a malicious
|
||||
PDF, inspect PDF JavaScript, or extract embedded payloads from a PDF.'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware-analysis
|
||||
- pdf
|
||||
- document-malware
|
||||
- javascript
|
||||
- static-analysis
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1204.002
|
||||
- T1059.007
|
||||
- T1027
|
||||
d3fend:
|
||||
- D3-FCR
|
||||
- D3-DA
|
||||
references:
|
||||
- 'PDF 32000-1:2008 (ISO PDF specification) — https://www.pdfa.org/resource/iso-32000-pdf/'
|
||||
- 'Didier Stevens PDF tools (pdfid, pdf-parser) — https://blog.didierstevens.com/programs/pdf-tools/'
|
||||
---
|
||||
|
||||
# Analyzing Malicious PDFs
|
||||
|
||||
## When to Use
|
||||
|
||||
- You have a suspicious PDF (often from phishing) and must determine if it is weaponized.
|
||||
- You need to find auto-executing actions, embedded JavaScript, launch actions, or embedded files.
|
||||
- You want to extract and decode suspicious streams safely.
|
||||
|
||||
**Do not use** a PDF reader to "just look" — opening in a viewer can trigger the very actions you
|
||||
are investigating. Parse the structure statically.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A PDF object parser (Python, or pdfid/pdf-parser/peepdf); the sample handled inertly.
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Never open the PDF in a graphical reader; parse the raw object structure only.
|
||||
- Defang any extracted URLs and store embedded payloads password-protected.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Triage the object inventory
|
||||
|
||||
Count high-risk keywords: `/JavaScript`, `/JS`, `/OpenAction`, `/AA`, `/Launch`, `/EmbeddedFile`,
|
||||
`/URI`, `/RichMedia`. Their presence (especially auto-actions) flags weaponization.
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py triage sample.pdf
|
||||
```
|
||||
|
||||
### Step 2: Locate auto-execution triggers
|
||||
|
||||
Resolve `/OpenAction` and `/AA` (additional actions) to see what runs on open, and any `/Launch`
|
||||
actions that spawn external programs.
|
||||
|
||||
### Step 3: Extract and decode streams
|
||||
|
||||
Pull JavaScript and suspicious streams, applying filters (`/FlateDecode`, `/ASCIIHexDecode`) to
|
||||
get the cleartext; deobfuscate layered JS as needed.
|
||||
|
||||
### Step 4: Recover payloads and IOCs
|
||||
|
||||
Extract embedded files and URIs; hash payloads, defang URLs, and route extracted executables to
|
||||
PE analysis.
|
||||
|
||||
## Validation
|
||||
|
||||
- Auto-execution triggers (`/OpenAction`, `/AA`, `/Launch`) are resolved to concrete actions.
|
||||
- JavaScript/streams are decoded through their filters, not left encoded.
|
||||
- Embedded payloads and URIs are extracted, hashed, and defanged.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Opening the PDF in a reader and triggering the payload.
|
||||
- Missing object-stream (`/ObjStm`) compressed objects that hide the malicious content.
|
||||
- Stopping at the first JS layer when it is multiply obfuscated.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the PDF triage parser.
|
||||
- ISO 32000 and Didier Stevens' PDF tools (linked in frontmatter).
|
||||
@@ -0,0 +1,39 @@
|
||||
# API Reference: Analyzing Malicious PDFs
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` scans raw PDF bytes for weaponization keywords (/JavaScript, /OpenAction,
|
||||
/Launch, /EmbeddedFile, /URI, /ObjStm, etc.), counts objects/streams, extracts and defangs URIs,
|
||||
and flags likely weaponization. It parses statically and never opens the document.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | regex, JSON, file IO |
|
||||
|
||||
## External tools
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| pdfid / pdf-parser (Didier Stevens) | Object inventory and stream extraction with filters |
|
||||
| peepdf | Interactive PDF analysis and JS extraction |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `triage(path)`
|
||||
Returns object/stream counts, risk-keyword counts, auto-action list, defanged URIs, and a
|
||||
weaponization flag.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py triage sample.pdf
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- PDF 32000-1:2008 (ISO PDF specification) — https://www.pdfa.org/resource/iso-32000-pdf/
|
||||
- Didier Stevens PDF tools (pdfid, pdf-parser) —
|
||||
https://blog.didierstevens.com/programs/pdf-tools/
|
||||
- /OpenAction, /AA, /Launch auto-execution and /ObjStm hiding, established PDF-analysis practice
|
||||
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Triage a PDF's object structure for weaponization indicators.
|
||||
|
||||
Scans the raw PDF bytes for high-risk keywords (/JavaScript, /OpenAction, /Launch, /EmbeddedFile,
|
||||
/URI, etc.), counts indirect objects and streams, and extracts URIs. It parses bytes statically
|
||||
and never opens the document in a reader, so auto-actions cannot fire.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
KEYWORDS = ["/JavaScript", "/JS", "/OpenAction", "/AA", "/Launch", "/EmbeddedFile",
|
||||
"/URI", "/RichMedia", "/SubmitForm", "/GoToR", "/ObjStm", "/AcroForm",
|
||||
"/XFA", "/JBIG2Decode"]
|
||||
|
||||
AUTO_ACTION = {"/OpenAction", "/AA", "/Launch"}
|
||||
|
||||
RE_URI = re.compile(rb"/URI\s*\(([^)]*)\)")
|
||||
RE_OBJ = re.compile(rb"\d+\s+\d+\s+obj")
|
||||
RE_STREAM = re.compile(rb"\bstream\b")
|
||||
|
||||
|
||||
def _defang(url: str) -> str:
|
||||
return url.replace("http", "hxxp").replace(".", "[.]")
|
||||
|
||||
|
||||
def triage(path: str) -> dict:
|
||||
data = Path(path).read_bytes()
|
||||
if not data.lstrip().startswith(b"%PDF"):
|
||||
return {"error": "not a PDF (no %PDF header)"}
|
||||
counts = {kw: len(re.findall(re.escape(kw.encode()), data)) for kw in KEYWORDS}
|
||||
counts = {k: v for k, v in counts.items() if v}
|
||||
uris = sorted({m.decode("latin-1", "replace") for m in RE_URI.findall(data)})
|
||||
auto = [kw for kw in AUTO_ACTION if counts.get(kw)]
|
||||
has_js = bool(counts.get("/JavaScript") or counts.get("/JS"))
|
||||
return {
|
||||
"objects": len(RE_OBJ.findall(data)),
|
||||
"streams": len(RE_STREAM.findall(data)),
|
||||
"risk_keywords": counts,
|
||||
"auto_actions": auto,
|
||||
"has_javascript": has_js,
|
||||
"uris": [_defang(u) for u in uris],
|
||||
"weaponized_likely": bool(auto and has_js) or bool(counts.get("/Launch")),
|
||||
}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Triage a malicious PDF")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("triage")
|
||||
p.add_argument("file")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(triage(args.file), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -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,91 @@
|
||||
---
|
||||
name: analyzing-malicious-vbscript-and-wsf
|
||||
description: 'Analyzes malicious VBScript, WSF, and HTA scripts: parsing WSF/HTA containers,
|
||||
decoding VBScript.Encode (#@~^) and string obfuscation, and identifying WScript.Shell/ActiveX
|
||||
abuse to recover dropper behavior. Activates for requests to analyze VBScript, decode a WSF/HTA,
|
||||
or investigate a Windows Script Host dropper.'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware-analysis
|
||||
- vbscript
|
||||
- wsf
|
||||
- hta
|
||||
- document-malware
|
||||
- deobfuscation
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1059.005
|
||||
- T1059.003
|
||||
- T1027
|
||||
d3fend:
|
||||
- D3-FCR
|
||||
- D3-DA
|
||||
references:
|
||||
- 'Microsoft Windows Script Host documentation — https://learn.microsoft.com/previous-versions/windows/internet-explorer/ie-developer/scripting-articles/'
|
||||
- 'VBScript.Encode / Script Encoder format analysis — https://gist.github.com/bcse/1834878'
|
||||
---
|
||||
|
||||
# Analyzing Malicious VBScript and WSF
|
||||
|
||||
## When to Use
|
||||
|
||||
- You have a `.vbs`, `.wsf`, or `.hta` delivered via phishing and need its real behavior.
|
||||
- You must decode `VBScript.Encode`-obfuscated script (the `#@~^` marker) or string obfuscation.
|
||||
- You are identifying WScript.Shell/ActiveX-based dropper actions and IOCs.
|
||||
|
||||
**Do not use** `wscript`/`cscript`/`mshta` to run the script for analysis — that executes the
|
||||
dropper. Decode and read it statically.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A static decoder (Python) or a script sandbox; the sample handled inertly in the lab.
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Never run the script with Windows Script Host (`wscript`/`cscript`/`mshta`).
|
||||
- Defang recovered URLs; store dropped payloads password-protected.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Parse the container
|
||||
|
||||
For WSF, parse the XML `<job>`/`<script>` elements (a WSF can bundle multiple languages). For HTA,
|
||||
separate the HTML wrapper from the `<script>` body.
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py analyze sample.wsf
|
||||
```
|
||||
|
||||
### Step 2: Decode VBScript.Encode
|
||||
|
||||
If you see the `#@~^` header, decode the Microsoft Script Encoder stream back to source.
|
||||
|
||||
### Step 3: Unroll string obfuscation
|
||||
|
||||
Resolve `Chr()`/`Asc()` builds, string concatenation, and `Replace()`-based deobfuscators to
|
||||
reveal commands and URLs.
|
||||
|
||||
### Step 4: Identify dropper actions and IOCs
|
||||
|
||||
Find `CreateObject("WScript.Shell")`, `Run`/`Exec`, ADODB/MSXML download patterns, and PowerShell
|
||||
hand-offs; recover URLs/paths, defang, and route payloads onward.
|
||||
|
||||
## Validation
|
||||
|
||||
- WSF/HTA containers are decomposed into their script bodies (all jobs/languages).
|
||||
- `VBScript.Encode` streams are decoded to readable source.
|
||||
- Dropper actions and IOCs are recovered without executing the script.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Executing with `wscript`/`mshta` to "see" behavior, infecting the host.
|
||||
- Missing one `<script>` block in a multi-job WSF.
|
||||
- Overlooking the `#@~^` encoded section and analyzing only the cleartext stub.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the WSF/VBScript decoder.
|
||||
- Windows Script Host docs and the Script Encoder format (linked in frontmatter).
|
||||
@@ -0,0 +1,37 @@
|
||||
# API Reference: Analyzing Malicious VBScript and WSF
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` extracts `<script>` bodies from WSF/HTA containers, detects and isolates
|
||||
Microsoft Script Encoder (`#@~^`) blocks, and flags suspicious WScript/ActiveX dropper APIs. It
|
||||
parses text only and never invokes Windows Script Host. Full Script Encoder decoding is delegated
|
||||
to a dedicated decoder; the script isolates encoded blocks and confirms their presence.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | regex, JSON, file IO |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `analyze(text)`
|
||||
Returns script-block counts, encoded-block presence, suspicious API hits, and defanged URLs.
|
||||
|
||||
### `_isolate_script_encoder(encoded)`
|
||||
Strips the `#@~^`/`^#~@` markers and framing to isolate the encoded body for follow-on decoding.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py analyze sample.wsf
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- Microsoft Windows Script Host documentation —
|
||||
https://learn.microsoft.com/previous-versions/windows/internet-explorer/ie-developer/scripting-articles/
|
||||
- VBScript.Encode / Script Encoder format (`#@~^` marker) analysis —
|
||||
https://gist.github.com/bcse/1834878
|
||||
- WScript.Shell / ADODB.Stream / MSXML2.XMLHTTP dropper patterns, established script-analysis
|
||||
practice
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Analyze WSF/HTA/VBScript droppers statically.
|
||||
|
||||
Extracts ``<script>`` bodies from WSF/HTA XML/HTML containers, decodes Microsoft Script Encoder
|
||||
(``VBScript.Encode``, ``#@~^``) streams, and flags suspicious WScript/ActiveX dropper patterns.
|
||||
It parses text only and never invokes Windows Script Host.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
RE_SCRIPT = re.compile(r"<script\b[^>]*>(.*?)</script>", re.I | re.S)
|
||||
RE_ENCODED = re.compile(r"#@~\^.*?\^#~@", re.S)
|
||||
RE_URL = re.compile(r"https?://[^\s'\"<>)]+", re.I)
|
||||
|
||||
SUSPECT = ["wscript.shell", "createobject", ".run", ".exec", "powershell", "adodb.stream",
|
||||
"msxml2.xmlhttp", "winhttp", "shell.application", "regwrite"]
|
||||
|
||||
|
||||
def _isolate_script_encoder(encoded: str) -> str:
|
||||
"""Isolate the body of a #@~^...^#~@ Script Encoder block for follow-on decoding.
|
||||
|
||||
Full Script Encoder decoding relies on a published ~140-entry substitution table; this
|
||||
strips the wrapper markers and the 6-character length/checksum framing so the encoded body
|
||||
can be handed to a dedicated decoder. The goal here is detection and isolation, not a
|
||||
reimplementation of the entire table.
|
||||
"""
|
||||
m = re.match(r"#@~\^.{6}==(.*)\^#~@", encoded, re.S)
|
||||
return m.group(1) if m else encoded
|
||||
|
||||
|
||||
def analyze(text: str) -> dict:
|
||||
scripts = [s.strip() for s in RE_SCRIPT.findall(text)]
|
||||
if not scripts:
|
||||
scripts = [text] # bare .vbs
|
||||
encoded_blocks = RE_ENCODED.findall(text)
|
||||
isolated = [_isolate_script_encoder(b) for b in encoded_blocks]
|
||||
combined = "\n".join(scripts + isolated).lower()
|
||||
indicators = sorted({s for s in SUSPECT if s in combined})
|
||||
urls = sorted(set(RE_URL.findall(text)))
|
||||
return {
|
||||
"script_blocks": len(scripts),
|
||||
"script_encoder_blocks": len(encoded_blocks),
|
||||
"encoded_block_present": bool(encoded_blocks),
|
||||
"suspicious_apis": indicators,
|
||||
"urls": [u.replace("http", "hxxp").replace(".", "[.]") for u in urls],
|
||||
"note": "encoded blocks isolated; finish Script Encoder decode with a dedicated decoder",
|
||||
}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Analyze VBScript/WSF/HTA")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("analyze")
|
||||
p.add_argument("file")
|
||||
args = parser.parse_args(argv)
|
||||
text = Path(args.file).read_text(encoding="utf-8-sig", errors="replace")
|
||||
print(json.dumps(analyze(text), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -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,119 @@
|
||||
---
|
||||
name: analyzing-malware-in-memory-with-volatility3
|
||||
description: 'Analyzes a memory image with Volatility 3 to find malware: rogue processes,
|
||||
injected code, suspicious network connections, loaded modules, and persistence, then
|
||||
extracts artifacts for further analysis. Activates for requests to do memory forensics,
|
||||
analyze a RAM dump, or hunt malware in memory with Volatility.'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware
|
||||
- memory-forensics
|
||||
- volatility3
|
||||
- process-injection
|
||||
- dfir
|
||||
- windows
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1055
|
||||
- T1057
|
||||
- T1003
|
||||
- T1056
|
||||
d3fend:
|
||||
- D3-PSA
|
||||
- D3-PMAD
|
||||
references:
|
||||
- 'Volatility 3 documentation — https://volatility3.readthedocs.io/'
|
||||
- 'The Art of Memory Forensics (concepts) — https://www.memoryanalysis.net/'
|
||||
---
|
||||
|
||||
# Analyzing Malware in Memory with Volatility 3
|
||||
|
||||
## When to Use
|
||||
|
||||
- You have a RAM image from a suspected-infected host and need to find malicious activity.
|
||||
- Disk artifacts are insufficient (fileless/in-memory malware) and you need volatile evidence.
|
||||
- You want to extract injected code, command lines, or network connections for analysis.
|
||||
|
||||
**Do not use** Volatility plugins blindly without an order of investigation — start broad
|
||||
(processes, network) before deep per-process dumps.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Volatility 3 (`pip install volatility3`) with appropriate symbol tables.
|
||||
- A memory image acquired with a sound tool (WinPmem, LiME, or hypervisor snapshot).
|
||||
- Knowledge of the source OS/version to select the right symbols.
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Work on a copy of the image; preserve the original with a recorded hash.
|
||||
- Treat any dumped executable region as a live sample — store and handle it accordingly.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Enumerate processes and spot anomalies
|
||||
|
||||
```bash
|
||||
vol -f memory.raw windows.pslist
|
||||
vol -f memory.raw windows.pstree
|
||||
```
|
||||
|
||||
Look for unusual parents (Word spawning cmd/powershell), masquerading names
|
||||
(`scvhost.exe`), processes with no disk path, and orphaned children.
|
||||
|
||||
### Step 2: Hunt injected code
|
||||
|
||||
```bash
|
||||
vol -f memory.raw windows.malfind
|
||||
```
|
||||
|
||||
`malfind` flags private, executable, RWX regions with no backing file — classic injection.
|
||||
Note the PID and base address for dumping.
|
||||
|
||||
### Step 3: Review network connections
|
||||
|
||||
```bash
|
||||
vol -f memory.raw windows.netscan
|
||||
```
|
||||
|
||||
Correlate listening/established connections with suspicious PIDs and the C2 endpoints from
|
||||
other analysis.
|
||||
|
||||
### Step 4: Check modules, handles, and persistence
|
||||
|
||||
Examine loaded DLLs (`windows.dlllist`), services, and registry (`windows.registry.*`) for
|
||||
persistence and unexpected modules.
|
||||
|
||||
### Step 5: Dump artifacts
|
||||
|
||||
Dump the suspicious process or injected region for static/RE analysis:
|
||||
|
||||
```bash
|
||||
vol -f memory.raw windows.dumpfiles --pid <pid>
|
||||
```
|
||||
|
||||
The helper script parses Volatility's JSON renderer output to highlight injection candidates.
|
||||
|
||||
```bash
|
||||
vol -f memory.raw -r json windows.malfind | python scripts/analyst.py malfind -
|
||||
```
|
||||
|
||||
## Validation
|
||||
|
||||
- Injection candidates from `malfind` correspond to anomalous processes from `pstree`.
|
||||
- Network connections map to known C2 or the sample's extracted config.
|
||||
- Dumped regions disassemble into meaningful code, not random bytes.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Wrong symbols/profile producing empty or garbage results — confirm OS build first.
|
||||
- Treating every RWX region as malicious; some legitimate JIT engines use RWX. Corroborate.
|
||||
- Forgetting to hash and preserve the original image before analysis.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the malfind output
|
||||
parser.
|
||||
- Volatility 3 documentation and memory-forensics concepts (linked in frontmatter).
|
||||
@@ -0,0 +1,40 @@
|
||||
# API Reference: Analyzing Malware in Memory with Volatility 3
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` consumes Volatility 3 `windows.malfind` JSON output and ranks memory
|
||||
regions by injection signal (RWX protection, embedded MZ header, executable prologue). It
|
||||
analyzes plugin output, not raw memory.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | JSON parsing |
|
||||
| volatility3 | >= 2.5 | Produces the memory-plugin output this tool consumes |
|
||||
|
||||
## External tools
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| Volatility 3 | Memory image analysis (pslist, pstree, malfind, netscan, dumpfiles) |
|
||||
| WinPmem / LiME | Memory acquisition |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `rank_malfind(doc)`
|
||||
Parses malfind rows and scores each by injection indicators, returning a sorted candidate
|
||||
list with PID, process, address, and protection.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
pip install volatility3
|
||||
vol -f memory.raw -r json windows.malfind | python scripts/analyst.py malfind -
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- Volatility 3 documentation — https://volatility3.readthedocs.io/
|
||||
- The Art of Memory Forensics — concepts of injection detection (malfind/RWX)
|
||||
- Volatility 3 JSON renderer output structure (project docs)
|
||||
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Parse Volatility 3 JSON output to highlight likely process-injection candidates.
|
||||
|
||||
Volatility 3 can render plugin output as JSON (``vol -r json ...``). This tool consumes a
|
||||
``windows.malfind`` JSON document (from a file or stdin) and ranks regions by injection
|
||||
signal: RWX protection and the presence of an MZ header or executable prologue bytes.
|
||||
|
||||
It analyzes plugin output only; it does not parse raw memory itself.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
|
||||
|
||||
def _rows(doc) -> list[dict]:
|
||||
# Volatility 3 JSON renderer emits a list of row dicts; tolerate a wrapped form.
|
||||
if isinstance(doc, list):
|
||||
return doc
|
||||
if isinstance(doc, dict):
|
||||
for key in ("rows", "data"):
|
||||
if isinstance(doc.get(key), list):
|
||||
return doc[key]
|
||||
return []
|
||||
|
||||
|
||||
def rank_malfind(doc) -> list[dict]:
|
||||
out = []
|
||||
for row in _rows(doc):
|
||||
prot = str(row.get("Protection", row.get("protection", ""))).upper()
|
||||
disasm = str(row.get("Disasm", row.get("disasm", "")))
|
||||
hexdump = str(row.get("Hexdump", row.get("hexdump", "")))
|
||||
score = 0
|
||||
if "EXECUTE_READWRITE" in prot or "RWX" in prot:
|
||||
score += 3
|
||||
if "4D 5A" in hexdump or hexdump.lstrip().startswith("MZ"):
|
||||
score += 3 # PE header in an injected region
|
||||
if any(op in disasm for op in ("push", "mov", "call", "jmp")):
|
||||
score += 1
|
||||
out.append({
|
||||
"pid": row.get("PID", row.get("pid")),
|
||||
"process": row.get("Process", row.get("process")),
|
||||
"address": row.get("Start VPN", row.get("Address", row.get("address"))),
|
||||
"protection": prot,
|
||||
"injection_score": score,
|
||||
})
|
||||
return sorted(out, key=lambda r: r["injection_score"], reverse=True)
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Rank Volatility malfind output")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("malfind")
|
||||
p.add_argument("json", help="path to malfind JSON, or - for stdin")
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
text = sys.stdin.read() if args.json == "-" else open(args.json, encoding="utf-8").read()
|
||||
doc = json.loads(text)
|
||||
print(json.dumps(rank_malfind(doc), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -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,93 @@
|
||||
---
|
||||
name: analyzing-pe-imports-and-exports
|
||||
description: 'Analyzes a PE file''s import and export tables to infer capability: mapping imported
|
||||
APIs to behaviors (networking, crypto, injection, persistence), spotting dynamic-resolution
|
||||
stubs, and reading exports of malicious DLLs. Activates for requests to analyze PE imports,
|
||||
inspect the IAT/exports, or infer capability from API usage.'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware-analysis
|
||||
- pe
|
||||
- imports
|
||||
- exports
|
||||
- capability
|
||||
- static-analysis
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1106
|
||||
- T1027
|
||||
d3fend:
|
||||
- D3-FCR
|
||||
- D3-DA
|
||||
references:
|
||||
- 'Microsoft PE/COFF specification — https://learn.microsoft.com/windows/win32/debug/pe-format'
|
||||
- 'pefile library — https://github.com/erocarrera/pefile'
|
||||
---
|
||||
|
||||
# Analyzing PE Imports and Exports
|
||||
|
||||
## When to Use
|
||||
|
||||
- You have a Windows PE and want a fast capability read from its imported APIs.
|
||||
- You need to identify suspicious API clusters (injection, crypto, anti-analysis) before deeper
|
||||
reversing.
|
||||
- You are inspecting a malicious DLL's exports to find its callable entry points.
|
||||
|
||||
**Do not use** the import table alone as a verdict — packers stub imports and malware resolves
|
||||
APIs dynamically; a thin IAT can hide real capability.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- `pefile` (`pip install pefile`); the sample handled inertly in the lab.
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Parse the PE statically; never run it during import analysis.
|
||||
- Keep the sample password-protected at rest and reference it by hash.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Parse imports
|
||||
|
||||
Enumerate imported DLLs and functions from the import directory (IAT). A sparse IAT plus a
|
||||
`GetProcAddress`/`LoadLibrary` pair suggests dynamic resolution.
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py imports sample.exe
|
||||
```
|
||||
|
||||
### Step 2: Map APIs to behaviors
|
||||
|
||||
Cluster imports into behavior buckets: networking (`Ws2_32`, `WinINet`), process/injection
|
||||
(`VirtualAllocEx`, `WriteProcessMemory`, `CreateRemoteThread`), crypto (`Crypt*`, `bcrypt`),
|
||||
persistence (`Reg*`, service APIs), and anti-analysis (`IsDebuggerPresent`).
|
||||
|
||||
### Step 3: Inspect exports
|
||||
|
||||
For DLLs, list exports (by name and ordinal). Unusual export names or a single exported
|
||||
`DllRegisterServer`/ordinal hint at how the module is invoked.
|
||||
|
||||
### Step 4: Flag dynamic resolution
|
||||
|
||||
Note `LoadLibrary`/`GetProcAddress`, ordinal-only imports, and tiny IATs as signs that the real
|
||||
capability is resolved at runtime — escalate to dynamic/RE workflows.
|
||||
|
||||
## Validation
|
||||
|
||||
- Import behavior buckets are consistent with other static signals (strings, sections).
|
||||
- A thin or stubbed IAT is recognized as a packing/dynamic-resolution indicator, not "benign".
|
||||
- Exports of a malicious DLL are enumerated with their invocation method identified.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Trusting a small IAT as low-capability when imports are resolved dynamically.
|
||||
- Ignoring delay-load and bound imports.
|
||||
- Reading ordinal-only imports without resolving them to function names.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the import/export parser.
|
||||
- Microsoft PE/COFF spec and pefile (linked in frontmatter).
|
||||
@@ -0,0 +1,36 @@
|
||||
# API Reference: Analyzing PE Imports and Exports
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` parses a PE's import and export tables with `pefile`, buckets imported APIs
|
||||
into behavior categories (networking, injection, crypto, persistence, anti-analysis,
|
||||
dynamic-resolution), and flags a thin IAT. It parses statically and never executes the sample.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| pefile | >= 2023.2.7 | PE import/export directory parsing |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `analyze(path)`
|
||||
Returns imported DLLs/functions, exports, capability buckets, and a `thin_iat` flag.
|
||||
|
||||
### `_bucketize(funcs)`
|
||||
Maps API names to behavior categories via substring matching.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
pip install pefile
|
||||
python scripts/analyst.py imports sample.exe
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- Microsoft PE/COFF specification (import/export directories) —
|
||||
https://learn.microsoft.com/windows/win32/debug/pe-format
|
||||
- pefile library — https://github.com/erocarrera/pefile
|
||||
- API-to-behavior mapping (e.g., VirtualAllocEx+WriteProcessMemory+CreateRemoteThread =
|
||||
injection), established static-analysis practice
|
||||
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Parse a PE file's imports/exports and bucket APIs into capability categories.
|
||||
|
||||
Uses ``pefile`` to enumerate imported DLLs/functions and exports, then maps imported API names to
|
||||
behavior buckets (networking, injection, crypto, persistence, anti-analysis) and flags
|
||||
dynamic-resolution indicators. It parses the PE statically and never executes it.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
|
||||
BEHAVIOR = {
|
||||
"networking": ["wsastartup", "connect", "send", "recv", "internetopen", "httpopenrequest",
|
||||
"winhttpopen", "urldownloadtofile"],
|
||||
"injection": ["virtualallocex", "writeprocessmemory", "createremotethread",
|
||||
"ntunmapviewofsection", "queueuserapc", "setthreadcontext"],
|
||||
"crypto": ["cryptencrypt", "cryptdecrypt", "cryptacquirecontext", "bcryptencrypt",
|
||||
"cryptgenkey"],
|
||||
"persistence": ["regsetvalue", "regcreatekey", "createservice", "openscmanager",
|
||||
"schedule"],
|
||||
"anti_analysis": ["isdebuggerpresent", "checkremotedebuggerpresent",
|
||||
"ntqueryinformationprocess", "outputdebugstring"],
|
||||
"dynamic_resolution": ["loadlibrary", "getprocaddress", "ldrloaddll"],
|
||||
}
|
||||
|
||||
|
||||
def _bucketize(funcs: list[str]) -> dict:
|
||||
lowered = [f.lower() for f in funcs]
|
||||
out = {}
|
||||
for bucket, needles in BEHAVIOR.items():
|
||||
hits = sorted({f for f in lowered if any(n in f for n in needles)})
|
||||
if hits:
|
||||
out[bucket] = hits
|
||||
return out
|
||||
|
||||
|
||||
def analyze(path: str) -> dict:
|
||||
import pefile # type: ignore
|
||||
pe = pefile.PE(path)
|
||||
imports = {}
|
||||
all_funcs = []
|
||||
if hasattr(pe, "DIRECTORY_ENTRY_IMPORT"):
|
||||
for entry in pe.DIRECTORY_ENTRY_IMPORT:
|
||||
dll = entry.dll.decode("latin-1") if entry.dll else "?"
|
||||
funcs = []
|
||||
for imp in entry.imports:
|
||||
name = imp.name.decode("latin-1") if imp.name else f"ordinal_{imp.ordinal}"
|
||||
funcs.append(name)
|
||||
imports[dll] = funcs
|
||||
all_funcs.extend(funcs)
|
||||
|
||||
exports = []
|
||||
if hasattr(pe, "DIRECTORY_ENTRY_EXPORT"):
|
||||
for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols:
|
||||
exports.append({"name": exp.name.decode("latin-1") if exp.name else None,
|
||||
"ordinal": exp.ordinal})
|
||||
|
||||
return {
|
||||
"imported_dlls": len(imports),
|
||||
"imported_functions": len(all_funcs),
|
||||
"imports": imports,
|
||||
"exports": exports,
|
||||
"capabilities": _bucketize(all_funcs),
|
||||
"thin_iat": len(all_funcs) < 10,
|
||||
}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Analyze PE imports/exports")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("imports")
|
||||
p.add_argument("file")
|
||||
args = parser.parse_args(argv)
|
||||
try:
|
||||
print(json.dumps(analyze(args.file), indent=2))
|
||||
except ImportError:
|
||||
print("missing dependency: pefile (pip install pefile)", file=sys.stderr)
|
||||
return 2
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -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,88 @@
|
||||
---
|
||||
name: analyzing-position-independent-code
|
||||
description: 'Analyzes position-independent code and shellcode by identifying GetPC/PEB-walk
|
||||
techniques, API-hash resolution loops, and relocation-free addressing so the payload can be
|
||||
disassembled at the right base. Activates for requests to analyze position-independent code,
|
||||
identify PIC techniques, or understand how shellcode resolves APIs without imports.'
|
||||
domain: cybersecurity
|
||||
subdomain: reverse-engineering
|
||||
tags:
|
||||
- reverse-engineering
|
||||
- position-independent-code
|
||||
- shellcode
|
||||
- peb-walk
|
||||
- api-hashing
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1027
|
||||
- T1027.007
|
||||
- T1620
|
||||
d3fend:
|
||||
- D3-SDA
|
||||
- D3-DA
|
||||
references:
|
||||
- 'PEB / LDR structures (Windows internals) — https://learn.microsoft.com/windows/win32/api/winternl/'
|
||||
- 'MITRE ATT&CK T1027.007 Dynamic API Resolution — https://attack.mitre.org/techniques/T1027/007/'
|
||||
---
|
||||
|
||||
# Analyzing Position-Independent Code
|
||||
|
||||
## When to Use
|
||||
|
||||
- You have a PIC blob/shellcode that resolves its own addresses and APIs without imports, and you
|
||||
need to understand its technique before disassembly.
|
||||
- You want to identify the base-resolution (GetPC), PEB-walk, and API-hashing mechanics.
|
||||
|
||||
**Do not use** this to execute the code — analyze it statically (and emulate separately if needed).
|
||||
Treat the blob as malicious.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- The PIC/shellcode blob (read inertly).
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Read bytes statically; store the blob password-protected and never execute it.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Identify PIC techniques
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py analyze shellcode.bin
|
||||
```
|
||||
|
||||
Detects GetPC stubs (`call $+5`/`fnstenv`), PEB access (`fs:[30]`/`gs:[60]`), API-hash loops
|
||||
(`lodsb` + `ror`), and stack-string construction.
|
||||
|
||||
### Step 2: Determine architecture and base behavior
|
||||
|
||||
Confirm 32- vs 64-bit PEB access and how the code computes its own base for relative addressing.
|
||||
|
||||
### Step 3: Map API resolution
|
||||
|
||||
Note the hashing algorithm and that resolved APIs are obtained by walking the PEB→LDR→export tables
|
||||
(pair with the API-hash resolver skill).
|
||||
|
||||
### Step 4: Disassemble at the right base
|
||||
|
||||
Load the blob at the inferred base/entry in a disassembler/emulator and proceed.
|
||||
|
||||
## Validation
|
||||
|
||||
- PIC technique hits reference real patterns (GetPC/PEB/API-hash), not arbitrary bytes.
|
||||
- Architecture is consistent with the PEB-access form detected.
|
||||
- The analysis points to a concrete entry/base for disassembly.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Encoded/staged shellcode where the PIC patterns appear only after decoding.
|
||||
- Mixed 32/64-bit (WoW64) transitions.
|
||||
- Custom GetPC variants not matching common signatures.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the analyzer.
|
||||
- PEB/LDR and ATT&CK T1027.007 references (linked in frontmatter).
|
||||
@@ -0,0 +1,37 @@
|
||||
# API Reference: Analyzing Position-Independent Code
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` identifies position-independent code techniques in a shellcode blob — GetPC
|
||||
stubs, PEB access (x86/x64), API-hash loops, and stack-string construction — and infers the
|
||||
architecture from the PEB-access form. It reads bytes statically and executes nothing.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | JSON, file IO |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `analyze(path)`
|
||||
Returns detected techniques, inferred architecture, PEB-walk/API-hashing flags, and a PIC flag.
|
||||
|
||||
## Techniques detected
|
||||
|
||||
- GetPC: `call $+5` (`E8 00 00 00 00`), `fldz; fnstenv`.
|
||||
- PEB: `fs:[30]` (x86), `gs:[60]` (x64).
|
||||
- API hashing: `lodsb` + `ror 13`.
|
||||
- Stack strings: `mov [esp+x], imm`.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py analyze shellcode.bin
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- PEB/LDR structures (winternl.h) — https://learn.microsoft.com/windows/win32/api/winternl/
|
||||
- MITRE ATT&CK T1027.007 Dynamic API Resolution — https://attack.mitre.org/techniques/T1027/007/
|
||||
- GetPC / PEB-walk / ror13 patterns from public shellcode research
|
||||
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Identify position-independent code techniques in a shellcode blob.
|
||||
|
||||
Scans for GetPC stubs, PEB access (x86/x64), API-hash loops, and stack-string construction, and
|
||||
infers architecture from the PEB-access form. Reads bytes statically; executes nothing.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
TECHNIQUES = {
|
||||
"getpc_call_pop": [b"\xe8\x00\x00\x00\x00"], # call $+5
|
||||
"getpc_fnstenv": [b"\xd9\xee\xd9\x74\x24\xf4"], # fldz; fnstenv [esp-0xc]
|
||||
"peb_access_x86": [b"\x64\xa1\x30\x00\x00\x00", b"\x64\x8b\x0d\x30", b"\x64\x8b\x15\x30"],
|
||||
"peb_access_x64": [b"\x65\x48\x8b", b"\x65\x4c\x8b"], # gs:[..]
|
||||
"api_hash_ror13": [b"\xac", b"\xc1\xcf\x0d", b"\xc1\xca\x0d"], # lodsb; ror reg,13
|
||||
"stack_strings": [b"\xc6\x44\x24", b"\xc7\x44\x24"], # mov byte/dword [esp+x], imm
|
||||
}
|
||||
|
||||
|
||||
def analyze(path: str) -> dict:
|
||||
data = Path(path).read_bytes()
|
||||
found = {name: True for name, sigs in TECHNIQUES.items()
|
||||
if any(s in data for s in sigs)}
|
||||
arch = ("x64" if "peb_access_x64" in found
|
||||
else "x86" if "peb_access_x86" in found else "unknown")
|
||||
return {
|
||||
"size": len(data),
|
||||
"techniques": sorted(found.keys()),
|
||||
"inferred_arch": arch,
|
||||
"uses_peb_walk": "peb_access_x86" in found or "peb_access_x64" in found,
|
||||
"uses_api_hashing": "api_hash_ror13" in found,
|
||||
"is_position_independent": bool(found),
|
||||
}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Analyze position-independent code")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("analyze")
|
||||
p.add_argument("file")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(analyze(args.file), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -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,113 @@
|
||||
---
|
||||
name: analyzing-ransomware-encryption-behavior
|
||||
description: 'Analyzes how a ransomware sample encrypts files: identifying the crypto scheme
|
||||
(symmetric/asymmetric/hybrid), key handling, file targeting and extension/marker changes,
|
||||
shadow-copy deletion, and ransom-note artifacts. Activates for requests to analyze
|
||||
ransomware encryption, assess decryptability, or study ransomware behavior.'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware
|
||||
- ransomware
|
||||
- cryptography
|
||||
- encryption
|
||||
- file-marker
|
||||
- impact
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1486
|
||||
- T1490
|
||||
- T1083
|
||||
- T1027
|
||||
d3fend:
|
||||
- D3-FA
|
||||
- D3-DA
|
||||
references:
|
||||
- 'No More Ransom project — https://www.nomoreransom.org/'
|
||||
- 'NIST cryptographic standards (AES/RSA) — https://csrc.nist.gov/'
|
||||
---
|
||||
|
||||
# Analyzing Ransomware Encryption Behavior
|
||||
|
||||
## When to Use
|
||||
|
||||
- You need to understand how a ransomware sample encrypts data and whether recovery without
|
||||
the key is feasible.
|
||||
- You are documenting file targeting, markers, and the ransom workflow for an IR report.
|
||||
- You want to identify the crypto primitives to assess decryptability and detection points.
|
||||
|
||||
**Do not use** this to attempt key recovery on production data without backups; analyze in the
|
||||
lab on disposable test files only.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Isolated lab with disposable test files of varied types/sizes.
|
||||
- Static/RE tools (see the Ghidra and crypto-identification skills) and the dynamic-analysis
|
||||
setup.
|
||||
- A clean snapshot to revert.
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Detonate only against **throwaway** files in the isolated victim VM; never real data.
|
||||
- Treat the sample and any dropped components as live; revert after each run.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Identify the crypto scheme statically
|
||||
|
||||
Locate crypto constants and APIs to classify the scheme:
|
||||
|
||||
```text
|
||||
Symmetric only (AES/ChaCha/RC4) -> key may be derivable/recoverable if mishandled
|
||||
Asymmetric (RSA/ECC) wrapping -> per-file symmetric key wrapped with attacker pubkey
|
||||
Hybrid (typical) -> AES per file, RSA-wrapped key in the encrypted file/footer
|
||||
```
|
||||
|
||||
Look for AES S-boxes, ChaCha constants ("expand 32-byte k"), and `CryptImportKey`/`BCrypt*`.
|
||||
|
||||
### Step 2: Trace key generation and handling
|
||||
|
||||
Determine where the symmetric key comes from (CSPRNG vs weak source), whether it is stored,
|
||||
and how it is protected. Weak/reused keys or keys left in memory are recovery opportunities.
|
||||
|
||||
### Step 3: Map file targeting
|
||||
|
||||
Identify which directories/extensions are targeted or skipped (allowlist of system paths),
|
||||
size thresholds, and partial-vs-full encryption (many encrypt only the first N bytes/blocks
|
||||
for speed).
|
||||
|
||||
### Step 4: Capture markers and metadata
|
||||
|
||||
Most families append an extension, write a per-file footer/magic marker, and store the
|
||||
wrapped key or IV there. The script fingerprints before/after test files to extract markers.
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py compare plain/ encrypted/
|
||||
```
|
||||
|
||||
### Step 5: Document recovery-relevant facts
|
||||
|
||||
Note shadow-copy/backup deletion (`vssadmin delete shadows`), the ransom note, and any
|
||||
cryptographic mistakes that affect decryptability.
|
||||
|
||||
## Validation
|
||||
|
||||
- The identified scheme matches observed file changes (e.g. hybrid → high-entropy body + a
|
||||
fixed-size footer).
|
||||
- File markers/extensions are consistent across encrypted test files.
|
||||
- Shadow-copy deletion and note-drop are confirmed dynamically.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Assuming decryptability from the algorithm name; correct AES/RSA usage is not recoverable.
|
||||
Recovery hinges on implementation flaws or leaked keys.
|
||||
- Missing partial-encryption schemes, leading to wrong impact assessment.
|
||||
- Running against real files instead of disposable test data.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the file-marker
|
||||
comparison tool.
|
||||
- No More Ransom and NIST crypto standards (linked in frontmatter).
|
||||
@@ -0,0 +1,41 @@
|
||||
# API Reference: Analyzing Ransomware Encryption Behavior
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` compares original test files against their encrypted counterparts to
|
||||
extract the appended extension, measure entropy change, and recover a common trailing footer
|
||||
(where families often store a wrapped key or IV). It runs on disposable test files only.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | Entropy, file IO |
|
||||
|
||||
## External tools
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| Ghidra / radare2 | Identify crypto routines and key handling |
|
||||
| Detect-It-Easy / capa | Crypto-constant detection |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `compare(plain_dir, enc_dir)`
|
||||
Returns added extensions, a common encrypted footer, per-file entropy, size deltas, and an
|
||||
encryption-coverage estimate (full vs partial).
|
||||
|
||||
### `_common_suffix(blobs)`
|
||||
Finds the longest shared trailing bytes across encrypted files (candidate footer/marker).
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py compare ./plain ./encrypted
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- No More Ransom project — https://www.nomoreransom.org/
|
||||
- NIST cryptographic standards (AES, RSA) — https://csrc.nist.gov/
|
||||
- Hybrid-encryption ransomware design (per-file AES + RSA-wrapped key), widely documented
|
||||
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compare plaintext vs ransomware-encrypted test files to extract markers and assess scheme.
|
||||
|
||||
Given a directory of original test files and a directory of their encrypted counterparts, it
|
||||
reports added extensions, entropy change, and any common trailing footer/magic (often where a
|
||||
wrapped key or IV is stored). Operates on inert test files only.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
import sys
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def entropy(data: bytes) -> float:
|
||||
if not data:
|
||||
return 0.0
|
||||
counts = Counter(data)
|
||||
n = len(data)
|
||||
return -sum((c / n) * math.log2(c / n) for c in counts.values())
|
||||
|
||||
|
||||
def _common_suffix(blobs: list[bytes], max_len: int = 512) -> bytes:
|
||||
if not blobs:
|
||||
return b""
|
||||
shortest = min(len(b) for b in blobs)
|
||||
limit = min(shortest, max_len)
|
||||
suffix = bytearray()
|
||||
for i in range(1, limit + 1):
|
||||
col = {b[-i] for b in blobs}
|
||||
if len(col) == 1:
|
||||
suffix.insert(0, col.pop())
|
||||
else:
|
||||
break
|
||||
return bytes(suffix)
|
||||
|
||||
|
||||
def compare(plain_dir: str, enc_dir: str) -> dict:
|
||||
plain = {p.stem: p for p in Path(plain_dir).iterdir() if p.is_file()}
|
||||
enc = [p for p in Path(enc_dir).iterdir() if p.is_file()]
|
||||
|
||||
added_ext = sorted({"".join(p.suffixes) for p in enc})
|
||||
enc_blobs = [p.read_bytes() for p in enc]
|
||||
footer = _common_suffix(enc_blobs)
|
||||
|
||||
per_file = []
|
||||
for p in enc:
|
||||
base = p.name
|
||||
# strip a possible appended extension to match the plaintext stem
|
||||
stem = p.stem
|
||||
orig = plain.get(stem)
|
||||
row = {
|
||||
"encrypted": base,
|
||||
"encrypted_entropy": round(entropy(p.read_bytes()), 3),
|
||||
}
|
||||
if orig:
|
||||
row["original"] = orig.name
|
||||
row["original_entropy"] = round(entropy(orig.read_bytes()), 3)
|
||||
row["size_delta"] = p.stat().st_size - orig.stat().st_size
|
||||
per_file.append(row)
|
||||
|
||||
likely_full = all(r.get("encrypted_entropy", 0) > 7.5 for r in per_file)
|
||||
return {
|
||||
"added_extensions": added_ext,
|
||||
"common_footer_len": len(footer),
|
||||
"common_footer_hex": footer.hex(),
|
||||
"encryption_coverage": "full/high-entropy" if likely_full else "partial/mixed",
|
||||
"files": per_file,
|
||||
}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Compare plain vs encrypted test files")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("compare")
|
||||
p.add_argument("plain_dir")
|
||||
p.add_argument("enc_dir")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(compare(args.plain_dir, args.enc_dir), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -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,89 @@
|
||||
---
|
||||
name: analyzing-rat-command-and-control
|
||||
description: 'Analyzes remote access trojan command-and-control by mapping the RAT command set,
|
||||
identifying the C2 transport and beacon parameters, and cataloging operator capabilities from
|
||||
static strings and config. Activates for requests to analyze RAT C2, map a trojan command
|
||||
handler, or identify remote-access capabilities and C2 transport.'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware-analysis
|
||||
- rat
|
||||
- command-and-control
|
||||
- c2
|
||||
- capability-mapping
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1219
|
||||
- T1071
|
||||
- T1105
|
||||
d3fend:
|
||||
- D3-NTA
|
||||
- D3-SDA
|
||||
references:
|
||||
- 'MITRE ATT&CK T1219 Remote Access Software — https://attack.mitre.org/techniques/T1219/'
|
||||
- 'MITRE ATT&CK T1071 Application Layer Protocol — https://attack.mitre.org/techniques/T1071/'
|
||||
---
|
||||
|
||||
# Analyzing RAT Command and Control
|
||||
|
||||
## When to Use
|
||||
|
||||
- You have a RAT sample and need to map its command set (shell, file ops, screenshot, keylog,
|
||||
webcam, proxy), C2 transport, and beacon parameters.
|
||||
- You are cataloging operator capabilities for detection and impact assessment.
|
||||
|
||||
**Do not use** this to operate the RAT or connect to its C2 — characterize capabilities from inert
|
||||
static analysis.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- The RAT sample (read inertly), optionally with an extracted config.
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Read bytes statically; defang C2 endpoints; never connect to the C2.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Map command handlers and capabilities
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py profile sample.bin
|
||||
```
|
||||
|
||||
Matches command-keyword and API patterns for capability classes: remote shell, file transfer,
|
||||
screenshot/keylog/webcam, persistence, proxy/relay, and self-update.
|
||||
|
||||
### Step 2: Identify the C2 transport
|
||||
|
||||
Detect HTTP(S), raw TCP, TLS, DNS, or messaging-platform transport from imports/strings, plus
|
||||
beacon intervals/user-agents where present.
|
||||
|
||||
### Step 3: Build the capability matrix
|
||||
|
||||
Summarize capabilities and transport, mapping to ATT&CK.
|
||||
|
||||
### Step 4: Defang and report
|
||||
|
||||
Defang endpoints and produce IOCs.
|
||||
|
||||
## Validation
|
||||
|
||||
- Capabilities are grouped into classes with the matched evidence.
|
||||
- The C2 transport is identified from concrete imports/strings.
|
||||
- Findings map to ATT&CK T1219/T1071.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Generic API presence (e.g., `gdi32`) misread as screenshot capability — corroborate with command
|
||||
handlers.
|
||||
- Commodity RAT builders sharing strings across families — avoid over-attribution.
|
||||
- Encrypted command sets requiring config decryption first.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the profiler.
|
||||
- ATT&CK T1219 and T1071 (linked in frontmatter).
|
||||
@@ -0,0 +1,37 @@
|
||||
# API Reference: Analyzing RAT Command and Control
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` extracts strings from a RAT and maps them to capability classes (remote shell,
|
||||
file ops, surveillance, persistence, proxy/relay, self-update, credential theft) and C2 transports
|
||||
(HTTP, raw TCP, TLS, DNS, messaging), defanging C2 URLs. It reads bytes statically and executes
|
||||
nothing.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | regex, JSON, file IO |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `profile(path)`
|
||||
Returns grouped capabilities, C2 transports, defanged C2 URLs, and a RAT-likelihood flag
|
||||
(≥3 capability classes).
|
||||
|
||||
## Capability classes
|
||||
|
||||
remote_shell, file_operations, surveillance (screenshot/keylog/webcam/audio), persistence,
|
||||
proxy_relay, self_update, credential_theft.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py profile sample.bin
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- MITRE ATT&CK T1219 Remote Access Software — https://attack.mitre.org/techniques/T1219/
|
||||
- MITRE ATT&CK T1071 Application Layer Protocol — https://attack.mitre.org/techniques/T1071/
|
||||
- RAT capability/command-handler patterns from public family analyses
|
||||
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Profile a RAT's capabilities and C2 transport from static strings.
|
||||
|
||||
Extracts ASCII/UTF-16LE strings and maps them to capability classes (remote shell, file ops,
|
||||
surveillance, persistence, proxy, self-update) and C2 transports (HTTP, TCP, TLS, DNS, messaging).
|
||||
Reads bytes statically, defangs endpoints, and executes nothing.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ASCII_RE = re.compile(rb"[\x20-\x7e]{4,}")
|
||||
UTF16_RE = re.compile(rb"(?:[\x20-\x7e]\x00){4,}")
|
||||
RE_URL = re.compile(r"https?://[^\s\"'<>]+", re.I)
|
||||
|
||||
CAPABILITIES = {
|
||||
"remote_shell": ["cmd.exe", "/c ", "createprocess", "shell", "winexec", "system32\\cmd"],
|
||||
"file_operations": ["upload", "download", "getfile", "putfile", "createfile", "deletefile",
|
||||
"findfirstfile"],
|
||||
"surveillance": ["screenshot", "bitblt", "getdc", "keylog", "getasynckeystate",
|
||||
"setwindowshook", "webcam", "capgetdriverdescription", "waveinopen"],
|
||||
"persistence": ["currentversion\\run", "schtasks", "createservice", "startup"],
|
||||
"proxy_relay": ["socks", "proxy", "relay", "portforward", "reverse"],
|
||||
"self_update": ["update", "selfdelete", "rename", "newversion"],
|
||||
"credential_theft": ["cryptunprotectdata", "vault", "password", "credential"],
|
||||
}
|
||||
|
||||
TRANSPORTS = {
|
||||
"http": ["wininet", "winhttp", "httpsendrequest", "internetopen", "user-agent"],
|
||||
"tcp_raw": ["ws2_32", "wsasocket", "connect", "recv", "send"],
|
||||
"tls": ["schannel", "ssl_", "tls", "openssl"],
|
||||
"dns": ["dnsquery", "dns_", "txt record"],
|
||||
"messaging": ["api.telegram.org", "discord", "pastebin", "steamcommunity"],
|
||||
}
|
||||
|
||||
|
||||
def _strings(data: bytes) -> str:
|
||||
parts = [m.decode("ascii", "ignore") for m in ASCII_RE.findall(data)]
|
||||
parts += [m.decode("utf-16-le", "ignore") for m in UTF16_RE.findall(data)]
|
||||
return "\n".join(parts)
|
||||
|
||||
|
||||
def _defang(s: str) -> str:
|
||||
return s.replace("http", "hxxp").replace(".", "[.]")
|
||||
|
||||
|
||||
def profile(path: str) -> dict:
|
||||
blob = _strings(Path(path).read_bytes())
|
||||
low = blob.lower()
|
||||
caps = {c: sorted({n for n in needles if n in low})
|
||||
for c, needles in CAPABILITIES.items()}
|
||||
caps = {c: hits for c, hits in caps.items() if hits}
|
||||
transports = {t: sorted({n for n in needles if n in low})
|
||||
for t, needles in TRANSPORTS.items()}
|
||||
transports = {t: hits for t, hits in transports.items() if hits}
|
||||
urls = sorted({_defang(u) for u in RE_URL.findall(blob)})[:50]
|
||||
return {
|
||||
"capabilities": caps,
|
||||
"c2_transports": transports,
|
||||
"c2_urls": urls,
|
||||
"capability_count": len(caps),
|
||||
"is_likely_rat": len(caps) >= 3,
|
||||
}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Profile RAT C2/capabilities")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("profile")
|
||||
p.add_argument("file")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(profile(args.file), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user