Dynamic Analysis (Phase 4)
Purpose
Observe actual runtime behavior. Verify hypotheses from static analysis. Capture data that's only visible during execution.
Human-in-the-Loop Requirement
CRITICAL: All execution requires human approval.
Before running ANY binary:
- Confirm sandbox configuration is acceptable
- Verify network isolation if required
- Document what execution will attempt
- Get explicit approval
Platform Support Matrix
| 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 |
macOS Docker Setup (One-Time)
# 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
Docker Mount Best Practices
CRITICAL: On Colima, /tmp mounts often fail silently. Always use home directory paths:
# ✅ 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 ...
Analysis Options
| 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 |
Option A: QEMU User-Mode with Syscall Trace
Safest approach - runs in isolation with syscall logging.
Setup
# 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
Sysroot Selection
| 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 |
Environment Control
# 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
Syscall Analysis
Strace output patterns to watch:
# 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
Option B: QEMU + GDB for Deep Debugging
Attach debugger for instruction-level control.
Launch Binary Under GDB
# 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 Commands for RE
# 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
GEF Enhancements
With GEF loaded, additional commands:
gef> vmmap # Memory layout
gef> checksec # Security features
gef> context # Full state display
gef> hexdump qword $sp 10 # Better hex dump
gef> pcustom # Structure definitions
Batch Debugging Script
# 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
Option C: Frida for Function Hooking
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:
# On target device:
./frida-server &
# On host:
frida -H device:27042 -f ./binary -l hook.js --no-pause
Basic Hook
// 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);
}
});
# Run with Frida
frida -f ./binary -l hook_connect.js --no-pause
Tracing All Calls to Library
// 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");
}
});
}
});
}
Memory Inspection
// 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 }));
Option D: Docker-Based Cross-Architecture (macOS)
Use Docker for cross-arch execution when native QEMU unavailable.
ARM32 Binary on macOS
docker 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
'
ARM64 Binary on macOS
docker run --rm --platform linux/arm64 \
-v ~/code/samples:/work:ro \
arm64v8/debian:bullseye-slim \
sh -c 'LD_DEBUG=libs /work/binary args'
x86 32-bit Binary on macOS
docker run --rm --platform linux/i386 \
-v ~/code/samples:/work:ro \
i386/debian:bullseye-slim \
sh -c '/work/binary args'
Tracing Limitations in Docker/QEMU User-Mode
| 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 |