ctf-agent 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +131 -0
- package/Dockerfile +192 -0
- package/README.md +522 -0
- package/agents/ctf-analyzer.md +58 -0
- package/agents/ctf-controller.md +151 -0
- package/agents/ctf-speedrun.md +74 -0
- package/bin/ctf-agent.js +119 -0
- package/docker-compose.yml +13 -0
- package/mcp_config.json +19 -0
- package/package.json +44 -0
- package/references/ctf-triage-ladder.md +68 -0
- package/references/exploit-databases.md +575 -0
- package/references/llm-safety-and-policy-compliance.md +173 -0
- package/references/multi-agent-orchestration-and-policy-routing.md +278 -0
- package/references/security-events-and-intelligence.md +566 -0
- package/references/version-matrix.md +59 -0
- package/rules/ctf-execution-rules.md +44 -0
- package/rules/ctf-safety-framing-rules.md +140 -0
- package/rules/flag-validation-rules.md +25 -0
- package/scripts/__init__.py +1 -0
- package/scripts/ctf-tools.lock +205 -0
- package/scripts/ctf_init.py +1026 -0
- package/scripts/ctf_update.py +583 -0
- package/scripts/ctfd_client.py +81 -0
- package/scripts/cve_lookup.py +453 -0
- package/scripts/extract_flags.py +155 -0
- package/scripts/install_as_agent.py +278 -0
- package/scripts/install_ctf_tools.sh +603 -0
- package/scripts/parallel_triage.py +395 -0
- package/scripts/prompt_policy_sanitizer.py +283 -0
- package/scripts/scope_guard.py +444 -0
- package/scripts/skill_validator.py +504 -0
- package/scripts/workspace_cleaner.py +141 -0
- package/skills/ctf-ai-ml/SKILL.md +120 -0
- package/skills/ctf-ai-ml/adversarial-ml.md +623 -0
- package/skills/ctf-ai-ml/llm-attacks.md +487 -0
- package/skills/ctf-ai-ml/model-attacks.md +422 -0
- package/skills/ctf-crypto/SKILL.md +320 -0
- package/skills/ctf-crypto/advanced-math.md +798 -0
- package/skills/ctf-crypto/classic-ciphers.md +651 -0
- package/skills/ctf-crypto/ecc-attacks.md +347 -0
- package/skills/ctf-crypto/exotic-crypto-2.md +380 -0
- package/skills/ctf-crypto/exotic-crypto.md +528 -0
- package/skills/ctf-crypto/historical.md +113 -0
- package/skills/ctf-crypto/lattice-and-lwe.md +524 -0
- package/skills/ctf-crypto/modern-ciphers-2.md +563 -0
- package/skills/ctf-crypto/modern-ciphers-3.md +453 -0
- package/skills/ctf-crypto/modern-ciphers.md +649 -0
- package/skills/ctf-crypto/prng-attacks.md +257 -0
- package/skills/ctf-crypto/prng.md +664 -0
- package/skills/ctf-crypto/rsa-attacks-2.md +792 -0
- package/skills/ctf-crypto/rsa-attacks.md +487 -0
- package/skills/ctf-crypto/stream-ciphers.md +390 -0
- package/skills/ctf-crypto/zkp-and-advanced.md +456 -0
- package/skills/ctf-forensics/3d-printing.md +121 -0
- package/skills/ctf-forensics/SKILL.md +379 -0
- package/skills/ctf-forensics/disk-advanced.md +497 -0
- package/skills/ctf-forensics/disk-and-memory.md +491 -0
- package/skills/ctf-forensics/disk-recovery.md +699 -0
- package/skills/ctf-forensics/linux-forensics.md +511 -0
- package/skills/ctf-forensics/network-advanced.md +583 -0
- package/skills/ctf-forensics/network.md +645 -0
- package/skills/ctf-forensics/peripheral-capture.md +287 -0
- package/skills/ctf-forensics/signals-and-hardware.md +713 -0
- package/skills/ctf-forensics/steganography.md +694 -0
- package/skills/ctf-forensics/stego-advanced-2.md +475 -0
- package/skills/ctf-forensics/stego-advanced.md +481 -0
- package/skills/ctf-forensics/stego-image.md +691 -0
- package/skills/ctf-forensics/windows.md +625 -0
- package/skills/ctf-malware/SKILL.md +181 -0
- package/skills/ctf-malware/c2-and-protocols.md +274 -0
- package/skills/ctf-malware/pe-and-dotnet.md +108 -0
- package/skills/ctf-malware/scripts-and-obfuscation.md +449 -0
- package/skills/ctf-misc/SKILL.md +498 -0
- package/skills/ctf-misc/bashjails.md +323 -0
- package/skills/ctf-misc/ctfd-navigation.md +465 -0
- package/skills/ctf-misc/dns.md +255 -0
- package/skills/ctf-misc/encodings-advanced.md +504 -0
- package/skills/ctf-misc/encodings.md +431 -0
- package/skills/ctf-misc/games-and-vms-2.md +254 -0
- package/skills/ctf-misc/games-and-vms-3.md +690 -0
- package/skills/ctf-misc/games-and-vms-4.md +229 -0
- package/skills/ctf-misc/games-and-vms.md +529 -0
- package/skills/ctf-misc/linux-privesc.md +333 -0
- package/skills/ctf-misc/pyjails.md +671 -0
- package/skills/ctf-misc/rf-sdr.md +91 -0
- package/skills/ctf-osint/SKILL.md +198 -0
- package/skills/ctf-osint/geolocation-and-media.md +464 -0
- package/skills/ctf-osint/social-media.md +312 -0
- package/skills/ctf-osint/web-and-dns.md +341 -0
- package/skills/ctf-pwn/SKILL.md +214 -0
- package/skills/ctf-pwn/advanced-exploits-2.md +579 -0
- package/skills/ctf-pwn/advanced-exploits-3.md +598 -0
- package/skills/ctf-pwn/advanced-exploits-4.md +590 -0
- package/skills/ctf-pwn/advanced-exploits-5.md +119 -0
- package/skills/ctf-pwn/advanced-exploits.md +773 -0
- package/skills/ctf-pwn/advanced.md +326 -0
- package/skills/ctf-pwn/field-notes.md +245 -0
- package/skills/ctf-pwn/format-string.md +694 -0
- package/skills/ctf-pwn/heap-fsop.md +285 -0
- package/skills/ctf-pwn/heap-techniques-2.md +333 -0
- package/skills/ctf-pwn/heap-techniques.md +513 -0
- package/skills/ctf-pwn/kernel-bypass.md +421 -0
- package/skills/ctf-pwn/kernel-techniques.md +366 -0
- package/skills/ctf-pwn/kernel.md +636 -0
- package/skills/ctf-pwn/overflow-basics.md +611 -0
- package/skills/ctf-pwn/rop-advanced.md +725 -0
- package/skills/ctf-pwn/rop-and-shellcode.md +659 -0
- package/skills/ctf-pwn/sandbox-escape.md +313 -0
- package/skills/ctf-reverse/SKILL.md +163 -0
- package/skills/ctf-reverse/anti-analysis-ctf.md +204 -0
- package/skills/ctf-reverse/anti-analysis.md +693 -0
- package/skills/ctf-reverse/field-notes.md +376 -0
- package/skills/ctf-reverse/languages-compiled.md +666 -0
- package/skills/ctf-reverse/languages-platforms.md +592 -0
- package/skills/ctf-reverse/languages.md +553 -0
- package/skills/ctf-reverse/patterns-ctf-2.md +397 -0
- package/skills/ctf-reverse/patterns-ctf-3.md +797 -0
- package/skills/ctf-reverse/patterns-ctf.md +670 -0
- package/skills/ctf-reverse/patterns-runtime.md +274 -0
- package/skills/ctf-reverse/patterns.md +572 -0
- package/skills/ctf-reverse/platforms-hardware.md +387 -0
- package/skills/ctf-reverse/platforms.md +664 -0
- package/skills/ctf-reverse/tools-advanced-2.md +421 -0
- package/skills/ctf-reverse/tools-advanced.md +407 -0
- package/skills/ctf-reverse/tools-dynamic.md +679 -0
- package/skills/ctf-reverse/tools-emulation.md +319 -0
- package/skills/ctf-reverse/tools.md +573 -0
- package/skills/ctf-web/SKILL.md +153 -0
- package/skills/ctf-web/auth-and-access-2.md +82 -0
- package/skills/ctf-web/auth-and-access.md +783 -0
- package/skills/ctf-web/auth-infra.md +321 -0
- package/skills/ctf-web/auth-jwt.md +186 -0
- package/skills/ctf-web/client-side-advanced.md +739 -0
- package/skills/ctf-web/client-side.md +529 -0
- package/skills/ctf-web/cves.md +373 -0
- package/skills/ctf-web/field-notes.md +482 -0
- package/skills/ctf-web/node-and-prototype.md +200 -0
- package/skills/ctf-web/server-side-2.md +337 -0
- package/skills/ctf-web/server-side-advanced-2.md +559 -0
- package/skills/ctf-web/server-side-advanced-3.md +125 -0
- package/skills/ctf-web/server-side-advanced-4.md +480 -0
- package/skills/ctf-web/server-side-advanced.md +378 -0
- package/skills/ctf-web/server-side-deser.md +443 -0
- package/skills/ctf-web/server-side-exec-2.md +799 -0
- package/skills/ctf-web/server-side-exec.md +457 -0
- package/skills/ctf-web/server-side.md +629 -0
- package/skills/ctf-web/sql-injection.md +790 -0
- package/skills/ctf-web/web3.md +374 -0
- package/skills/ctf-writeup/SKILL.md +90 -0
- package/skills/solve-challenge/SKILL.md +269 -0
- package/skills.json +16 -0
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
# CTF Reverse - Dynamic Analysis Tools
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
- [Frida (Dynamic Instrumentation)](#frida-dynamic-instrumentation)
|
|
5
|
+
- [Installation](#installation)
|
|
6
|
+
- [Basic Function Hooking](#basic-function-hooking)
|
|
7
|
+
- [Anti-Debug Bypass](#anti-debug-bypass)
|
|
8
|
+
- [Memory Scanning and Patching](#memory-scanning-and-patching)
|
|
9
|
+
- [Function Replacement](#function-replacement)
|
|
10
|
+
- [Tracing and Stalker](#tracing-and-stalker)
|
|
11
|
+
- [r2frida (Radare2 + Frida Integration)](#r2frida-radare2--frida-integration)
|
|
12
|
+
- [Frida for Android/iOS](#frida-for-androidios)
|
|
13
|
+
- [Frida Memoization for Recursive Function Speedup (hxp CTF 2017)](#frida-memoization-for-recursive-function-speedup-hxp-ctf-2017)
|
|
14
|
+
- [angr (Symbolic Execution)](#angr-symbolic-execution)
|
|
15
|
+
- [angr Installation](#angr-installation)
|
|
16
|
+
- [Basic Path Exploration](#basic-path-exploration)
|
|
17
|
+
- [Symbolic Input with Constraints](#symbolic-input-with-constraints)
|
|
18
|
+
- [Hook Functions to Simplify Analysis](#hook-functions-to-simplify-analysis)
|
|
19
|
+
- [Exploring from Specific Address](#exploring-from-specific-address)
|
|
20
|
+
- [Common Patterns and Tips](#common-patterns-and-tips)
|
|
21
|
+
- [Dealing with Path Explosion](#dealing-with-path-explosion)
|
|
22
|
+
- [angr CFG Recovery](#angr-cfg-recovery)
|
|
23
|
+
- [lldb (LLVM Debugger)](#lldb-llvm-debugger)
|
|
24
|
+
- [Basic Commands](#basic-commands)
|
|
25
|
+
- [Scripting (Python)](#scripting-python)
|
|
26
|
+
- [x64dbg (Windows Debugger)](#x64dbg-windows-debugger)
|
|
27
|
+
- [Key Features](#key-features)
|
|
28
|
+
- [Scripting](#scripting)
|
|
29
|
+
- [Common CTF Workflow](#common-ctf-workflow)
|
|
30
|
+
- [GDB Register Side-Channel on putchar() (picoCTF 2018)](#gdb-register-side-channel-on-putchar-picoctf-2018)
|
|
31
|
+
- [radare2 Visual Panels for Custom VM Tracing (OTW Advent 2018)](#radare2-visual-panels-for-custom-vm-tracing-otw-advent-2018)
|
|
32
|
+
- [libSegFault.so Register Dump at Crash (OTW Advent 2018)](#libsegfaultso-register-dump-at-crash-otw-advent-2018)
|
|
33
|
+
- [r2pipe Binary Walking + DP Constraint Solver (OTW Advent 2018)](#r2pipe-binary-walking--dp-constraint-solver-otw-advent-2018)
|
|
34
|
+
- [GDB Commands at strcmp to Recover Dynamic XOR Key (TAMUctf 2019)](#gdb-commands-at-strcmp-to-recover-dynamic-xor-key-tamuctf-2019)
|
|
35
|
+
|
|
36
|
+
For Qiling/Triton emulation and Intel Pin / LD_PRELOAD side-channel techniques, see [tools-emulation.md](tools-emulation.md).
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Frida (Dynamic Instrumentation)
|
|
41
|
+
|
|
42
|
+
Frida injects JavaScript into running processes for real-time hooking, tracing, and modification. Essential for anti-debug bypass, runtime inspection, and mobile RE.
|
|
43
|
+
|
|
44
|
+
### Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install frida-tools frida
|
|
48
|
+
# Verify
|
|
49
|
+
frida --version
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Basic Function Hooking
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
// hook.js — intercept a function and log arguments/return value
|
|
56
|
+
Interceptor.attach(Module.findExportByName(null, "strcmp"), {
|
|
57
|
+
onEnter: function(args) {
|
|
58
|
+
this.arg0 = Memory.readUtf8String(args[0]);
|
|
59
|
+
this.arg1 = Memory.readUtf8String(args[1]);
|
|
60
|
+
console.log(`strcmp("${this.arg0}", "${this.arg1}")`);
|
|
61
|
+
},
|
|
62
|
+
onLeave: function(retval) {
|
|
63
|
+
console.log(` → ${retval}`);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Attach to running process
|
|
70
|
+
frida -p $(pidof binary) -l hook.js
|
|
71
|
+
|
|
72
|
+
# Spawn and instrument from start
|
|
73
|
+
frida -f ./binary -l hook.js --no-pause
|
|
74
|
+
|
|
75
|
+
# One-liner: hook strcmp and dump comparisons
|
|
76
|
+
frida -f ./binary --no-pause -e '
|
|
77
|
+
Interceptor.attach(Module.findExportByName(null, "strcmp"), {
|
|
78
|
+
onEnter(args) {
|
|
79
|
+
console.log("strcmp:", Memory.readUtf8String(args[0]), Memory.readUtf8String(args[1]));
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
'
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Anti-Debug Bypass
|
|
86
|
+
|
|
87
|
+
```javascript
|
|
88
|
+
// Bypass ptrace(PTRACE_TRACEME) — returns 0 (success) without calling
|
|
89
|
+
Interceptor.attach(Module.findExportByName(null, "ptrace"), {
|
|
90
|
+
onEnter: function(args) {
|
|
91
|
+
this.request = args[0].toInt32();
|
|
92
|
+
},
|
|
93
|
+
onLeave: function(retval) {
|
|
94
|
+
if (this.request === 0) { // PTRACE_TRACEME
|
|
95
|
+
retval.replace(ptr(0));
|
|
96
|
+
console.log("[*] ptrace(TRACEME) bypassed");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Bypass IsDebuggerPresent (Windows)
|
|
102
|
+
var isDbg = Module.findExportByName("kernel32.dll", "IsDebuggerPresent");
|
|
103
|
+
Interceptor.attach(isDbg, {
|
|
104
|
+
onLeave: function(retval) {
|
|
105
|
+
retval.replace(ptr(0));
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Bypass timing checks — hook clock_gettime to return constant
|
|
110
|
+
Interceptor.attach(Module.findExportByName(null, "clock_gettime"), {
|
|
111
|
+
onLeave: function(retval) {
|
|
112
|
+
// Force constant timestamp to defeat timing checks
|
|
113
|
+
var ts = this.context.rsi || this.context.x1; // x86 or ARM
|
|
114
|
+
Memory.writeU64(ts, 0); // tv_sec
|
|
115
|
+
Memory.writeU64(ts.add(8), 0); // tv_nsec
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Memory Scanning and Patching
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
// Scan for flag pattern in memory
|
|
124
|
+
Process.enumerateRanges('r--').forEach(function(range) {
|
|
125
|
+
Memory.scan(range.base, range.size, "66 6c 61 67 7b", { // "flag{"
|
|
126
|
+
onMatch: function(address, size) {
|
|
127
|
+
console.log("[FLAG] Found at:", address, Memory.readUtf8String(address, 64));
|
|
128
|
+
},
|
|
129
|
+
onComplete: function() {}
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Patch instruction (NOP out a check)
|
|
134
|
+
var addr = Module.findBaseAddress("binary").add(0x1234);
|
|
135
|
+
Memory.patchCode(addr, 2, function(code) {
|
|
136
|
+
var writer = new X86Writer(code, { pc: addr });
|
|
137
|
+
writer.putNop();
|
|
138
|
+
writer.putNop();
|
|
139
|
+
writer.flush();
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Function Replacement
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
// Replace a validation function to always return true
|
|
147
|
+
var checkFlag = Module.findExportByName(null, "check_flag");
|
|
148
|
+
Interceptor.replace(checkFlag, new NativeCallback(function(input) {
|
|
149
|
+
console.log("[*] check_flag called with:", Memory.readUtf8String(input));
|
|
150
|
+
return 1; // always valid
|
|
151
|
+
}, 'int', ['pointer']));
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Tracing and Stalker
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
// Trace all calls in a function (Stalker — instruction-level tracing)
|
|
158
|
+
var targetAddr = Module.findExportByName(null, "main");
|
|
159
|
+
Stalker.follow(Process.getCurrentThreadId(), {
|
|
160
|
+
transform: function(iterator) {
|
|
161
|
+
var instruction;
|
|
162
|
+
while ((instruction = iterator.next()) !== null) {
|
|
163
|
+
if (instruction.mnemonic === "call") {
|
|
164
|
+
iterator.putCallout(function(context) {
|
|
165
|
+
console.log("CALL at", context.pc, "→", ptr(context.pc).readPointer());
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
iterator.keep();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### r2frida (Radare2 + Frida Integration)
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
# Attach radare2 to process via Frida
|
|
178
|
+
r2 frida://spawn/./binary
|
|
179
|
+
|
|
180
|
+
# r2frida commands
|
|
181
|
+
\ii # List imports
|
|
182
|
+
\il # List loaded modules
|
|
183
|
+
\dt strcmp # Trace strcmp calls
|
|
184
|
+
\dc # Continue execution
|
|
185
|
+
\dm # List memory maps
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Frida for Android/iOS
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
# Android (requires rooted device or Frida server)
|
|
192
|
+
adb push frida-server /data/local/tmp/
|
|
193
|
+
adb shell "chmod 755 /data/local/tmp/frida-server && /data/local/tmp/frida-server &"
|
|
194
|
+
|
|
195
|
+
# Hook Android Java methods
|
|
196
|
+
frida -U -f com.example.app -l hook_android.js --no-pause
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
```javascript
|
|
200
|
+
// hook_android.js — hook Java method
|
|
201
|
+
Java.perform(function() {
|
|
202
|
+
var MainActivity = Java.use("com.example.app.MainActivity");
|
|
203
|
+
MainActivity.checkPassword.implementation = function(input) {
|
|
204
|
+
console.log("[*] checkPassword called with:", input);
|
|
205
|
+
var result = this.checkPassword(input);
|
|
206
|
+
console.log("[*] Result:", result);
|
|
207
|
+
return result;
|
|
208
|
+
};
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**Key insight:** Frida excels where static analysis fails — obfuscated code, packed binaries, and runtime-generated data. Hook comparison functions (`strcmp`, `memcmp`, custom validators) to extract expected values without reversing the algorithm. Use `Interceptor.attach` for observation, `Interceptor.replace` for modification.
|
|
213
|
+
|
|
214
|
+
**When to use:** Anti-debugging bypass, extracting runtime-computed keys, hooking crypto functions to dump plaintext, mobile app analysis, packed binary inspection.
|
|
215
|
+
|
|
216
|
+
### Frida Memoization for Recursive Function Speedup (hxp CTF 2017)
|
|
217
|
+
|
|
218
|
+
Hook a recursive function with Frida, memoize results, and replay cached values to skip redundant computation. Fibonacci-like recursive challenges with exponential complexity become instant with memoization.
|
|
219
|
+
|
|
220
|
+
```javascript
|
|
221
|
+
// memo_hook.js — memoize a recursive function to skip redundant calls
|
|
222
|
+
var memo = {};
|
|
223
|
+
var funcAddr = ptr("0x400abc"); // Address of the recursive function
|
|
224
|
+
var retAddr = ptr("0x400def"); // Address of the function's ret instruction
|
|
225
|
+
|
|
226
|
+
Interceptor.attach(funcAddr, {
|
|
227
|
+
onEnter: function(args) {
|
|
228
|
+
this.key = args[0].toInt32();
|
|
229
|
+
if (memo[this.key] !== undefined) {
|
|
230
|
+
// Skip computation entirely: set return value and jump to ret
|
|
231
|
+
this.context.rax = memo[this.key];
|
|
232
|
+
this.context.rip = retAddr;
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
onLeave: function(retval) {
|
|
236
|
+
// Cache the result for future calls with the same argument
|
|
237
|
+
memo[this.key] = retval.toInt32();
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
# Usage
|
|
244
|
+
frida -f ./binary -l memo_hook.js --no-pause
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
For multi-argument functions, build a composite key:
|
|
248
|
+
```javascript
|
|
249
|
+
Interceptor.attach(funcAddr, {
|
|
250
|
+
onEnter: function(args) {
|
|
251
|
+
this.key = args[0].toInt32() + "," + args[1].toInt32();
|
|
252
|
+
if (memo[this.key] !== undefined) {
|
|
253
|
+
this.context.rax = memo[this.key];
|
|
254
|
+
this.context.rip = retAddr;
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
onLeave: function(retval) {
|
|
258
|
+
memo[this.key] = retval.toInt32();
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
**Key insight:** Frida's `Interceptor` can both read and modify register state, allowing you to skip function execution entirely by setting `rax` (return value) and `rip` (to the `ret` instruction). This works on any recursive function where the same arguments produce the same result. Exponential-time recursive computations (Fibonacci, Ackermann, tree traversals) become linear with memoization.
|
|
264
|
+
|
|
265
|
+
**References:** hxp CTF 2017
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## angr (Symbolic Execution)
|
|
270
|
+
|
|
271
|
+
angr automatically explores program paths to find inputs satisfying constraints. Solves many flag-checking binaries in minutes that take hours manually.
|
|
272
|
+
|
|
273
|
+
### angr Installation
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
pip install angr
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Basic Path Exploration
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
import angr
|
|
283
|
+
import claripy
|
|
284
|
+
|
|
285
|
+
# Load binary
|
|
286
|
+
proj = angr.Project('./binary', auto_load_libs=False)
|
|
287
|
+
|
|
288
|
+
# Find address of "Correct!" print, avoid "Wrong!" print
|
|
289
|
+
# Get these from disassembly (objdump -d or Ghidra)
|
|
290
|
+
FIND_ADDR = 0x401234 # Address of success path
|
|
291
|
+
AVOID_ADDR = 0x401256 # Address of failure path
|
|
292
|
+
|
|
293
|
+
# Create simulation manager and explore
|
|
294
|
+
simgr = proj.factory.simgr()
|
|
295
|
+
simgr.explore(find=FIND_ADDR, avoid=AVOID_ADDR)
|
|
296
|
+
|
|
297
|
+
if simgr.found:
|
|
298
|
+
found = simgr.found[0]
|
|
299
|
+
# Get stdin that reaches the target
|
|
300
|
+
print("Flag:", found.posix.dumps(0)) # fd 0 = stdin
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Symbolic Input with Constraints
|
|
304
|
+
|
|
305
|
+
```python
|
|
306
|
+
import angr
|
|
307
|
+
import claripy
|
|
308
|
+
|
|
309
|
+
proj = angr.Project('./binary', auto_load_libs=False)
|
|
310
|
+
|
|
311
|
+
# Create symbolic input (e.g., 32-byte flag)
|
|
312
|
+
flag_len = 32
|
|
313
|
+
flag_chars = [claripy.BVS(f'flag_{i}', 8) for i in range(flag_len)]
|
|
314
|
+
flag = claripy.Concat(*flag_chars + [claripy.BVV(b'\n')])
|
|
315
|
+
|
|
316
|
+
# Constrain to printable ASCII
|
|
317
|
+
state = proj.factory.entry_state(stdin=flag)
|
|
318
|
+
for c in flag_chars:
|
|
319
|
+
state.solver.add(c >= 0x20)
|
|
320
|
+
state.solver.add(c <= 0x7e)
|
|
321
|
+
|
|
322
|
+
# Constrain known prefix: "flag{"
|
|
323
|
+
state.solver.add(flag_chars[0] == ord('f'))
|
|
324
|
+
state.solver.add(flag_chars[1] == ord('l'))
|
|
325
|
+
state.solver.add(flag_chars[2] == ord('a'))
|
|
326
|
+
state.solver.add(flag_chars[3] == ord('g'))
|
|
327
|
+
state.solver.add(flag_chars[4] == ord('{'))
|
|
328
|
+
state.solver.add(flag_chars[flag_len-1] == ord('}'))
|
|
329
|
+
|
|
330
|
+
simgr = proj.factory.simgr(state)
|
|
331
|
+
simgr.explore(find=0x401234, avoid=0x401256)
|
|
332
|
+
|
|
333
|
+
if simgr.found:
|
|
334
|
+
found = simgr.found[0]
|
|
335
|
+
result = found.solver.eval(flag, cast_to=bytes)
|
|
336
|
+
print("Flag:", result.decode())
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Hook Functions to Simplify Analysis
|
|
340
|
+
|
|
341
|
+
```python
|
|
342
|
+
import angr
|
|
343
|
+
|
|
344
|
+
proj = angr.Project('./binary', auto_load_libs=False)
|
|
345
|
+
|
|
346
|
+
# Hook printf to avoid path explosion in I/O
|
|
347
|
+
@proj.hook(0x401100, length=5) # Address of call to printf
|
|
348
|
+
def skip_printf(state):
|
|
349
|
+
pass # Do nothing, just skip
|
|
350
|
+
|
|
351
|
+
# Hook sleep/anti-debug functions
|
|
352
|
+
@proj.hook(0x401050, length=5) # Address of call to sleep
|
|
353
|
+
def skip_sleep(state):
|
|
354
|
+
pass
|
|
355
|
+
|
|
356
|
+
# Replace a function with a summary
|
|
357
|
+
class AlwaysSucceed(angr.SimProcedure):
|
|
358
|
+
def run(self):
|
|
359
|
+
return 1
|
|
360
|
+
|
|
361
|
+
proj.hook_symbol('check_license', AlwaysSucceed())
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Exploring from Specific Address
|
|
365
|
+
|
|
366
|
+
```python
|
|
367
|
+
# Start from middle of function (skip initialization)
|
|
368
|
+
state = proj.factory.blank_state(addr=0x401200)
|
|
369
|
+
|
|
370
|
+
# Set up registers/memory manually
|
|
371
|
+
state.regs.rdi = 0x600000 # Pointer to input buffer
|
|
372
|
+
state.memory.store(0x600000, b"AAAA" + b"\x00" * 28)
|
|
373
|
+
|
|
374
|
+
simgr = proj.factory.simgr(state)
|
|
375
|
+
simgr.explore(find=0x401300, avoid=0x401350)
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### Common Patterns and Tips
|
|
379
|
+
|
|
380
|
+
```python
|
|
381
|
+
# Pattern 1: argv-based input
|
|
382
|
+
state = proj.factory.entry_state(args=['./binary', flag_sym])
|
|
383
|
+
|
|
384
|
+
# Pattern 2: Multiple find/avoid addresses
|
|
385
|
+
simgr.explore(
|
|
386
|
+
find=[0x401234, 0x401300], # Any success path
|
|
387
|
+
avoid=[0x401256, 0x401400] # All failure paths
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
# Pattern 3: Find by output string (no address needed)
|
|
391
|
+
def is_successful(state):
|
|
392
|
+
stdout = state.posix.dumps(1) # fd 1 = stdout
|
|
393
|
+
return b"Correct" in stdout
|
|
394
|
+
|
|
395
|
+
def should_avoid(state):
|
|
396
|
+
stdout = state.posix.dumps(1)
|
|
397
|
+
return b"Wrong" in stdout
|
|
398
|
+
|
|
399
|
+
simgr.explore(find=is_successful, avoid=should_avoid)
|
|
400
|
+
|
|
401
|
+
# Pattern 4: Timeout protection
|
|
402
|
+
simgr.explore(find=0x401234, avoid=0x401256, num_find=1)
|
|
403
|
+
# Or use exploration techniques:
|
|
404
|
+
simgr.use_technique(angr.exploration_techniques.DFS()) # Depth-first
|
|
405
|
+
simgr.use_technique(angr.exploration_techniques.LengthLimiter(max_length=500))
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### Dealing with Path Explosion
|
|
409
|
+
|
|
410
|
+
```python
|
|
411
|
+
# Use DFS instead of BFS (default) for flag checkers
|
|
412
|
+
simgr.use_technique(angr.exploration_techniques.DFS())
|
|
413
|
+
|
|
414
|
+
# Limit symbolic memory operations
|
|
415
|
+
state.options.add(angr.options.ZERO_FILL_UNCONSTRAINED_MEMORY)
|
|
416
|
+
state.options.add(angr.options.ZERO_FILL_UNCONSTRAINED_REGISTERS)
|
|
417
|
+
|
|
418
|
+
# Hook expensive functions (crypto, hashing) to avoid explosion
|
|
419
|
+
import hashlib
|
|
420
|
+
class SHA256Hook(angr.SimProcedure):
|
|
421
|
+
def run(self, data, length, output):
|
|
422
|
+
# Concretize input and compute hash
|
|
423
|
+
concrete_data = self.state.solver.eval(
|
|
424
|
+
self.state.memory.load(data, self.state.solver.eval(length)),
|
|
425
|
+
cast_to=bytes
|
|
426
|
+
)
|
|
427
|
+
h = hashlib.sha256(concrete_data).digest()
|
|
428
|
+
self.state.memory.store(output, h)
|
|
429
|
+
|
|
430
|
+
proj.hook_symbol('SHA256', SHA256Hook())
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### angr CFG Recovery
|
|
434
|
+
|
|
435
|
+
```python
|
|
436
|
+
# Control flow graph for understanding structure
|
|
437
|
+
cfg = proj.analyses.CFGFast()
|
|
438
|
+
print(f"Functions found: {len(cfg.functions)}")
|
|
439
|
+
|
|
440
|
+
# Find main
|
|
441
|
+
for addr, func in cfg.functions.items():
|
|
442
|
+
if func.name == 'main':
|
|
443
|
+
print(f"main at {addr:#x}")
|
|
444
|
+
break
|
|
445
|
+
|
|
446
|
+
# Cross-references
|
|
447
|
+
node = cfg.model.get_any_node(0x401234)
|
|
448
|
+
print("Predecessors:", [hex(p.addr) for p in cfg.model.get_predecessors(node)])
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
**Key insight:** angr works best on flag-checker binaries with clear success/failure paths. For complex binaries, hook expensive functions (crypto, I/O) and use DFS exploration. Start with the simplest approach (just find/avoid addresses) before adding constraints. If angr is slow, constrain input to printable ASCII and add known prefix.
|
|
452
|
+
|
|
453
|
+
**When to use:** Flag validators with branching logic, maze/path-finding binaries, constraint-heavy checks, automated binary analysis. Less effective for: heavy crypto, floating-point math, complex heap operations.
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
## lldb (LLVM Debugger)
|
|
458
|
+
|
|
459
|
+
Primary debugger for macOS/iOS. Also works on Linux. Preferred for Swift/Objective-C and Apple platform binaries.
|
|
460
|
+
|
|
461
|
+
### Basic Commands
|
|
462
|
+
|
|
463
|
+
```bash
|
|
464
|
+
lldb ./binary
|
|
465
|
+
(lldb) run # Run program
|
|
466
|
+
(lldb) b main # Breakpoint on main
|
|
467
|
+
(lldb) b 0x401234 # Breakpoint at address
|
|
468
|
+
(lldb) breakpoint set -r "check.*" # Regex breakpoint
|
|
469
|
+
(lldb) c # Continue
|
|
470
|
+
(lldb) si # Step instruction
|
|
471
|
+
(lldb) ni # Next instruction
|
|
472
|
+
(lldb) register read # Show all registers
|
|
473
|
+
(lldb) register write rax 0 # Modify register
|
|
474
|
+
(lldb) memory read 0x401000 -c 32 # Read 32 bytes
|
|
475
|
+
(lldb) x/s $rsi # Examine string (GDB-style)
|
|
476
|
+
(lldb) dis -n main # Disassemble function
|
|
477
|
+
(lldb) image list # Loaded modules + base addresses
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### Scripting (Python)
|
|
481
|
+
|
|
482
|
+
```python
|
|
483
|
+
# lldb Python scripting
|
|
484
|
+
import lldb
|
|
485
|
+
|
|
486
|
+
def hook_strcmp(debugger, command, result, internal_dict):
|
|
487
|
+
target = debugger.GetSelectedTarget()
|
|
488
|
+
process = target.GetProcess()
|
|
489
|
+
thread = process.GetSelectedThread()
|
|
490
|
+
frame = thread.GetSelectedFrame()
|
|
491
|
+
arg0 = frame.FindRegister("rdi").GetValueAsUnsigned()
|
|
492
|
+
arg1 = frame.FindRegister("rsi").GetValueAsUnsigned()
|
|
493
|
+
s0 = process.ReadCStringFromMemory(arg0, 256, lldb.SBError())
|
|
494
|
+
s1 = process.ReadCStringFromMemory(arg1, 256, lldb.SBError())
|
|
495
|
+
print(f'strcmp("{s0}", "{s1}")')
|
|
496
|
+
|
|
497
|
+
# Register in lldb: command script add -f script.hook_strcmp hook_strcmp
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
**Key insight:** Use lldb for macOS binaries (Mach-O), iOS apps, and when GDB isn't available. `image list` gives ASLR slide for PIE binaries. Scripting API is more structured than GDB's.
|
|
501
|
+
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
## x64dbg (Windows Debugger)
|
|
505
|
+
|
|
506
|
+
Open-source Windows debugger with modern UI. Alternative to OllyDbg/WinDbg for Windows RE challenges.
|
|
507
|
+
|
|
508
|
+
### Key Features
|
|
509
|
+
|
|
510
|
+
```bash
|
|
511
|
+
# Launch
|
|
512
|
+
x64dbg.exe binary.exe # 64-bit
|
|
513
|
+
x32dbg.exe binary.exe # 32-bit
|
|
514
|
+
|
|
515
|
+
# Essential shortcuts
|
|
516
|
+
F2 → Toggle breakpoint
|
|
517
|
+
F7 → Step into
|
|
518
|
+
F8 → Step over
|
|
519
|
+
F9 → Run
|
|
520
|
+
Ctrl+G → Go to address
|
|
521
|
+
Ctrl+F → Find pattern in memory
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
### Scripting
|
|
525
|
+
|
|
526
|
+
```bash
|
|
527
|
+
# x64dbg command line
|
|
528
|
+
bp 0x401234 # Breakpoint
|
|
529
|
+
SetBPX 0x401234, 0, "log {s:utf8@[esp+4]}" # Log string arg on hit
|
|
530
|
+
run # Continue
|
|
531
|
+
StepOver # Step over
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### Common CTF Workflow
|
|
535
|
+
|
|
536
|
+
1. Set breakpoint on `GetWindowTextA`/`MessageBoxA` for GUI crackers
|
|
537
|
+
2. Trace back from success/failure message
|
|
538
|
+
3. Use **Scylla** plugin for IAT reconstruction on packed binaries
|
|
539
|
+
4. **Snowman** decompiler plugin for quick pseudo-C
|
|
540
|
+
|
|
541
|
+
**Key insight:** x64dbg has built-in pattern scanning, hardware breakpoints, and conditional logging. For Windows CTF binaries, it's often faster than IDA/Ghidra for dynamic analysis. Use the **xAnalyzer** plugin for automatic function argument annotation.
|
|
542
|
+
|
|
543
|
+
---
|
|
544
|
+
|
|
545
|
+
## GDB Register Side-Channel on putchar() (picoCTF 2018)
|
|
546
|
+
|
|
547
|
+
**Pattern:** The binary decrypts a flag one character at a time and calls `putchar()` with a `usleep()` between prints. Rather than wait out the sleeps, set a breakpoint on `putchar@plt` and log `$rdi` (on glibc x86-64 the character lives there) at every hit. A GDB logging loop dumps the full flag in milliseconds regardless of the artificial delay.
|
|
548
|
+
|
|
549
|
+
```gdb
|
|
550
|
+
# ~/.gdbinit for this challenge
|
|
551
|
+
set pagination off
|
|
552
|
+
set logging file flag.log
|
|
553
|
+
set logging overwrite on
|
|
554
|
+
set logging on
|
|
555
|
+
|
|
556
|
+
break putchar
|
|
557
|
+
commands
|
|
558
|
+
silent
|
|
559
|
+
printf "%c", $rdi
|
|
560
|
+
continue
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
run
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
```bash
|
|
567
|
+
gdb -batch -x script.gdb ./crackme
|
|
568
|
+
cat flag.log
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
**Key insight:** Any time a program artificially slows output with `usleep`, `nanosleep`, or busy-loop delays, the character to be printed is already in a register before the sleep runs. Breakpoint on the output function (`putchar`, `fputc`, `write` with `fd=1`), print the first-argument register (`$rdi` on x86-64, `$r0` on ARM, `$a0` on RISC-V/MIPS), and let GDB scripting batch-extract the data. Works even on anti-debug binaries when hardware breakpoints are available.
|
|
572
|
+
|
|
573
|
+
**References:** picoCTF 2018 — learn gdb, writeup 11784
|
|
574
|
+
|
|
575
|
+
---
|
|
576
|
+
|
|
577
|
+
## radare2 Visual Panels for Custom VM Tracing (OTW Advent 2018)
|
|
578
|
+
|
|
579
|
+
**Pattern:** Custom-VM binaries look opaque until you can see the program counter, next opcode, stack, and heap simultaneously. radare2's panel mode (`V!`) lets you pin all four views on one screen and step through host-level instructions while watching the VM state move.
|
|
580
|
+
|
|
581
|
+
```text
|
|
582
|
+
f sp @ rbp-0x160 # flag VM sp
|
|
583
|
+
f ip @ rbp-0x158 # flag VM ip
|
|
584
|
+
f stack @ rbp-0x150
|
|
585
|
+
f heap @ rbp-0x148
|
|
586
|
+
|
|
587
|
+
V! # enter panels
|
|
588
|
+
# panel 1: ?v [ip]; pd 1 @ [ip] (next VM instruction)
|
|
589
|
+
# panel 2: pxQ 0x60 @ sp (stack)
|
|
590
|
+
# panel 3: pxQ 0x60 @ heap (heap)
|
|
591
|
+
# panel 4: afvd (local vars / registers)
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
Set conditional breakpoints on host-level branches that correspond to VM opcode dispatch, and step with `ds`. Combine with `e io.cache=true` for non-destructive patching of VM opcodes during analysis.
|
|
595
|
+
|
|
596
|
+
**Key insight:** Custom VMs are reversible in minutes once you watch their state live. Panel mode beats static decompilation because the host binary often lacks decompiler-friendly structure; the VM becomes self-explanatory when you see every register tick in real time.
|
|
597
|
+
|
|
598
|
+
**References:** OverTheWire Advent 2018 — Jackinthebox, writeup 12789
|
|
599
|
+
|
|
600
|
+
---
|
|
601
|
+
|
|
602
|
+
## libSegFault.so Register Dump at Crash (OTW Advent 2018)
|
|
603
|
+
|
|
604
|
+
**Pattern:** You need the exact register state at shellcode entry but gdb is unavailable or hooked. Preload `libSegFault.so` (shipped with glibc) and crash the program: it prints a full register dump, backtrace, and memory map to stderr.
|
|
605
|
+
|
|
606
|
+
```bash
|
|
607
|
+
LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libSegFault.so ./target
|
|
608
|
+
# or 32-bit:
|
|
609
|
+
LD_PRELOAD=/lib32/libSegFault.so ./target
|
|
610
|
+
|
|
611
|
+
# Force the crash:
|
|
612
|
+
# segfault_handler dumps: RIP, RSP, RAX..R15, stack backtrace
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
Read the printed registers to discover which already point at your shellcode (common: `RAX` → buffer, `RDI` → zero) and design minimal shellcode.
|
|
616
|
+
|
|
617
|
+
**Key insight:** libSegFault is installed on every glibc system as part of standard debugging infrastructure. It turns any segfault into a free register snapshot, even on hardened boxes without `strace`/`gdb` permissions.
|
|
618
|
+
|
|
619
|
+
**References:** OverTheWire Advent Bonanza 2018 — Day 22, writeup 12757
|
|
620
|
+
|
|
621
|
+
---
|
|
622
|
+
|
|
623
|
+
## r2pipe Binary Walking + DP Constraint Solver (OTW Advent 2018)
|
|
624
|
+
|
|
625
|
+
**Pattern:** 12 MB binary with 300k+ basic blocks performs chained hash checks on `argv[1]`. Walk every block via `r2pipe`, classify each instruction as hash/cmp/jmp/print, build a constraint graph, then solve with dynamic programming + backtracking over input positions.
|
|
626
|
+
|
|
627
|
+
```python
|
|
628
|
+
import r2pipe
|
|
629
|
+
r = r2pipe.open('./huge_binary')
|
|
630
|
+
r.cmd('aaa')
|
|
631
|
+
for fn in r.cmdj('aflj'):
|
|
632
|
+
for block in r.cmdj(f"pdfj @ {fn['offset']}")['ops']:
|
|
633
|
+
op = block['type']
|
|
634
|
+
if op == 'cmp': constraints.append(parse_cmp(block))
|
|
635
|
+
if op == 'call': targets.append(block['jump'])
|
|
636
|
+
# DP: memoize (position, accepted_set) -> char
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
**Key insight:** Big binaries with hash chains are solvable if you treat each branch as an inequality on input bytes. r2pipe's JSON output is machine-readable; DP over position/value tuples prunes most branches before running.
|
|
640
|
+
|
|
641
|
+
**References:** OverTheWire Advent Bonanza 2018 — Day 8, writeup 12771
|
|
642
|
+
|
|
643
|
+
---
|
|
644
|
+
|
|
645
|
+
## GDB Commands at strcmp to Recover Dynamic XOR Key (TAMUctf 2019)
|
|
646
|
+
|
|
647
|
+
**Pattern (Obfuscaxor):** Binary uses the [obfy](https://github.com/fritzone/obfy) C++ template obfuscator to bury a simple `enc(input)` XOR loop under thousands of opaque predicates. The terminal check is still `strcmp(expected_ciphertext, enc(input))` — so instead of unwinding obfy, break at the `strcmp` call and dump both operands:
|
|
648
|
+
|
|
649
|
+
```
|
|
650
|
+
disassemble verify_key
|
|
651
|
+
# ... 0x5555555560b9 <+96>: call strcmp@plt
|
|
652
|
+
break *verify_key+96
|
|
653
|
+
commands
|
|
654
|
+
silent
|
|
655
|
+
printf "RDI (expected): "
|
|
656
|
+
x/4xg $rdi
|
|
657
|
+
printf "RSI (computed): "
|
|
658
|
+
x/4xg $rsi
|
|
659
|
+
continue
|
|
660
|
+
end
|
|
661
|
+
run
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
Feed a known plaintext (`AAAAAAAAA`) and record `computed_A[i]`. Because `enc` is a byte-wise XOR keystream, the key byte is recovered directly from the delta with the target:
|
|
665
|
+
|
|
666
|
+
```python
|
|
667
|
+
# input_char ^ key = computed_char, and we want: target_char ^ key = target_input
|
|
668
|
+
def to_ans(got_A, expected):
|
|
669
|
+
return chr(got_A ^ ord('A') ^ expected)
|
|
670
|
+
|
|
671
|
+
# Sanity: flip just one byte of input and confirm only one computed byte moves.
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
Chain the per-byte recovery over the full 16-byte target and reconstruct the correct key (`p3Asujmn9CEeCB3A` for this challenge).
|
|
675
|
+
|
|
676
|
+
**Key insight:** When `strcmp` is the last gate, the obfuscator is irrelevant — its output still has to equal a fixed string at a known call site. GDB's `commands` block turns the breakpoint into an automatic oracle: one run with `AAAA...` leaks the keystream, and a second pass with any target string gives the valid input. Works for any keyed transform that is effectively a permutation of the input under a fixed key.
|
|
677
|
+
|
|
678
|
+
**References:** TAMUctf 2019 — Obfuscaxor, writeup 13574
|
|
679
|
+
|