Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use when you need to run a binary, trace execution, or observe runtime behavior. Runtime analysis via QEMU emulation, GDB debugging, and Frida hooking - syscall tracing (strace), breakpoints, memory inspection, function interception. Keywords - "run binary", "execute", "debug", "trace syscalls", "set breakpoint", "qemu", "gdb", "frida", "strace", "watch memory"
.claude/skills/aiskillstore-binary-re-dynamic-analysis/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 115% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 71% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 294% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 119% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 380% | 0% |
Observe actual runtime behavior. Verify hypotheses from static analysis. Capture data that's only visible during execution.
CRITICAL: All execution requires human approval.
Before running ANY binary:
| Host Platform | Target Arch | Method | Complexity | |---------------|-------------|--------|------------| | Linux x86_64 | ARM32/64, MIPS | Native qemu-user | Low | | Linux x86_64 | x86-32 | Native or linux32 | Low | | macOS (any) | ARM32/64 | Docker + binfmt | Medium | | macOS (any) | x86-32 | Docker --platform linux/i386 | Medium | | Windows | Any | WSL2 → Linux method | Medium |
bash# Start Docker runtime (Colima, Docker Desktop, etc.) colima start # Register ARM emulation handlers (requires privileged mode) docker run --rm --privileged --platform linux/arm64 \ tonistiigi/binfmt --install arm
CRITICAL: On Colima, /tmp mounts often fail silently. Always use home directory paths:
bash# ✅ GOOD - use home directory docker run -v ~/code/samples:/work:ro ... # ❌ BAD - /tmp mounts can fail on Colima docker run -v /tmp/samples:/work:ro ...
| Method | Isolation | Granularity | Best For | |--------|-----------|-------------|----------| | QEMU -strace | High | Syscall level | Initial behavior mapping | | QEMU + GDB | High | Instruction level | Detailed debugging | | Docker | High | Process level | Cross-arch on macOS | | Frida | Medium | Function level | Hooking without recompilation | | On-device | Low | Full system | When emulation fails |
Safest approach - runs in isolation with syscall logging.
bash# Verify sysroot exists ls /usr/arm-linux-gnueabihf/lib/libc.so* # ARM 32-bit execution qemu-arm -L /usr/arm-linux-gnueabihf -strace -- ./binary # ARM 64-bit execution qemu-aarch64 -L /usr/aarch64-linux-gnu -strace -- ./binary
| Binary ABI | Sysroot Path | QEMU Flag | |------------|--------------|-----------| | ARM glibc hard-float | /usr/arm-linux-gnueabihf | -L | | ARM glibc soft-float | /usr/arm-linux-gnueabi | -L | | ARM64 glibc | /usr/aarch64-linux-gnu | -L | | ARM musl | Custom extraction needed | -L |
bash# Set environment variables qemu-arm -L /sysroot \ -E HOME=/tmp \ -E USER=nobody \ -E LD_DEBUG=bindings \ -- ./binary # Unset dangerous variables qemu-arm -L /sysroot \ -U LD_PRELOAD \ -- ./binary
Strace output patterns to watch:
bash# Network activity openat.*socket connect(.*AF_INET sendto\|send\|write.*socket recvfrom\|recv\|read.*socket # File access openat.*O_RDONLY.*"/etc openat.*O_WRONLY stat\|lstat.*"/ # Process operations execve fork\|clone
Attach debugger for instruction-level control.
bash# Start QEMU with GDB server qemu-arm -g 1234 -L /usr/arm-linux-gnueabihf ./binary & # Connect with gdb-multiarch gdb-multiarch -q \ -ex "set architecture arm" \ -ex "target remote :1234" \ -ex "source ~/.gdbinit-gef.py" \ ./binary
gdb# Breakpoints break *0x8400 # Address break main # Symbol break *0x8400 if $r0 == 5 # Conditional # Execution control continue # Run until break stepi # Single instruction nexti # Step over calls finish # Run until return # Inspection info registers # All registers x/20i $pc # Disassemble from PC x/10wx $sp # Stack contents x/s 0x12345 # String at address # Memory find 0x8000, 0x10000, "pattern" # Search memory dump memory /tmp/mem.bin 0x8000 0x9000 # Extract region
With GEF loaded, additional commands:
gdbgef> vmmap # Memory layout gef> checksec # Security features gef> context # Full state display gef> hexdump qword $sp 10 # Better hex dump gef> pcustom # Structure definitions
bash# Create GDB script cat > analyze.gdb << 'EOF' set architecture arm target remote :1234 break main continue info registers x/20i $pc continue quit EOF # Run batch gdb-multiarch -batch -x analyze.gdb ./binary
Intercept function calls without modifying binary.
⚠️ Architecture Constraint: Frida requires native-arch execution. It cannot attach to QEMU-user targets.
| Scenario | Works? | Alternative | |----------|--------|-------------| | Native binary (x86_64 on x86_64) | ✅ | - | | Cross-arch under QEMU-user | ❌ | Use on-device frida-server | | Docker native-arch container | ✅ | - | | Docker cross-arch (emulated) | ❌ | Use on-device frida-server |
For cross-arch Frida, deploy frida-server to the target device:
bash# On target device: ./frida-server & # On host: frida -H device:27042 -f ./binary -l hook.js --no-pause
javascript// hook_connect.js Interceptor.attach(Module.findExportByName(null, "connect"), { onEnter: function(args) { console.log("[connect] Called"); var sockaddr = args[1]; var family = sockaddr.readU16(); if (family == 2) { // AF_INET var port = sockaddr.add(2).readU16(); var ip = sockaddr.add(4).readByteArray(4); console.log(" Port: " + ((port >> 8) | ((port & 0xff) << 8))); console.log(" IP: " + new Uint8Array(ip).join(".")); } }, onLeave: function(retval) { console.log(" Return: " + retval); } });
bash# Run with Frida frida -f ./binary -l hook_connect.js --no-pause
javascript// trace_libcurl.js var libcurl = Process.findModuleByName("libcurl.so.4"); if (libcurl) { libcurl.enumerateExports().forEach(function(exp) { if (exp.type === "function") { Interceptor.attach(exp.address, { onEnter: function(args) { console.log("[" + exp.name + "] called"); } }); } }); }
javascript// dump_memory.js var base = Module.findBaseAddress("binary"); console.log("Base: " + base); // Dump region var data = base.add(0x1000).readByteArray(256); console.log(hexdump(data, { offset: 0, length: 256 }));
Use Docker for cross-arch execution when native QEMU unavailable.
bashdocker run --rm --platform linux/arm/v7 \ -v ~/code/samples:/work:ro \ arm32v7/debian:bullseye-slim \ sh -c ' # Fix linker path mismatch (common issue) ln -sf /lib/ld-linux-armhf.so.3 /lib/ld-linux.so.3 2>/dev/null || true # Install dependencies if needed (check rabin2 -l output) apt-get update -qq && apt-get install -qq -y libcap2 libacl1 2>/dev/null # Run with library debug output (strace alternative) LD_DEBUG=libs /work/binary args '
bashdocker run --rm --platform linux/arm64 \ -v ~/code/samples:/work:ro \ arm64v8/debian:bullseye-slim \ sh -c 'LD_DEBUG=libs /work/binary args'
bashdocker run --rm --platform linux/i386 \ -v ~/code/samples:/work:ro \ i386/debian:bullseye-slim \ sh -c '/work/binary args'
| Method | Works? | Alternative | |--------|--------|-------------| | strace | ❌ (ptrace not implemented) | LD_DEBUG=files,libs | | ltrace | ❌ (same reason) | Direct observation or Frida | | gdb | ✓ (with QEMU -g flag) | N/A |
bashLD_DEBUG=libs # Library search and loading LD_DEBUG=files # File operations during loading LD_DEBUG=symbols # Symbol resolution LD_DEBUG=bindings # Symbol binding details LD_DEBUG=all # Everything (verbose)
When emulation fails or device-specific behavior needed.
bash# On target device (via SSH/ADB) gdbserver :1234 ./binary # On host (with port forward) ssh -L 1234:localhost:1234 user@device & gdb-multiarch -q \ -ex "target remote localhost:1234" \ ./binary
bash# On target device strace -f -o /tmp/trace.log ./binary # Pull log scp user@device:/tmp/trace.log .
bashnsjail \ --mode o \ --chroot /sysroot \ --user 65534 \ --group 65534 \ --disable_clone_newnet \ --rlimit_as 512 \ --time_limit 60 \ -- /binary
bash# CPU time limit timeout 60 qemu-arm -L /sysroot -strace ./binary # Memory limit via cgroup (requires setup) cgexec -g memory:qemu_sandbox qemu-arm -L /sysroot ./binary
Before dynamic analysis, check for common anti-debugging/anti-analysis patterns:
bash# Check for anti-debug strings/imports strings -a binary | grep -Ei 'ptrace|anti|debugger|seccomp|LD_PRELOAD|/proc/self' # r2: Look for ptrace/prctl/seccomp imports r2 -q -c 'iij' binary | jq '.[].name' | grep -Ei 'ptrace|prctl|seccomp' # Common anti-analysis indicators: # - ptrace(PTRACE_TRACEME) - Prevent debugger attach # - prctl(PR_SET_DUMPABLE, 0) - Prevent core dumps # - seccomp - Syscall filtering # - /proc/self/status checks - Detect TracerPid
bash# If native execution possible: strace -f ./binary 2>&1 | grep -E 'ptrace|prctl|seccomp|/proc/self'
| Pattern | Detection | Bypass | |---------|-----------|--------| | ptrace(TRACEME) | Returns EPERM if debugger attached | Patch call to NOP, use QEMU | | /proc/self/status check | Reads TracerPid field | Use QEMU (no /proc emulation) | | Timing checks | gettimeofday/rdtsc loops | Single-step with GDB, patch checks | | Self-checksum | Reads own binary/memory | Compute expected checksum, patch |
When anti-analysis detected: Prefer QEMU-strace over GDB (fewer detection vectors), or patch checks in r2 before execution.
| Error | Cause | Solution | |-------|-------|----------| | Unsupported syscall | QEMU limitation | Try Qiling or on-device | | Invalid ELF image | Wrong arch/sysroot | Verify file output | | Segfault at 0x0 | Missing library | Check ldd equivalent | | QEMU hangs | Blocking on I/O | Add timeout, check strace | | Anti-debugging | Detection code | Use Frida stalker mode | | exec format error in Docker | binfmt not registered | Run tonistiigi/binfmt --install arm | | ld-linux.so.3 not found | Linker path mismatch | Create symlink in container | | libXXX.so not found | Missing dependency | apt install in container | | Empty mount in Docker | Colima /tmp issue | Use ~/ path instead of /tmp/ | | ptrace: Operation not permitted | strace in QEMU | Use LD_DEBUG instead |
Record observations as structured data:
json{ "experiment": { "id": "exp_001", "method": "qemu_strace", "command": "qemu-arm -L /usr/arm-linux-gnueabihf -strace ./binary", "duration_secs": 12, "exit_code": 0 }, "syscall_summary": { "network": { "socket": 2, "connect": 1, "send": 5, "recv": 3 }, "file": { "openat": 4, "read": 12, "close": 4 } }, "network_connections": [ { "family": "AF_INET", "address": "192.168.1.100", "port": 8443, "protocol": "tcp" } ], "files_accessed": [ {"path": "/etc/config.json", "mode": "read"}, {"path": "/var/log/app.log", "mode": "write"} ], "hypotheses_tested": [ { "hypothesis_id": "hyp_001", "result": "confirmed", "evidence": "connect() to 192.168.1.100:8443 observed" } ] }
After dynamic analysis, record findings for episodic memory:
[BINARY-RE:dynamic] {filename} (sha256: {hash})
Execution method: {qemu-strace|qemu-gdb|frida|on-device}
DECISION: Approved execution with {sandbox_config} (rationale: {why_safe})
Runtime observations:
FACT: Binary reads {path} (source: strace openat)
FACT: Binary connects to {ip}:{port} (source: strace connect)
FACT: Binary writes to {path} (source: strace write)
FACT: Function {addr} receives args {values} at runtime (source: gdb)
Syscall summary:
Network: {socket|connect|send|recv counts}
File: {open|read|write|close counts}
Process: {fork|exec|clone counts}
HYPOTHESIS UPDATE: {confirmed or refined theory} (confidence: {new_value})
Confirmed by: {runtime observation}
Contradicted by: {if any}
New questions:
QUESTION: {runtime-discovered unknown}
Answered questions:
RESOLVED: {question} → {runtime evidence}[BINARY-RE:dynamic] thermostat_daemon (sha256: a1b2c3d4...)
Execution method: qemu-strace
DECISION: Approved execution with network-blocked sandbox (rationale: static analysis shows outbound only, no server)
Runtime observations:
FACT: Binary reads /etc/thermostat.conf at startup (source: strace openat)
FACT: Binary attempts connect to 93.184.216.34:443 (source: strace connect)
FACT: Binary writes to /var/log/thermostat.log (source: strace openat O_WRONLY)
FACT: sleep(30) called between network attempts (source: strace nanosleep)
Syscall summary:
Network: socket(2), connect(1-blocked), send(0), recv(0)
File: openat(4), read(12), write(8), close(4)
Process: none
HYPOTHESIS UPDATE: Telemetry client confirmed - reads config, attempts HTTPS to thermco servers every 30s (confidence: 0.95)
Confirmed by: connect() to expected IP, sleep(30) timing, config file read
Contradicted by: none
Answered questions:
RESOLVED: "Does it actually phone home?" → Yes, connect() to 93.184.216.34:443 observed
RESOLVED: "What files does it access?" → /etc/thermostat.conf (read), /var/log/thermostat.log (write)→ binary-re-synthesis to compile findings into report → Additional static analysis if new functions identified → Repeat with different inputs if behavior varies
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 31,993 | 12,676 | -60% | 1 | 1 | 0% | 3,248 | 6,996 | +115% | 0 | 0 | — |
case-02 | fail→pass | 24,067 | 14,388 | -40% | 1 | 1 | 0% | 4,355 | 7,444 | +71% | 0 | 0 | — |
case-03 | fail→fail | 13,524 | 15,136 | +12% | 1 | 1 | 0% | 2,350 | 7,439 | +217% | 0 | 0 | — |
case-04 | pass→pass | 21,494 | 16,944 | -21% | 1 | 1 | 0% | 3,501 | 7,504 | +114% | 0 | 0 | — |
case-05 | fail→pass | 8,984 | 7,680 | -15% | 1 | 1 | 0% | 1,494 | 5,884 | +294% | 0 | 0 | — |
case-06 | pass→pass | 6,491 | 2,655 | -59% | 1 | 1 | 0% | 1,111 | 5,064 | +356% | 0 | 0 | — |
case-07 | fail→pass | 16,600 | 9,336 | -44% | 1 | 1 | 0% | 2,849 | 6,239 | +119% | 0 | 0 | — |
case-08 | fail→pass | 6,011 | 5,055 | -16% | 1 | 1 | 0% | 1,134 | 5,441 | +380% | 0 | 0 | — |
case-09 | pass→pass | 7,271 | 2,839 | -61% | 1 | 1 | 0% | 1,158 | 5,120 | +342% | 0 | 0 | — |
case-10 | pass→pass | 3,728 | 3,867 | +4% | 1 | 1 | 0% | 686 | 5,233 | +663% | 0 | 0 | — |
case-11 | pass→pass | 4,497 | 3,626 | -19% | 1 | 1 | 0% | 765 | 5,314 | +595% | 0 | 0 | — |
case-12 | fail→pass | 11,112 | 6,333 | -43% | 1 | 1 | 0% | 2,180 | 5,891 | +170% | 0 | 0 | — |
case-13 | fail→fail | 4,964 | 2,384 | -52% | 1 | 1 | 0% | 821 | 5,056 | +516% | 0 | 0 | — |
case-14 | fail→fail | 8,235 | 7,038 | -15% | 1 | 1 | 0% | 1,353 | 5,812 | +330% | 0 | 0 | — |
case-15 | fail→pass | 6,798 | 4,464 | -34% | 1 | 1 | 0% | 1,109 | 5,418 | +389% | 0 | 0 | — |
case-16 | pass→pass | 5,176 | 5,470 | +6% | 1 | 1 | 0% | 826 | 5,163 | +525% | 0 | 0 | — |
case-17 | fail→pass | 16,232 | 3,221 | -80% | 1 | 1 | 0% | 1,378 | 5,173 | +275% | 0 | 0 | — |
case-18 | pass→pass | 8,028 | 5,800 | -28% | 1 | 1 | 0% | 1,358 | 5,571 | +310% | 0 | 0 | — |
case-19 | pass→pass | 8,460 | 7,507 | -11% | 1 | 1 | 0% | 1,565 | 6,044 | +286% | 0 | 0 | — |
case-20 | pass→pass | 16,422 | 12,979 | -21% | 1 | 1 | 0% | 2,388 | 6,600 | +176% | 0 | 0 | — |
case-21 | fail→pass | 19,262 | 15,734 | -18% | 1 | 1 | 0% | 3,162 | 7,258 | +130% | 0 | 0 | — |
case-22 | fail→pass | 5,320 | 4,288 | -19% | 1 | 1 | 0% | 1,046 | 5,529 | +429% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 21 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +45 percentage points is the difference between those two pass rates over the 21 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.