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,90 @@
|
||||
---
|
||||
name: extracting-and-classifying-strings
|
||||
description: 'Extracts ASCII and Unicode strings from a binary and classifies them into
|
||||
investigative categories: URLs, IPs, file paths, registry keys, mutexes, commands, and API
|
||||
names, prioritizing the analyst''s attention. Activates for requests to extract strings,
|
||||
classify strings output, or pull human-readable artifacts from a sample.'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware-analysis
|
||||
- strings
|
||||
- static-analysis
|
||||
- triage
|
||||
- ioc
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1027
|
||||
d3fend:
|
||||
- D3-FCR
|
||||
- D3-DA
|
||||
references:
|
||||
- 'GNU binutils strings — https://www.gnu.org/software/binutils/'
|
||||
- 'FLOSS (FLARE Obfuscated String Solver) — https://github.com/mandiant/flare-floss'
|
||||
---
|
||||
|
||||
# Extracting and Classifying Strings
|
||||
|
||||
## When to Use
|
||||
|
||||
- You are triaging a sample and want quick investigative leads from its readable strings.
|
||||
- You need both ASCII and UTF-16LE (Unicode) strings, which many tools miss by default.
|
||||
- You want strings grouped by meaning (URLs, paths, registry, commands) rather than a flat dump.
|
||||
|
||||
**Do not use** plaintext strings as the whole story for packed/obfuscated samples — the useful
|
||||
strings may be encrypted; pair with entropy checks and deobfuscation (FLOSS) when the dump is
|
||||
sparse.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python (stdlib) or `strings`/FLOSS; the sample handled inertly in the lab.
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Extract strings from the inert file; never execute the sample.
|
||||
- Defang any URLs/IPs before putting them in a report or ticket.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Extract ASCII and Unicode strings
|
||||
|
||||
Pull printable runs at a minimum length (default 4–5) for both ASCII and UTF-16LE encodings to
|
||||
avoid missing Windows wide strings.
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py strings sample.bin --min-len 5
|
||||
```
|
||||
|
||||
### Step 2: Classify by category
|
||||
|
||||
Bucket strings into URLs, IPs, domains, file paths, registry keys, mutex/event names, suspicious
|
||||
commands, and API names so leads surface immediately.
|
||||
|
||||
### Step 3: Prioritize leads
|
||||
|
||||
Promote network indicators, command lines, and suspicious API references to the top; defang
|
||||
network artifacts for safe handling.
|
||||
|
||||
### Step 4: Recognize obfuscation
|
||||
|
||||
A near-empty or junk-only dump signals packing/string obfuscation; route to entropy analysis and
|
||||
FLOSS/manual deobfuscation.
|
||||
|
||||
## Validation
|
||||
|
||||
- Both ASCII and Unicode strings are extracted (Windows wide strings are not missed).
|
||||
- Strings are grouped into useful categories, not a flat list.
|
||||
- A sparse/garbage dump is correctly read as an obfuscation signal, not "clean".
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Extracting ASCII only and missing UTF-16LE strings.
|
||||
- Treating a packed sample's empty dump as benign.
|
||||
- Reporting live URLs/IPs without defanging.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the string extractor.
|
||||
- binutils strings and FLOSS (linked in frontmatter).
|
||||
@@ -0,0 +1,37 @@
|
||||
# API Reference: Extracting and Classifying Strings
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` extracts ASCII and UTF-16LE strings at a minimum length and classifies them
|
||||
into investigative categories (urls, ips, paths, registry, commands, apis), flagging a sparse
|
||||
dump as a possible obfuscation signal. It reads bytes only and never executes the sample.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | regex, JSON, file IO |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `ascii_strings(data, min_len)` / `unicode_strings(data, min_len)`
|
||||
Extract printable ASCII and UTF-16LE runs respectively.
|
||||
|
||||
### `classify(strings)`
|
||||
Buckets strings into categories via per-category regexes.
|
||||
|
||||
### `run(path, min_len)`
|
||||
Returns counts, categorized strings, and an obfuscation hint.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py strings sample.bin --min-len 5
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- GNU binutils `strings` — https://www.gnu.org/software/binutils/
|
||||
- FLOSS (FLARE Obfuscated String Solver) for stacked/encoded strings —
|
||||
https://github.com/mandiant/flare-floss
|
||||
- UTF-16LE wide-string extraction (printable byte + 0x00), standard Windows-analysis practice
|
||||
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Extract ASCII and UTF-16LE strings from a binary and classify them.
|
||||
|
||||
Scans the inert file for printable ASCII runs and UTF-16LE runs at a minimum length, then buckets
|
||||
results into investigative categories (urls, ips, paths, registry, mutex/event, commands, apis).
|
||||
It reads bytes only and never executes the sample.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
PRINTABLE = rb"[\x20-\x7e]"
|
||||
|
||||
|
||||
def ascii_strings(data: bytes, min_len: int) -> list[str]:
|
||||
pat = re.compile(PRINTABLE + b"{%d,}" % min_len)
|
||||
return [m.decode("latin-1") for m in pat.findall(data)]
|
||||
|
||||
|
||||
def unicode_strings(data: bytes, min_len: int) -> list[str]:
|
||||
# UTF-16LE: printable byte followed by 0x00, repeated
|
||||
pat = re.compile(b"(?:" + PRINTABLE + b"\x00){%d,}" % min_len)
|
||||
out = []
|
||||
for m in pat.findall(data):
|
||||
out.append(m.decode("utf-16-le", errors="replace"))
|
||||
return out
|
||||
|
||||
|
||||
CATEGORIES = {
|
||||
"urls": re.compile(r"https?://[^\s\"'<>]+", re.I),
|
||||
"ips": re.compile(r"\b(?:\d{1,3}\.){3}\d{1,3}\b"),
|
||||
"paths": re.compile(r"[a-zA-Z]:\\[^\s\"'<>|]+|/(?:usr|etc|tmp|var|home)/[^\s\"'<>|]+"),
|
||||
"registry": re.compile(r"(?:HKLM|HKCU|HKEY_[A-Z_]+)\\[^\s\"'<>|]+", re.I),
|
||||
"commands": re.compile(r"\b(?:cmd\.exe|powershell|rundll32|regsvr32|schtasks|net\s+user)\b",
|
||||
re.I),
|
||||
"apis": re.compile(r"\b(?:VirtualAlloc|WriteProcessMemory|CreateRemoteThread|LoadLibrary|"
|
||||
r"GetProcAddress|WinExec|URLDownloadToFile)\w*", re.I),
|
||||
}
|
||||
|
||||
|
||||
def classify(strings: list[str]) -> dict:
|
||||
buckets: dict[str, set] = {k: set() for k in CATEGORIES}
|
||||
for s in strings:
|
||||
for cat, rx in CATEGORIES.items():
|
||||
for m in rx.findall(s):
|
||||
buckets[cat].add(m)
|
||||
return {k: sorted(v) for k, v in buckets.items() if v}
|
||||
|
||||
|
||||
def run(path: str, min_len: int) -> dict:
|
||||
data = Path(path).read_bytes()
|
||||
a = ascii_strings(data, min_len)
|
||||
u = unicode_strings(data, min_len)
|
||||
allstr = a + u
|
||||
return {
|
||||
"ascii_count": len(a),
|
||||
"unicode_count": len(u),
|
||||
"categorized": classify(allstr),
|
||||
"obfuscation_hint": len(allstr) < 20,
|
||||
}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Extract and classify strings")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("strings")
|
||||
p.add_argument("file")
|
||||
p.add_argument("--min-len", type=int, default=5)
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(run(args.file, args.min_len), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user