binary-re-dynamic-analysisSkill binary-re-dynamic-analysis

这是一个用于二进制逆向工程的动态分析指南,包含如何在不同平台上运行、跟踪和调试二进制文件的详细步骤和命令。关键词包括:'run binary', 'execute', 'debug', 'trace syscalls', 'set breakpoint', 'qemu', 'gdb', 'frida', 'strace', 'watch memory'。

逆向工程 1 次安装 8 次浏览 更新于 3/1/2026

二进制逆向动态分析(Phase 4)

目的

观察实际的运行时行为。验证静态分析的假设。捕获仅在执行期间可见的数据。

人工介入要求

CRITICAL: 所有执行都需要人工批准。

在运行任何二进制文件之前:

  1. 确认沙箱配置是否可接受
  2. 如有需要,验证网络隔离
  3. 记录将尝试执行的内容
  4. 获取明确的批准

平台支持矩阵

主机平台 目标架构 方法 复杂度
Linux x86_64 ARM32/64, MIPS 原生 qemu-user
Linux x86_64 x86-32 原生或 linux32
macOS (任何) ARM32/64 Docker + binfmt 中等
macOS (任何) x86-32 Docker --platform linux/i386 中等
Windows 任何 WSL2 → Linux 方法 中等

macOS Docker 设置(一次性)

# 启动 Docker 运行时(Colima, Docker Desktop 等)
colima start

# 注册 ARM 仿真处理器(需要特权模式)
docker run --rm --privileged --platform linux/arm64 \
  tonistiigi/binfmt --install arm

Docker 挂载最佳实践

CRITICAL: 在 Colima 上,/tmp 挂载经常默默失败。始终使用家目录路径:

# ✅ 好 - 使用家目录
docker run -v ~/code/samples:/work:ro ...

# ❌ 坏 - /tmp 挂载在 Colima 上可能失败
docker run -v /tmp/samples:/work:ro ...

分析选项

方法 隔离 粒度 最适合
QEMU -strace 系统调用级别 初始行为映射
QEMU + GDB 指令级别 详细调试
Docker 进程级别 在 macOS 上跨架构
Frida 中等 函数级别 无需重新编译即可钩子
设备上 全系统 当仿真失败时

选项 A: QEMU 用户模式与系统调用跟踪

最安全的方法 - 在隔离中运行并记录系统调用日志。

设置

# 验证 sysroot 存在
ls /usr/arm-linux-gnueabihf/lib/libc.so*

# ARM 32位执行
qemu-arm -L /usr/arm-linux-gnueabihf -strace -- ./binary

# ARM 64位执行
qemu-aarch64 -L /usr/aarch64-linux-gnu -strace -- ./binary

Sysroot 选择

二进制 ABI Sysroot 路径 QEMU 标志
ARM glibc 硬浮点 /usr/arm-linux-gnueabihf -L
ARM glibc 软浮点 /usr/arm-linux-gnueabi -L
ARM64 glibc /usr/aarch64-linux-gnu -L
ARM musl 需要自定义提取 -L

环境控制

# 设置环境变量
qemu-arm -L /sysroot \
  -E HOME=/tmp \
  -E USER=nobody \
  -E LD_DEBUG=bindings \
  -- ./binary

# 取消危险变量
qemu-arm -L /sysroot \
  -U LD_PRELOAD \
  -- ./binary

系统调用分析

Strace 输出模式要关注:

# 网络活动
openat.*socket
connect(.*AF_INET
sendto|send|write.*socket
recvfrom|recv|read.*socket

# 文件访问
openat.*O_RDONLY.*"/etc
openat.*O_WRONLY
stat|lstat.*"/

# 进程操作
execve
fork|clone

选项 B: QEMU + GDB 进行深度调试

附加调试器以进行指令级控制。

在 GDB 下启动二进制文件

# 以 GDB 服务器启动 QEMU
qemu-arm -g 1234 -L /usr/arm-linux-gnueabihf ./binary &

# 使用 gdb-multiarch 连接
gdb-multiarch -q \
  -ex "set architecture arm" \
  -ex "target remote :1234" \
  -ex "source ~/.gdbinit-gef.py" \
  ./binary

GDB 逆向工程命令

# 断点
break *0x8400              # 地址
break main                 # 符号
break *0x8400 if $r0 == 5  # 条件

# 执行控制
continue                   # 运行直到断点
stepi                      # 单步指令
nexti                      # 跳过调用
finish                     # 运行直到返回

# 检查
info registers             # 所有寄存器
x/20i $pc                  # 从 PC 反汇编
x/10wx $sp                 # 栈内容
x/s 0x12345                # 地址处的字符串

# 内存
find 0x8000, 0x10000, "pattern"  # 搜索内存
dump memory /tmp/mem.bin 0x8000 0x9000  # 提取区域

GEF 增强功能

使用 GEF 加载后,附加命令:

gef> vmmap                 # 内存布局
gef> checksec              # 安全特性
gef> context               # 全状态显示
gef> hexdump qword $sp 10  # 更好的十六进制转储
gef> pcustom               # 结构定义

批量调试脚本

# 创建 GDB 脚本
cat > analyze.gdb << 'EOF'
set architecture arm
target remote :1234
break main
continue
info registers
x/20i $pc
continue
quit
EOF

# 运行批量
gdb-multiarch -batch -x analyze.gdb ./binary

选项 C: Frida 用于函数钩子

无需修改二进制文件即可拦截函数调用。

⚠️ 架构限制: Frida 需要原生架构执行。它 不能 附加到 QEMU-user 目标。

场景 可行? 替代方案
本机二进制(x86_64 上的 x86_64) -
QEMU-user 下的跨架构 使用设备上的 frida-server
本机架构容器下的 Docker -
模拟的跨架构 Docker 使用设备上的 frida-server

对于跨架构 Frida,将 frida-server 部署到目标设备:

# 在目标设备上:
./frida-server &

# 在主机上:
frida -H device:27042 -f ./binary -l hook.js --no-pause

基本钩子

// 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);
  }
});
# 使用 Frida 运行
frida -f ./binary -l hook_connect.js --no-pause

跟踪所有库调用

// 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");
        }
      });
    }
  });
}

内存检查

// dump_memory.js
var base = Module.findBaseAddress("binary");
console.log("Base: " + base);

// 转储区域
var data = base.add(0x1000).readByteArray(256);
console.log(hexdump(data, { offset: 0, length: 256 }));

选项 D: 基于 Docker 的跨架构(macOS)

当原生 QEMU 不可用时,使用 Docker 进行跨架构执行。

macOS 上的 ARM32 二进制文件

docker run --rm --platform linux/arm/v7 \
  -v ~/code/samples:/work:ro \
  arm32v7/debian:bullseye-slim \
  sh -c '
    # 修复链接器路径不匹配(常见问题)
    ln -sf /lib/ld-linux-armhf.so.3 /lib/ld-linux.so.3 2>/dev/null || true

    # 如有需要,安装依赖项(检查 rabin2 -l 输出)
    apt-get update -qq && apt-get install -qq -y libcap2 libacl1 2>/dev/null

    # 使用库调试输出运行(strace 替代)
    LD_DEBUG=libs /work/binary args
  '

macOS 上的 ARM64 二进制文件

docker run --rm --platform linux/arm64 \
  -v ~/code/samples:/work:ro \
  arm64v8/debian:bullseye-slim \
  sh -c 'LD_DEBUG=libs /work/binary args'

macOS 上的 x86 32位二进制文件

docker run --rm --platform linux/i386 \
  -v ~/code/samples:/work:ro \
  i386/debian:bullseye-slim \
  sh -c '/work/binary args'

Docker/QEMU 用户模式中的跟踪限制

方法 可行? 替代方案
strace ❌ (ptrace 未实现) LD_DEBUG=files,libs
ltrace ❌ (同样的原因) 直接观察或 Frida
gdb ✓ (带 QEMU -g 标志) N/A

LD_DEBUG 选项(strace 替代)

LD_DEBUG=libs     # 库搜索和加载
LD_DEBUG=files    # 加载期间的文件操作
LD_DEBUG=symbols  # 符号解析
LD_DEBUG=bindings # 符号绑定详情
LD_DEBUG=all      # 一切(详细)

选项 E: 设备上的分析

当仿真失败或需要设备特定行为时。

通过 gdbserver 进行远程 GDB

# 在目标设备上(通过 SSH/ADB)
gdbserver :1234 ./binary

# 在主机上(带有端口转发)
ssh -L 1234:localhost:1234 user@device &
gdb-multiarch -q \
  -ex "target remote localhost:1234" \
  ./binary

远程 strace(如果可用)

# 在目标设备上
strace -f -o /tmp/trace.log ./binary

# 拉取日志
scp user@device:/tmp/trace.log .

沙箱配置

最小沙箱(nsjail)

nsjail \
  --mode o \
  --chroot /sysroot \
  --user 65534 \
  --group 65534 \
  --disable_clone_newnet \
  --rlimit_as 512 \
  --time_limit 60 \
  -- /binary

QEMU 与资源限制

# CPU 时间限制
timeout 60 qemu-arm -L /sysroot -strace ./binary

# 通过 cgroup 限制内存(需要设置)
cgexec -g memory:qemu_sandbox qemu-arm -L /sysroot ./binary

反分析检测

在动态分析之前,检查常见的反调试/反分析模式:

静态检测(执行前)

# 检查反调试字符串/导入
strings -a binary | grep -Ei 'ptrace|anti|debugger|seccomp|LD_PRELOAD|/proc/self'

# r2: 查找 ptrace/prctl/seccomp 导入
r2 -q -c 'iij' binary | jq '.[].name' | grep -Ei 'ptrace|prctl|seccomp'

# 常见反分析指标:
# - ptrace(PTRACE_TRACEME) - 防止调试器附加
# - prctl(PR_SET_DUMPABLE, 0) - 防止核心转储
# - seccomp - 系统调用过滤
# - /proc/self/status 检查 - 检测 TracerPid

运行时检测

# 如果可能进行本地执行:
strace -f ./binary 2>&1 | grep -E 'ptrace|prctl|seccomp|/proc/self'

缓解策略

模式 检测 绕过
ptrace(TRACEME) 如果附加了调试器则返回 EPERM 修补调用为 NOP,使用 QEMU
/proc/self/status 检查 读取 TracerPid 字段 使用 QEMU(无 /proc 模拟)
计时检查 gettimeofday/rdtsc 循环 使用 GDB 单步,修补检查
自我校验和 读取自己的二进制/内存 计算预期校验和,修补

当检测到反分析时: 优先选择 QEMU-strace 而不是 GDB(检测向量更少),或在执行前在 r2 中修补检查。


错误恢复

错误 原因 解决方案
Unsupported syscall QEMU 限制 尝试 Qiling 或设备上
Invalid ELF image 错误的架构/ sysroot 验证 file 输出
Segfault at 0x0 缺少库 检查 ldd 等效物
QEMU hangs 阻塞在 I/O 上 添加超时,检查 strace
Anti-debugging 检测代码 使用 Frida stalker 模式
exec format error 在 Docker 中 binfmt 未注册 运行 tonistiigi/binfmt --install arm
ld-linux.so.3 not found 链接器路径不匹配 在容器中创建符号链接
libXXX.so not found 缺少依赖 容器中的 apt install
Docker 中的空挂载 Colima /tmp 问题 使用 ~/ 路径而不是 /tmp/
ptrace: Operation not permitted QEMU 中的 strace 使用 LD_DEBUG 代替

输出格式

将观察结果记录为结构化数据:

{
  "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"
    }
  ]
}

知识日志记录

动态分析后,记录发现以便后续记忆:

[BINARY-RE:dynamic] {filename} (sha256: {hash})

执行方法:{qemu-strace|qemu-gdb|frida|on-device}
决策:批准执行 {sandbox_config}(理由:{why_safe})

运行时观察:
  FACT: 二进制文件在启动时读取 {path}(来源:strace openat)
  FACT: 二进制文件尝试连接到 {ip}:{port}(来源:strace connect)
  FACT: 二进制文件写入到 {path}(来源:strace openat O_WRONLY)
  FACT: 在运行时,函数 {addr} 接收参数 {values}(来源:gdb)

系统调用摘要:
  网络:{socket|connect|send|recv 计数}
  文件:{open|read|write|close 计数}
  进程:{fork|exec|clone 计数}

假设更新:{确认或细化的理论}(信心:{new_value})
  确认由:{运行时观察}
  相矛盾:{如果有}

新问题:
  QUESTION: {运行时发现的未知}

已回答问题:
  RESOLVED: {问题} → {运行时证据}

示例日志条目

[BINARY-RE:dynamic] thermostat_daemon (sha256: a1b2c3d4...)

执行方法:qemu-strace
决策:批准执行网络阻塞沙箱(理由:静态分析显示仅出站,无服务器)

运行时观察:
  FACT: 二进制文件在启动时读取 /etc/thermostat.conf(来源:strace openat)
  FACT: 二进制文件尝试连接到 93.184.216.34:443(来源:strace connect)
  FACT: 二进制文件写入到 /var/log/thermostat.log(来源:strace openat O_WRONLY)
  FACT: 在运行时,sleep(30) 在网络尝试之间被调用(来源:strace nanosleep)

系统调用摘要:
  网络:socket(2), connect(1-blocked), send(0), recv(0)
  文件:openat(4), read(12), write(8), close(4)
  进程:无

假设更新:遥测客户端确认 - 读取配置,每 30s 尝试 HTTPS 到 thermco 服务器(信心:0.95)
  确认由:预期 IP 的 connect(),sleep(30) 定时,配置文件读取
  相矛盾:无

已回答问题:
  RESOLVED: "它真的打电话回家吗?" → 是的,观察到 connect() 到 93.184.216.34:443
  RESOLVED: "它访问了哪些文件?" → /etc/thermostat.conf(读取),/var/log/thermostat.log(写入)

下一步

binary-re-synthesis 将发现编译成报告 → 如果识别出新功能,则进行额外的静态分析 → 如果行为变化,则用不同输入重复