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,85 @@
|
||||
---
|
||||
name: defeating-control-flow-flattening
|
||||
description: 'Defeats control-flow-flattening obfuscation by identifying the dispatcher/state-
|
||||
variable structure and reconstructing the original control flow so the logic becomes readable.
|
||||
Activates for requests to defeat control-flow flattening, deobfuscate an OLLVM-flattened function,
|
||||
or recover original control flow from a dispatcher loop.'
|
||||
domain: cybersecurity
|
||||
subdomain: reverse-engineering
|
||||
tags:
|
||||
- reverse-engineering
|
||||
- obfuscation
|
||||
- control-flow-flattening
|
||||
- ollvm
|
||||
- deobfuscation
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1027
|
||||
- T1027.013
|
||||
- T1140
|
||||
d3fend:
|
||||
- D3-DA
|
||||
- D3-SDA
|
||||
references:
|
||||
- 'OLLVM control-flow flattening (Obfuscator-LLVM) — https://github.com/obfuscator-llvm/obfuscator'
|
||||
- 'MITRE ATT&CK T1027 Obfuscated Files or Information — https://attack.mitre.org/techniques/T1027/'
|
||||
---
|
||||
|
||||
# Defeating Control-Flow Flattening
|
||||
|
||||
## When to Use
|
||||
|
||||
- A function is flattened (a dispatcher loop switching on a state variable, with original blocks as
|
||||
switch cases) and you need to recover the original control flow.
|
||||
- You are reversing OLLVM-style or similar flattening.
|
||||
|
||||
**Do not use** this on non-flattened code — confirm the dispatcher/state-variable pattern first.
|
||||
This skill analyzes disassembly/CFG data statically and executes nothing.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A disassembled function's basic-block/edge data (e.g., exported from Ghidra/IDA/Binary Ninja as
|
||||
JSON).
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Detect the flattening structure
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py detect cfg.json
|
||||
```
|
||||
|
||||
Identifies a dispatcher block (high in-degree, switch/compare on a state variable) and the relay/
|
||||
case blocks that all branch back to it.
|
||||
|
||||
### Step 2: Recover state transitions
|
||||
|
||||
Track the constant assigned to the state variable in each case to determine the successor block,
|
||||
rebuilding the true edges.
|
||||
|
||||
### Step 3: Reconstruct the original CFG
|
||||
|
||||
Emit the deflattened successor mapping (case → next case) for re-annotation in the disassembler.
|
||||
|
||||
### Step 4: Verify
|
||||
|
||||
Confirm the recovered flow has a single entry, sane successors, and no orphan blocks.
|
||||
|
||||
## Validation
|
||||
|
||||
- The dispatcher is identified by in-degree and state-variable comparison.
|
||||
- Each case's next-state constant maps to a real successor block.
|
||||
- The reconstructed CFG covers all original (non-relay) blocks.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- State variable computed (not a constant) — requires emulation/symbolic execution.
|
||||
- Multiple dispatchers or nested flattening.
|
||||
- Opaque predicates and bogus blocks inflating the case set.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the deflattener.
|
||||
- OLLVM and ATT&CK T1027 references (linked in frontmatter).
|
||||
@@ -0,0 +1,49 @@
|
||||
# API Reference: Defeating Control-Flow Flattening
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` consumes a function CFG (basic blocks with successors, in-degree, state-compare
|
||||
flag, and next-state constants) and reconstructs the original control flow by identifying the
|
||||
dispatcher and mapping each case's next-state to its successor. It parses JSON only and executes
|
||||
nothing.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | JSON, argparse, pathlib |
|
||||
|
||||
## Input format
|
||||
|
||||
```json
|
||||
{
|
||||
"state_var": "eax",
|
||||
"entry": "A",
|
||||
"blocks": [
|
||||
{"id": "D", "succ": ["B","C"], "in_degree": 5, "state_compare": true},
|
||||
{"id": "B", "succ": ["D"], "in_degree": 1, "next_state": "C"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Core functions
|
||||
|
||||
### `detect(path)`
|
||||
Returns the dispatcher, case count, deflattened transitions, and unresolved blocks.
|
||||
|
||||
## Method
|
||||
|
||||
Dispatcher = highest in-degree block that compares the state variable; case blocks branch back to
|
||||
it; each case's `next_state` constant gives the true successor.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py detect cfg.json
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- Obfuscator-LLVM control-flow flattening — https://github.com/obfuscator-llvm/obfuscator
|
||||
- MITRE ATT&CK T1027 Obfuscated Files or Information — https://attack.mitre.org/techniques/T1027/
|
||||
- Dispatcher/state-variable deflattening method from public deobfuscation research
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Detect control-flow flattening and reconstruct original control flow from CFG JSON.
|
||||
|
||||
Input JSON describes a function's basic blocks:
|
||||
{
|
||||
"state_var": "eax",
|
||||
"blocks": [
|
||||
{"id": "A", "succ": ["D"], "in_degree": 1, "state_compare": false,
|
||||
"next_state": null},
|
||||
{"id": "D", "succ": ["B","C","E"], "in_degree": 5, "state_compare": true},
|
||||
{"id": "B", "succ": ["D"], "in_degree": 1, "next_state": "C_const"} ...
|
||||
]
|
||||
}
|
||||
Identifies the dispatcher (high in-degree + state_compare) and rebuilds case->next mappings using
|
||||
each case's next_state. Parses JSON only; executes nothing.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def detect(path: str) -> dict:
|
||||
data = json.loads(Path(path).read_text(encoding="utf-8-sig"))
|
||||
blocks = {b["id"]: b for b in data.get("blocks", [])}
|
||||
# Dispatcher: a block that compares the state variable and has the highest in-degree.
|
||||
candidates = [b for b in blocks.values() if b.get("state_compare")]
|
||||
if not candidates:
|
||||
candidates = list(blocks.values())
|
||||
dispatcher = max(candidates, key=lambda b: b.get("in_degree", 0), default=None)
|
||||
if not dispatcher:
|
||||
return {"flattening_detected": False}
|
||||
disp_id = dispatcher["id"]
|
||||
# Case/relay blocks branch back to the dispatcher.
|
||||
cases = [b for b in blocks.values()
|
||||
if disp_id in b.get("succ", []) and b["id"] != disp_id]
|
||||
deflattened = {}
|
||||
for case in cases:
|
||||
nxt = case.get("next_state")
|
||||
deflattened[case["id"]] = nxt
|
||||
covered = set(deflattened) | {disp_id}
|
||||
orphans = [bid for bid in blocks if bid not in covered
|
||||
and bid not in (data.get("entry"),)]
|
||||
return {
|
||||
"flattening_detected": len(cases) >= 3,
|
||||
"dispatcher": disp_id,
|
||||
"state_var": data.get("state_var"),
|
||||
"case_count": len(cases),
|
||||
"deflattened_transitions": deflattened,
|
||||
"unresolved_blocks": orphans,
|
||||
}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Defeat control-flow flattening")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("detect")
|
||||
p.add_argument("cfg")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(detect(args.cfg), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user