Files

485 lines
13 KiB
Markdown

---
name: reverse-engineering-obfuscation
description: Use for deobfuscation, unpacking, anti-analysis bypass, code virtualization
reversing, control flow flattening recovery, string deobfuscation, and any task
involving removal or analysis of code protection mechanisms.
version: 1.0.0
platforms:
- linux
- macos
- windows
triggers:
- reverse
- engineering
- obfuscation
- deobfuscation
- unpacking
- anti
- analysis
- bypass
- code
- virtualization
- reversing
- control
depends_on: []
metadata:
tags:
- reverse
- engineering
- obfuscation
---
# Reverse Engineering — Obfuscation & Anti-Analysis
## Trigger Conditions
- Deobfuscating obfuscated code (JavaScript, Python, Java, .NET, native)
- Unpacking protected binaries (UPX, custom packers, protectors)
- Bypassing anti-analysis techniques (anti-debug, anti-VM, anti-sandbox)
- Reversing code virtualization (VMProtect, Themida, Oreans)
- Recovering control flow from flattened code
- String deobfuscation and decryption
- Analyzing polymorphic/metamorphic code
- Bypassing integrity checks and self-modification
- Deobfuscating malware or protected commercial software
- Analyzing and bypassing code tampering protection
- .NET deobfuscation (de4dot, dnlib)
- JavaScript deobfuscation (eval chains, string arrays, control flow)
## Core Methodology
### 1. Packer Identification
**Common packers:**
```
UPX — Universal Packer for Executables
Signature: "UPX!" in section names
Unpack: upx -d file.exe
ASPack — Advanced SCS Packer
Signature: ".aspack", ".adata" sections
Unpack: manual dump at OEP
Themida/WinLicense — Oreans Technologies
Signature: heavy code virtualization, API redirection
Unpack: extremely difficult, requires VM analysis
VMProtect — VMP
Signature: "VMProtect" strings, virtualized code
Unpack: requires VM handler analysis
Armadillo — Silicon Realms
Signature: Nanomite debugging, code morphing
Unpack: debug child process
PECompact, PELock, ExeStealth
Various signatures, custom unpacking needed
```
**Detection:**
```bash
# Detect It Easy (DIE) — comprehensive packer detection
diec file.exe
# PEiD — legacy but useful
peid file.exe
# Exeinfo PE — another detector
exeinfope file.exe
# Manual: check section characteristics
# Packed binaries have:
# - Single executable section (no .rdata, .data separation)
# - High entropy (>7.0) in code section
# - Unusual section names
# - Small import table
```
### 2. Unpacking Techniques
**Manual unpacking (generic):**
```
1. Find OEP (Original Entry Point)
- ESP law: set hardware breakpoint on ESP, run, step until return
- Section breakpoint: set BP on first instruction of unpacked section
- Memory breakpoint: set BP on write to original code section
- API breakpoint: set BP on GetProcAddress/LoadLibrary
2. Dump process memory
- Use Scylla, Process Dump, or custom dumper
- Dump at OEP, after unpacker decompressed everything
3. Fix IAT (Import Address Table)
- Scylla can rebuild IAT automatically
- Manual: trace IAT reconstruction in unpacker
- Identify IAT thunk addresses and original API names
4. Rebuild PE headers
- Fix entry point, section table, import directory
- Test with ImpREC or Scylla
```
**UPX unpacking:**
```bash
# Automatic
upx -d packed.exe -o unpacked.exe
# Manual (if modified UPX):
# 1. Find UPX1 section end (jmp to OEP)
# 2. Set breakpoint on the jump
# 3. Dump after jump executes
# 4. Fix IAT with Scylla
```
**Custom packer unpacking:**
```python
# Memory dumper — dump after unpacker runs
import psutil
import pefile
def dump_process(pid, base_address, size, output_path):
"""Dump memory region from running process."""
proc = psutil.Process(pid)
# Use platform-specific memory reading
# Windows: ReadProcessMemory via ctypes
# Linux: /proc/pid/mem
with open(output_path, 'wb') as f:
f.read(...)
# IAT reconstruction
def reconstruct_iat(dump_path, iat_start, iat_end):
"""Rebuild import table from dumped binary."""
pe = pefile.PE(dump_path)
# Scan IAT range for valid function pointers
# Resolve to module + function name
# Rebuild import directory
```
### 3. Anti-Debug Bypass
**Detection → Bypass mapping:**
```
Detection Method | Bypass
──────────────────────────────|────────────────────────────
IsDebuggerPresent | Patch to return 0
CheckRemoteDebuggerPresent | Hook to return FALSE
NtQueryInfoProcess(DebugPort) | Hook NtQueryInformationProcess
GetTickCount timing | Hook GetTickCount, return realistic values
RDTSC timing | Patch RDTSC or hook CPUID
SEH debugger detection | Patch exception handler
Hardware breakpoint detection | Clear DR0-DR7 registers
Parent process check | Spoof parent PID
Debug object handle | Close debug object
Window class check | Rename debugger window
```
**Frida — comprehensive anti-debug bypass:**
```javascript
// Anti-debug bypass suite
function bypassAntiDebug() {
// IsDebuggerPresent
var isDebuggerPresent = Module.findExportByName('kernel32.dll', 'IsDebuggerPresent');
if (isDebuggerPresent) {
Interceptor.replace(isDebuggerPresent, new NativeCallback(function() {
return 0;
}, 'int', []));
}
// CheckRemoteDebuggerPresent
var checkRemote = Module.findExportByName('kernel32.dll', 'CheckRemoteDebuggerPresent');
if (checkRemote) {
Interceptor.attach(checkRemote, {
onEnter: function(args) {
this.hdbg = args[1];
},
onLeave: function(retval) {
this.hdbg.writeU8(0); // not being debugged
retval.replace(1); // function succeeded
}
});
}
// NtQueryInformationProcess
var ntQuery = Module.findExportByName('ntdll.dll', 'NtQueryInformationProcess');
if (ntQuery) {
Interceptor.attach(ntQuery, {
onEnter: function(args) {
this.processInfoClass = args[1].toInt32();
this.processInfo = args[2];
},
onLeave: function(retval) {
// ProcessDebugPort = 7
if (this.processInfoClass === 7) {
this.processInfo.writePointer(ptr(0));
}
// ProcessDebugFlags = 0x1E
if (this.processInfoClass === 0x1E) {
this.processInfo.writeU8(1);
}
// ProcessDebugObjectHandle = 0x1E
if (this.processInfoClass === 30) {
this.processInfo.writePointer(ptr(0));
retval.replace(0xC0000353); // STATUS_PORT_NOT_SET
}
}
});
}
}
```
### 4. String Deobfuscation
**Common string obfuscation patterns:**
**XOR encrypted strings:**
```python
def xor_decrypt(data, key):
if isinstance(key, int):
return bytes([b ^ key for b in data])
return bytes([b ^ key[i % len(key)] for i, b in enumerate(data)])
# Find XOR key by known plaintext
known = b"GetProcAddress"
cipher = bytes_from_binary(offset, len(known))
key = bytes([p ^ c for p, c in zip(known, cipher)])
```
**String array + index (JavaScript):**
```javascript
// Obfuscated:
var _0x1234 = ['hello', 'world', 'test'];
function _0x5678(i) { return _0x1234[i]; }
console.log(_0x5678(0) + ' ' + _0x5678(1));
// Deobfuscation: replace calls with actual strings
// AST-based approach:
// 1. Parse JS to AST
// 2. Find array declaration
// 3. Find accessor function
// 4. Replace all calls with literal values
```
**.NET string decryption:**
```csharp
// Many .NET obfuscators encrypt strings and decrypt at runtime
// Find the decryption method, emulate it
using dnlib.DotNet;
using dnlib.DotNet.Emit;
// Load assembly
var mod = ModuleDefMD.Load("obfuscated.exe");
// Find string decryption method
// Usually called before string usage
// Pattern: ldtoken <string>, call decrypt_method
// Emulate or hook the decryption
// de4dot handles many common obfuscators automatically
```
### 5. Control Flow Flattening Recovery
**What it looks like:**
```c
// Original:
if (condition) {
block_a();
} else {
block_b();
}
block_c();
// Flattened:
state = INITIAL;
while (state != EXIT) {
switch (state) {
case INITIAL:
if (condition) state = STATE_A;
else state = STATE_B;
break;
case STATE_A:
block_a();
state = STATE_C;
break;
case STATE_B:
block_b();
state = STATE_C;
break;
case STATE_C:
block_c();
state = EXIT;
break;
}
}
```
**Recovery approaches:**
```
1. Symbolic execution (Angr, Triton)
- Track state variable
- Build CFG from state transitions
- Reconstruct original flow
2. Dynamic tracing
- Log state values during execution
- Build execution trace
- Reconstruct CFG from trace
3. Static analysis
- Identify dispatcher (switch statement)
- Map state values to basic blocks
- Rebuild CFG by analyzing state assignments
4. IDA plugin — deflat
- Automates dispatcher identification
- Rebuilds CFG
- Patches binary to remove flattening
```
**Angr — symbolic deobfuscation:**
```python
import angr
# Load binary
proj = angr.Project('obfuscated_binary', auto_load_libs=False)
# Find the dispatcher
# Set up symbolic execution
state = proj.factory.entry_state()
simgr = proj.factory.simulation_manager(state)
# Explore to find all reachable states
simgr.explore(find=lambda s: s.solver.is_true(s.regs.rip == exit_addr))
# Analyze paths to reconstruct CFG
for path in simgr.found:
print(path.history.bbl_addrs)
```
### 6. Code Virtualization Reversing
**VMProtect/Themida analysis:**
```
Virtualized code structure:
1. VM entry point (transition from native to VM)
2. VM bytecode (custom instruction set)
3. VM handler (interprets bytecode)
4. VM exit (return to native code)
Analysis approach:
1. Identify VM entry/exit points
2. Dump VM bytecode
3. Analyze VM handler to understand instruction set
4. Write VM bytecode disassembler
5. Reconstruct original logic from VM bytecode
```
**VM handler analysis:**
```c
// Typical VM dispatcher:
void vm_dispatcher(vm_context* ctx) {
while (ctx->ip < ctx->code_end) {
uint8_t opcode = *ctx->ip++;
switch (opcode) {
case 0x01: vm_push(ctx); break;
case 0x02: vm_pop(ctx); break;
case 0x03: vm_add(ctx); break;
// ... 200+ handlers
}
}
}
// Extract handler table:
// Find the switch statement in IDA
// Each case → handler function
// Document handler behavior
// Build bytecode disassembler
```
### 7. Integrity Check Bypass
**Common integrity checks:**
```
1. Checksum of code section
- Compute CRC32/MD5 of .text section
- Compare with stored value
- Bypass: patch comparison or stored checksum
2. Self-debugging
- Process debugs itself to detect tampering
- Bypass: hook debugging APIs
3. Code signing verification
- Verify Authenticode signature
- Bypass: patch verification function
4. Size/alignment checks
- Check section sizes, alignment
- Bypass: maintain original sizes
5. API hook detection
- Check if API functions are hooked
- Bypass: use direct syscalls
```
### 8. Automation Tools
**Generic deobfuscation framework:**
```python
class Deobfuscator:
def __init__(self, binary_path):
self.binary = open(binary_path, 'rb').read()
self.pe = pefile.PE(binary_path)
def find_string_references(self, target_string):
"""Find all references to a string in the binary."""
offsets = []
pos = 0
while True:
pos = self.binary.find(target_string.encode(), pos)
if pos == -1:
break
offsets.append(pos)
pos += 1
return offsets
def extract_xor_strings(self, key):
"""Extract XOR-encrypted strings with known key."""
results = []
# Scan data sections for XOR patterns
for section in self.pe.sections:
data = section.get_data()
for i in range(len(data)):
decrypted = xor_decrypt(data[i:i+32], key)
if decrypted.isascii() and len(decrypted) > 4:
results.append((section.Name, i, decrypted))
return results
```
## Implementation Standards
- All deobfuscation produces working, readable output
- Provide both automated and manual approaches
- Include scripts for repetitive deobfuscation tasks
- Document obfuscation techniques identified
- No partial deobfuscation — complete or nothing
## Output Format
For obfuscation RE tasks:
1. **Protection Identification** — packer, protector, obfuscator type
2. **Obfuscation Techniques** — what methods are used
3. **Deobfuscation Process** — step-by-step removal
4. **Recovered Code** — deobfuscated, readable output
5. **Bypass Methods** — working patches, scripts, tools
6. **Automation** — scripts for future deobfuscation
Всё, брат.
## Related
- `skill reverse-engineering-binary — бинарный RE`
- `skill reverse-engineering-full — полный RE`
- `skill binary-exploitation — обход защит`