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

这项技能涉及动态分析二进制文件,使用QEMU模拟、GDB调试和Frida挂钩等技术,进行系统调用跟踪、断点设置和内存检查,以观察二进制文件在实际运行时的行为,验证静态分析的假设,并捕获仅在执行期间可见的数据。关键词包括'运行二进制'、'执行'、'调试'、'跟踪系统调用'、'设置断点'、'QEMU'、'GDB'、'Frida'、'strace'、'监视内存'。

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

动态分析(第4阶段)

目的

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

人在循环中的要求

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

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

  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挂载最佳实践

关键: 在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单步,修补检查
自我校验和 读取自己的二进制/内存 计算预期校验和,修补

当检测到反分析时: 相对于GDB,更倾向于使用QEMU-strace(检测向量更少),或者在执行前在r2中修补检查。


错误恢复

错误 原因 解决方案
Unsupported syscall QEMU限制 尝试Qiling或设备上
Invalid ELF image 错误的架构//sysroot 验证file输出
Segfault at 0x0 缺少库 检查ldd等价物
QEMU hangs 阻塞在I/O上 添加超时,检查strace
Anti-debugging 检测代码 使用Frida跟踪模式
exec format error在Docker中 binfmt未注册 运行tonistiigi/binfmt --install arm
ld-linux.so.3未找到 链接器路径不匹配 在容器中创建符号链接
libXXX.so未找到 缺少依赖 容器中的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)
  进程:无

假设更新:遥测客户端确认 - 读取配置,每30秒尝试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将发现编译成报告 → 如果识别出新函数,进行额外的静态分析 → 如果行为变化,用不同输入重复