reverse-skills-max: initial public release (restored, redacted, hardened)

This commit is contained in:
baradok609hecl1pq6qi
2026-10-20 02:17:15 +03:00
commit da24c2bd20
2227 changed files with 443931 additions and 0 deletions

View File

@@ -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

View File

@@ -0,0 +1,102 @@
---
name: defeating-string-and-api-obfuscation
description: 'Recovers obfuscated strings and resolves dynamically loaded APIs in malware:
reversing XOR/stack-string/encoding schemes and mapping API-hash lookups back to function
names. Activates for requests to deobfuscate strings, decode stack strings, or resolve
API hashing in a binary.'
domain: cybersecurity
subdomain: reverse-engineering
tags:
- reverse-engineering
- obfuscation
- strings
- api-hashing
- xor
- deobfuscation
version: 1.0.0
author: analyst-ai-pack
license: Apache-2.0
mitre_attack:
- T1027
- T1140
- T1106
d3fend:
- D3-FCR
- D3-FA
references:
- 'FLOSS (FLARE Obfuscated String Solver) — https://github.com/mandiant/flare-floss'
- 'API hashing techniques (ROR13 / djb2) — https://learn.microsoft.com/windows/win32/api/'
---
# Defeating String and API Obfuscation
## When to Use
- A binary has few readable strings because they are XOR/stack-encoded or built at runtime.
- The import table is sparse because APIs are resolved by hash at runtime.
- You need to recover C2 URLs, paths, and the real API set to understand behavior.
**Do not use** plain `strings` and conclude "no indicators" — modern malware hides strings;
absence of readable strings is itself a sign of obfuscation.
## Prerequisites
- A disassembler/decompiler to read the decode routine, plus the bundled XOR/hash tooling.
- Optionally FLOSS for automated stack/decoded-string recovery.
## Workflow
### Step 1: Recognize the obfuscation type
```text
Stack strings : bytes mov'd to the stack one/few at a time, then used
Single-byte XOR: a loop XORing a buffer with a constant
Multi-byte/RC4 : a keyed stream over a blob
API hashing : a hash compared against export-name hashes to resolve functions
```
### Step 2: Recover XOR-encoded strings
If you find the key and ciphertext, decode directly. The script brute-forces single-byte XOR
and surfaces readable results:
```bash
python scripts/analyst.py xor-strings sample.bin
```
### Step 3: Reconstruct stack strings
Read the decompiler to collect the byte sequence assembled on the stack and reassemble it. For
volume, FLOSS emulates and extracts these automatically.
### Step 4: Resolve API hashing
Identify the hash algorithm (often ROR13/ROR7 additive, or djb2). Precompute hashes for known
export names and match the constants in the binary back to functions:
```bash
python scripts/analyst.py api-hash --algo ror13 --hash 0x726774C
```
### Step 5: Reannotate and extract IOCs
Apply recovered strings and API names back in the disassembler and extract the now-visible
URLs, paths, and behavior.
## Validation
- Decoded strings are meaningful (URLs, DLL/API names, paths), not random bytes.
- Resolved API names match the calls' usage in the surrounding code.
- The recovered API set explains behavior seen dynamically.
## Pitfalls
- Assuming single-byte XOR when it is keyed/RC4; check key length and the decode loop.
- Using the wrong hash algorithm/seed and getting no matches — verify against a known function.
- Recovering strings but not feeding them back into the analysis.
## References
- See [`references/api-reference.md`](references/api-reference.md) for the XOR and API-hash
tools.
- FLOSS and API-hashing references (linked in frontmatter).

View File

@@ -0,0 +1,45 @@
# API Reference: Defeating String and API Obfuscation
## Overview
`scripts/analyst.py` provides two deobfuscation utilities: brute-forcing single-byte XOR to
surface readable strings, and resolving API-name hashes (ROR13/ROR7/djb2) back to function
names. Both read input only and never execute the sample.
## Dependencies
| Package | Version | Purpose |
|---------|---------|---------|
| (stdlib) | — | regex, hashing arithmetic |
## External tools
| Tool | Purpose |
|------|---------|
| FLOSS | Automated stack-string and decoded-string recovery via emulation |
| Ghidra / IDA | Read the decode routine and reapply recovered names |
## Core functions
### `xor_strings(data, min_hits)`
Brute-forces single-byte XOR keys and returns keys that yield several interesting readable
strings (URLs, DLL/API names, paths).
### `resolve_hash(algo, target, names)`
Returns candidate API names whose hash (ROR13/ROR7/djb2) equals the target constant.
### `hash_ror13` / `hash_ror7` / `hash_djb2`
Implementations of the common API-hashing algorithms.
## Usage
```bash
python scripts/analyst.py xor-strings sample.bin
python scripts/analyst.py api-hash --algo ror13 --hash 0x726774C
```
## Sources
- FLOSS (FLARE Obfuscated String Solver) — https://github.com/mandiant/flare-floss
- ROR13 API-hashing technique (widely used in shellcode/loaders)
- djb2 string hash (Daniel J. Bernstein)

View File

@@ -0,0 +1,108 @@
#!/usr/bin/env python3
"""Recover XOR-obfuscated strings and resolve API hashes.
Two original utilities for deobfuscation triage:
- ``xor-strings``: brute-force single-byte XOR over a file and surface readable strings.
- ``api-hash``: compute common API-name hashes (ROR13/ROR7/djb2) and match a target constant
against a provided or built-in list of export names.
Reads bytes/text only; never executes the sample.
"""
from __future__ import annotations
import argparse
import json
import re
import sys
from pathlib import Path
_READABLE = re.compile(rb"[\x20-\x7e]{5,}")
COMMON_APIS = [
"LoadLibraryA", "GetProcAddress", "VirtualAlloc", "VirtualProtect",
"WriteProcessMemory", "CreateRemoteThread", "CreateProcessA", "WinExec",
"InternetOpenA", "InternetConnectA", "HttpSendRequestA", "URLDownloadToFileA",
"RegSetValueExA", "CreateFileA", "WriteFile", "ReadFile", "CloseHandle",
"GetProcAddress", "ExitProcess", "Sleep", "WSAStartup", "connect", "send", "recv",
]
def _ror(val: int, bits: int, width: int = 32) -> int:
mask = (1 << width) - 1
val &= mask
return ((val >> bits) | (val << (width - bits))) & mask
def hash_ror13(name: str) -> int:
h = 0
for ch in name.encode() + b"\x00":
h = (_ror(h, 13) + ch) & 0xFFFFFFFF
return h
def hash_ror7(name: str) -> int:
h = 0
for ch in name.encode():
h = (_ror(h, 7) + ch) & 0xFFFFFFFF
return h
def hash_djb2(name: str) -> int:
h = 5381
for ch in name.encode():
h = ((h * 33) + ch) & 0xFFFFFFFF
return h
_ALGOS = {"ror13": hash_ror13, "ror7": hash_ror7, "djb2": hash_djb2}
def xor_strings(data: bytes, min_hits: int = 3) -> list[dict]:
results = []
for key in range(1, 256):
decoded = bytes(b ^ key for b in data)
hits = [m.group(0).decode("ascii", "replace")
for m in _READABLE.finditer(decoded)]
interesting = [s for s in hits
if re.search(r"https?://|\.dll|\.exe|\\|[A-Z][a-z]+[A-Z]", s)]
if len(interesting) >= min_hits:
results.append({"key": hex(key), "samples": interesting[:20],
"count": len(interesting)})
return sorted(results, key=lambda r: r["count"], reverse=True)[:5]
def resolve_hash(algo: str, target: int, names: list[str]) -> list[str]:
fn = _ALGOS[algo]
return [n for n in names if fn(n) == (target & 0xFFFFFFFF)]
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Defeat string/API obfuscation")
sub = parser.add_subparsers(dest="cmd", required=True)
p_x = sub.add_parser("xor-strings", help="brute single-byte XOR for readable strings")
p_x.add_argument("file")
p_h = sub.add_parser("api-hash", help="match an API hash to a function name")
p_h.add_argument("--algo", choices=sorted(_ALGOS), required=True)
p_h.add_argument("--hash", required=True, help="target hash, e.g. 0x726774C")
p_h.add_argument("--names", help="optional file of candidate API names (one per line)")
args = parser.parse_args(argv)
if args.cmd == "xor-strings":
data = Path(args.file).read_bytes()
print(json.dumps(xor_strings(data), indent=2))
return 0
names = COMMON_APIS
if args.names:
names = [n.strip() for n in Path(args.names).read_text().splitlines() if n.strip()]
target = int(args.hash, 0)
matches = resolve_hash(args.algo, target, names)
print(json.dumps({"algo": args.algo, "hash": hex(target),
"matches": matches}, indent=2))
return 0
if __name__ == "__main__":
sys.exit(main())