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,88 @@
---
name: resolving-dynamic-api-hashing
description: 'Resolves dynamically hashed Windows API names by brute-forcing observed hash constants
against a wordlist of API/DLL names using common malware hashing algorithms (ROR13, djb2, FNV,
CRC32). Activates for requests to resolve API hashes, identify hashed imports, or reverse an API
hashing routine.'
domain: cybersecurity
subdomain: reverse-engineering
tags:
- reverse-engineering
- api-hashing
- obfuscation
- imports
- deobfuscation
version: 1.0.0
author: analyst-ai-pack
license: Apache-2.0
mitre_attack:
- T1027.007
- T1106
- T1140
d3fend:
- D3-SDA
- D3-DA
references:
- 'MITRE ATT&CK T1027.007 Dynamic API Resolution — https://attack.mitre.org/techniques/T1027/007/'
- 'Windows API Index — https://learn.microsoft.com/windows/win32/apiindex/windows-api-list'
---
# Resolving Dynamic API Hashing
## When to Use
- A sample resolves APIs by hash (no plaintext import names) and you have the hash constants from
disassembly.
- You need to map each hash back to an API/DLL name to understand functionality.
**Do not use** this to execute the resolver — it computes hashes over a name wordlist statically
and matches them to your observed constants.
## Prerequisites
- The hash constants observed in the binary and (optionally) a names wordlist.
## Workflow
### Step 1: Identify the algorithm
```bash
python scripts/analyst.py id 0x6A4ABC5B
```
Computes the constant under each supported algorithm against a built-in seed list to suggest which
algorithm/seed reproduces known API hashes.
### Step 2: Resolve hashes against a wordlist
```bash
python scripts/analyst.py resolve hashes.txt --algo ror13 --wordlist names.txt
```
Brute-forces the provided hashes against the wordlist using the chosen algorithm and reports
matches.
### Step 3: Annotate the disassembly
Label resolved call sites with their API names to recover behavior.
### Step 4: Document the routine
Record the algorithm, seed/initial value, and any key so the routine is reusable.
## Validation
- The chosen algorithm reproduces at least one known API hash (sanity anchor).
- Resolved names are real exported symbols of plausible DLLs.
- Unresolved hashes are reported, not silently dropped.
## Pitfalls
- Wrong endianness/rotation direction producing no matches — try variants.
- Algorithms seeded with a per-sample key; without the key, matches fail.
- Case sensitivity: some routines hash uppercased names, others as-is.
## References
- See [`references/api-reference.md`](references/api-reference.md) for the resolver.
- ATT&CK T1027.007 and the Windows API Index (linked in frontmatter).

View File

@@ -0,0 +1,37 @@
# API Reference: Resolving Dynamic API Hashing
## Overview
`scripts/analyst.py` implements common malware API-hash algorithms (ROR13-add, djb2, FNV-1a, CRC32)
and matches observed 32-bit hash constants against a names wordlist. It is pure computation over
name strings and never loads or executes the sample.
## Dependencies
| Package | Version | Purpose |
|---------|---------|---------|
| (stdlib) | — | binascii (CRC32), JSON, file IO |
## Core functions
### `ror13_add(name)`, `djb2(name)`, `fnv1a(name)`, `crc32(name)`
The supported 32-bit hashing algorithms.
### `identify(value)`
Tries each algorithm against a small seed API list to suggest which algorithm a constant came from.
### `resolve(hashes, algo, names)`
Builds a hash→name table for the wordlist and resolves the provided hashes; reports unresolved.
## Usage
```bash
python scripts/analyst.py id 0x6A4ABC5B
python scripts/analyst.py resolve hashes.txt --algo ror13 --wordlist names.txt
```
## Sources
- MITRE ATT&CK T1027.007 Dynamic API Resolution — https://attack.mitre.org/techniques/T1027/007/
- ROR13 PEB-walk shellcode hashing pattern (public shellcode/RE literature)
- djb2 (Daniel J. Bernstein), FNV-1a (Fowler/Noll/Vo), CRC-32 (ISO 3309) standard definitions

View File

@@ -0,0 +1,108 @@
#!/usr/bin/env python3
"""Resolve dynamically hashed API names by brute-forcing hash constants over a wordlist.
Implements common malware API-hash algorithms (ROR13 add, djb2, FNV-1a, CRC32) and matches a list
of observed 32-bit hash constants against a names wordlist. Pure computation over names; never
loads or executes the sample.
"""
from __future__ import annotations
import argparse
import binascii
import json
import sys
from pathlib import Path
DEFAULT_NAMES = [
"LoadLibraryA", "GetProcAddress", "VirtualAlloc", "VirtualAllocEx", "VirtualProtect",
"WriteProcessMemory", "CreateRemoteThread", "CreateProcessA", "WinExec", "ExitProcess",
"GetModuleHandleA", "CreateFileA", "WriteFile", "ReadFile", "InternetOpenA",
"InternetConnectA", "HttpSendRequestA", "URLDownloadToFileA", "RegSetValueExA",
"CryptDecrypt", "kernel32.dll", "ntdll.dll", "wininet.dll", "advapi32.dll",
]
def ror32(v: int, n: int) -> int:
return ((v >> n) | (v << (32 - n))) & 0xFFFFFFFF
def ror13_add(name: str) -> int:
h = 0
for ch in name.encode("ascii", "ignore"):
h = ror32(h, 13)
h = (h + ch) & 0xFFFFFFFF
return h
def djb2(name: str) -> int:
h = 5381
for ch in name.encode("ascii", "ignore"):
h = ((h * 33) + ch) & 0xFFFFFFFF
return h
def fnv1a(name: str) -> int:
h = 0x811C9DC5
for ch in name.encode("ascii", "ignore"):
h = ((h ^ ch) * 0x01000193) & 0xFFFFFFFF
return h
def crc32(name: str) -> int:
return binascii.crc32(name.encode("ascii", "ignore")) & 0xFFFFFFFF
ALGOS = {"ror13": ror13_add, "djb2": djb2, "fnv1a": fnv1a, "crc32": crc32}
def identify(value: int) -> dict:
matches = {}
for algo, fn in ALGOS.items():
for name in DEFAULT_NAMES:
if fn(name) == value:
matches.setdefault(algo, []).append(name)
return {"value": hex(value), "matches": matches,
"note": "matched against a small seed list; use resolve with a full wordlist"}
def resolve(hashes: list[int], algo: str, names: list[str]) -> dict:
fn = ALGOS[algo]
table = {fn(n): n for n in names}
resolved = {hex(h): table.get(h) for h in hashes}
return {
"algorithm": algo,
"resolved": {k: v for k, v in resolved.items() if v},
"unresolved": [k for k, v in resolved.items() if not v],
}
def _read_hashes(path: str) -> list[int]:
out = []
for line in Path(path).read_text(encoding="utf-8-sig").splitlines():
line = line.strip()
if line:
out.append(int(line, 0))
return out
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Resolve API hashes")
sub = parser.add_subparsers(dest="cmd", required=True)
pid = sub.add_parser("id")
pid.add_argument("value")
pr = sub.add_parser("resolve")
pr.add_argument("hashes_file")
pr.add_argument("--algo", choices=list(ALGOS), required=True)
pr.add_argument("--wordlist")
args = parser.parse_args(argv)
if args.cmd == "id":
print(json.dumps(identify(int(args.value, 0)), indent=2))
else:
names = (Path(args.wordlist).read_text(encoding="utf-8-sig").split()
if args.wordlist else DEFAULT_NAMES)
print(json.dumps(resolve(_read_hashes(args.hashes_file), args.algo, names), indent=2))
return 0
if __name__ == "__main__":
sys.exit(main())