485 lines
13 KiB
Markdown
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 — обход защит`
|