$ cat ~ / posts /reverse /lecture08 6.1k Words ~ 23 Mins
cover.png
逆向工程基本原理08

#逆向工程基本原理08

exdoubled Lv5

动态分析技术概览

逆向工程中常见的两类分析方法:

  • 静态分析:不运行程序,直接分析二进制文件、反汇编代码、反编译代码、字符串、导入表等信息
  • 动态分析:运行程序,在运行过程中观察程序的真实状态,例如变量值、寄存器值、分支选择、API 调用、系统调用、内存访问等

两者是互补关系。

静态分析适合先建立整体认识,例如函数结构、控制流、字符串引用、导入函数和关键常量。动态分析适合验证静态分析得到的假设,尤其适合处理以下情况:

  • 静态代码过大,难以直接定位关键逻辑
  • 代码存在混淆、自修改、字符串加密等保护
  • 需要确认某条分支在真实输入下是否会被执行
  • 需要观察函数输入输出,而不关心函数内部全部实现
  • 需要修改寄存器、内存或返回值来验证程序行为

动态分析的核心问题是:如何让程序在关键位置停下来,如何观察现场,如何让程序继续以可控方式执行。

断点

断点是专门设置的程序暂停位置。程序执行到断点时,会触发异常,操作系统接管目标程序,并把控制权交给调试器。

断点命中后,分析人员可以:

  • 观察线程调用栈
  • 观察寄存器、栈、堆、全局数据等状态
  • 观察模块加载情况
  • 修改变量、寄存器或内存
  • 修改代码或跳转目标

断点工作流程

断点的一般工作流程:

  1. 分析人员通过调试器设置断点
  2. 程序运行到断点位置或满足断点条件
  3. CPU 触发异常,操作系统暂停目标程序
  4. 操作系统将目标程序状态交给调试器
  5. 分析人员在调试器中观察或修改程序状态
  6. 调试器令程序继续运行、单步执行或返回上层函数

可以概括为:

1
设置断点 -> 运行程序 -> 命中断点 -> 分析现场 -> 跟踪执行 -> 继续运行

断点分类

根据触发时机,断点可以分为:

  • 指令断点:目标指令被执行时触发
  • 数据断点:目标内存被读、写或执行时触发,也称 Watchpoint
  • 事件断点:特定事件发生时触发,例如库加载、程序初始化完成、键盘输入等

逆向时最常用的是指令断点和数据断点。

指令断点适合定位代码执行位置,例如在 mainprintfstrcmp 或某个反汇编地址处暂停。

数据断点适合定位谁访问了某块内存,例如输入字符串已经读入内存,但不知道后续由哪段代码处理,此时可以对该内存地址设置 watchpoint。

软件断点

软件断点通过修改目标指令实现。调试器把目标地址处的原始指令替换为特殊的中断指令,当 CPU 执行该指令时触发异常。

ARM/AArch64 中常见的中断相关指令包括:

  • BRK
  • SWI
  • BKPT
  • UND

例如在 0x9014 处设置指令断点,可以把原始指令临时替换为 brk

1
2
3
4
5
6
7
8
9
0x9000  adrp    x8, 0x20008
0x9004 ldr x1, [x8, 0]
0x9008 adrp x0, .L.str
0x900c add x0, x0, :lo12:.L.str
0x9010 bl __isoc99_scanf
0x9014 brk #0xf000
0x9018 adrp x0, .L.str.1
0x901c add x0, x0, :lo12:.L.str.1
0x9020 bl printf

执行到 brk 后,CPU 在用户态触发异常,再由内核引导调试器接管程序。

软件断点的特点:

  • 不依赖专用硬件,使用范围广
  • 常用于指令断点和事件断点
  • 会修改目标代码内存,因此可能被反调试逻辑检测

硬件断点

硬件断点通过 CPU 的专用调试寄存器实现,不需要直接改写目标代码。

AArch64 中与数据断点相关的寄存器包括:

  • DBGWVR<n>_EL1:保存被监控的内存地址
  • DBGWCR<n>_EL1:保存断点条件,例如读、写、执行和启用状态

例如监控地址 0x20008 的写访问:

1
2
DBGWVR<n>_EL1 = 0x20008
DBGWCR<n>_EL1 = enable + write

当程序执行类似下面的写内存指令时,硬件检测到访问地址满足条件,触发异常:

1
2
0x9000  adrp    x8, 0x20008
0x9004 str x1, [x8, 0]

硬件断点的特点:

  • 常用于数据断点
  • 不需要修改目标指令
  • 数量有限,依赖硬件支持

断点的典型用途

定位输入处理代码

如果已知输入数据所在内存地址,但不知道后续由哪段代码处理,可以对输入缓冲区设置数据断点。

1
2
3
4
5
(gdb) watch *0xaaaaaaacd200
(gdb) c
Hardware watchpoint 4: *0xaaaaaaacd200
Old value = 0
New value = -1431510320

命中后查看当前 PC 附近的指令:

1
2
3
4
5
6
(gdb) x/2i $pc-4
0xaaaaaaab769c <add_entry+76>: str x1, [x0]
=> 0xaaaaaaab76a0 <add_entry+80>: ldrb w3, [sp, #31]
(gdb) i r
x0 0xaaaaaaacd200
x1 0xaaaaaaace2d0

此时 0xaaaaaaab769c 附近的代码就是访问输入变量的代码。

观察复杂函数的输入输出

如果某个函数很复杂,可以先不分析内部实现,而是观察调用前后的寄存器和内存状态。

ARM64 调用约定中,整数和指针参数通常从 x0x7 传入,返回值通常在 x0

1
2
3
4
5
6
7
8
(gdb) b *0xaaaaaaab0f08
(gdb) b *0xaaaaaaab0f0c
(gdb) c
(gdb) i r $x0
x0 0x0
(gdb) c
(gdb) x/s $x0
0xaaaaaaace2a0: "pore.example.com"

根据输入和输出关系,可以推断该函数可能把数字 ID 转换为字符串。

修改程序状态

动态调试不仅能观察,还能修改程序状态。

例如在函数调用前修改第一个参数:

1
2
3
4
5
6
7
(gdb) b *0xaaaaaaab0f08
(gdb) b *0xaaaaaaab0f0c
(gdb) c
(gdb) set $x0 = 48
(gdb) c
(gdb) x/s $x0
0xaaaaaaace2a0: "/proc/self/cmdline"

这种方法适合快速测试函数在不同输入下的输出,不一定需要从程序外部构造完整输入。

调试器工作原理

调试器本质上是一个能够接管目标程序的工具。它通过操作系统提供的调试接口控制目标程序,在目标程序触发异常、系统调用或断点时暂停程序。

调试器暂停程序后,常见观察对象包括:

  • 寄存器:临时变量、函数参数、返回值
  • PC:当前执行指令位置
  • SP:当前栈顶
  • FP:当前栈帧基址
  • CPSR:条件标志和控制位
  • 调用链:当前函数从哪些函数调用进入
  • 内存映射:代码段、堆、栈、共享库的地址和权限

ptrace

Linux 上常用的调试器依赖 ptrace 系统调用。

1
2
3
4
5
6
#include <sys/ptrace.h>

long ptrace(enum __ptrace_request request,
pid_t pid,
void *addr,
void *data);

ptrace 提供一个程序接管另一个程序的能力:

  • 接管程序称为 tracer
  • 被接管程序称为 tracee
  • tracer 可以读取或修改 tracee 的寄存器、内存和执行状态
  • tracer 可以令 tracee 恢复执行、单步执行或继续到下一个事件

需要注意:

  • ptrace 的接管粒度是线程
  • 一个线程同一时间只能被一个 tracer 接管
  • 这个限制也是很多反调试技术的基础

ptrace 追踪关系

追踪关系的一般流程:

  1. 调试器调用 ptrace,声明要追踪目标程序
  2. 调试器调用 waitpid,等待目标程序状态变化
  3. 目标程序运行到断点、异常或系统调用位置
  4. 内核暂停目标程序,并把状态转发给调试器
  5. 调试器读取或修改目标程序状态
  6. 调试器再次调用 ptrace 让目标程序恢复执行
1
2
3
4
5
6
7
8
9
10
11
调试器 ptrace attach

调试器 waitpid 等待状态

目标程序触发断点/异常/syscall

内核暂停目标程序并通知调试器

调试器观察/修改寄存器和内存

调试器恢复目标程序执行

GDB

GDB 是基于 ptrace 的常见调试器。

基本工作流程:

  • GDB 通过 ptrace 请求监控目标程序
  • 目标程序发生断点、异常或中断时,GDB 接管程序
  • 分析人员通过 GDB 命令观察、修改程序状态
  • 分析人员令程序继续运行、单步执行、步过函数或运行到函数返回

本地调试:

1
2
gdb /bin/sh
gdb attach `pidof ls`

远程调试:

1
2
3
4
5
6
# 目标环境
gdbserver 127.0.0.1:1234 ./target

# 本地环境
gdb-multiarch target
(gdb) target remote 127.0.0.1:1234

远程调试适合目标环境无法方便运行完整 GDB 的情况,例如固件、开发板、模拟器和非完整 Linux 环境。

GDB 常见操作

GDB 命令是分析人员控制目标程序的主要接口。命令中经常包含表达式,例如寄存器、地址、指针和内存解引用。

例如:

1
x/s *0xdeadbeef

含义是把 0xdeadbeef 处保存的值作为字符串地址,并打印该字符串。

启动与连接

加载程序:

1
(gdb) file ./a.out

运行程序:

1
(gdb) run

连接远程目标:

1
(gdb) target remote localhost:1234

附加到已有进程:

1
gdb attach <pid>

断点命令

按函数名下断:

1
(gdb) b printf

按源码行下断:

1
(gdb) b 32

按地址下断:

1
(gdb) b *0x400800

设置条件断点:

1
(gdb) b *0x400800 if $x0 == 0

查看断点:

1
(gdb) info breakpoints

启用断点:

1
(gdb) enable 1

设置数据断点:

1
2
(gdb) watch *0xaaaaaaacd200
(gdb) info watchpoints

执行控制

继续运行到下一个断点:

1
(gdb) c

执行下一条源码语句:

1
(gdb) n

执行到当前函数返回:

1
(gdb) finish

直接从当前函数返回指定值:

1
(gdb) return 0

return 命令不会继续执行当前函数剩余代码,而是直接让当前函数以指定返回值返回。分析反调试逻辑时可以用它跳过检测函数。

寄存器和内存观察

查看主要寄存器:

1
(gdb) info registers

查看所有寄存器:

1
(gdb) info all-registers

查看指定寄存器:

1
2
(gdb) i r $x0
(gdb) i r $pc $sp $fp

打印内存:

1
2
3
(gdb) x/16gx $sp
(gdb) x/8i $pc
(gdb) x/s $x0

常见格式:

  • x/i:按指令反汇编
  • x/s:按字符串打印
  • x/gx:按 8 字节十六进制打印
  • x/wx:按 4 字节十六进制打印

修改寄存器:

1
(gdb) set $x1 = 456

修改内存:

1
(gdb) set {int}0x400000 = 0

调用栈和源码辅助

查看调用栈:

1
(gdb) bt

选择栈帧:

1
(gdb) frame 1

打印源码:

1
(gdb) list

查看局部变量:

1
(gdb) info locals

逆向场景中经常没有源码和调试符号,此时更依赖 x/idisas、寄存器和内存观察。

ARM64 动态调试实践

实验环境中经常需要在 x86 主机上调试 ARM64 程序,可以通过 qemu-aarch64gdb-multiarch 完成。

编译和运行 ARM64 程序

使用交叉编译器生成 AArch64 程序:

1
aarch64-linux-gnu-gcc -o a.out code.c

使用 QEMU 运行:

1
qemu-aarch64 -L /usr/aarch64-linux-gnu ./a.out

其中 -L /usr/aarch64-linux-gnu 用于指定 ARM64 用户态运行所需的动态库路径。

QEMU 开启远程调试端口

让 QEMU 在程序启动时等待 GDB 连接:

1
qemu-aarch64 -L /usr/aarch64-linux-gnu -g 1234 ./a.out

-g 1234 表示打开 1234 端口,程序会暂停等待远程调试器连接。

另开一个终端连接:

1
2
3
4
5
6
gdb-multiarch -q --nh \
-ex 'set architecture aarch64' \
-ex 'file a.out' \
-ex 'target remote localhost:1234' \
-ex 'layout split' \
-ex 'layout regs'

各参数含义:

  • set architecture aarch64:指定目标架构
  • file a.out:加载本地符号和 ELF 信息
  • target remote localhost:1234:连接 QEMU 暴露的调试端口
  • layout split:同时显示源码/汇编和命令窗口
  • layout regs:显示寄存器窗口

基本调试流程

一个常见流程:

1
2
3
4
5
6
7
8
(gdb) b main
(gdb) c
(gdb) disas
(gdb) b printf
(gdb) c
(gdb) i r $x0 $x1
(gdb) set $x1 = 456
(gdb) c

这个流程体现了动态分析的几个动作:

  • 先让程序停在稳定入口,例如 main
  • 反汇编当前函数,找到感兴趣的调用点
  • 对库函数或地址下断
  • 命中断点后观察参数寄存器
  • 修改参数寄存器
  • 继续执行并观察输出变化

GDB 脚本

手动调试适合探索,脚本适合重复执行。GDB 支持两类脚本:

  • 原生 GDB 脚本:多条 GDB 命令按行组合
  • GDB Python 脚本:通过 gdb 模块编写更复杂的自动化逻辑

脚本可以通过两种方式执行:

1
gdb -x gdb-script

或在 GDB 中执行:

1
(gdb) source gdb-script

原生 GDB 脚本

例如在 printf 处断下,修改第二个参数寄存器 x1,再继续执行:

1
2
3
4
5
6
b printf
commands 1
set $x1 = 456
c
end
c

这里有两个 c

  • commands 1 内部的 c:断点命中并修改寄存器后继续运行
  • 最后一行 c:脚本加载后立刻启动/恢复程序运行

自动打印函数输入输出

假设已知一个解密函数的入口和返回位置,可以用脚本自动打印输入 ID 和返回字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
file mirai.aarch64
set pagination off

b *0xaaaaaaab75e0
commands 1
i r $x0
c
end

b *0xaaaaaaab7648
commands 2
x/s $x0
c
end

run

入口断点打印 x0,用于观察传入的字符串 ID。返回断点打印 x0 指向的字符串,用于观察解密结果。

GDB Python 脚本

GDB Python 脚本可以定义断点类,并在断点命中时自动执行逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import gdb

class FuncStart(gdb.Breakpoint):
def stop(self):
x0_value = gdb.parse_and_eval('$x0')
print(f'ID: {x0_value}')
return False

class FuncEnd(gdb.Breakpoint):
def stop(self):
x0_value = int(gdb.parse_and_eval('$x0'))
data = gdb.selected_inferior().read_memory(x0_value, 16)
print(f'Decoded String: {data}')
return False

FuncStart('*0xaaaaaaab75e0')
FuncEnd('*0xaaaaaaab7648')
gdb.execute('run')

return False 表示断点触发后不让程序停住,而是执行脚本逻辑后继续运行。这样可以把人工断点操作变成自动日志。

插桩

插桩是在目标程序或执行环境中插入分析逻辑,用于收集运行时信息或控制程序状态。

在逆向工程中,插桩通常指对二进制程序进行修改或拦截。

调试和插桩的区别:

项目调试插桩
目标实时控制和分析执行过程收集执行过程中的数据
干预方式主动暂停、单步、修改状态通常自动记录或拦截
使用场景定位特定函数、观察分支、修改变量长期跟踪、日志记录、批量行为分析

两者并不冲突。调试适合交互式探索,插桩适合自动化和可重复分析。

插桩的关键概念

  • 插桩目标:被分析的软件
  • 插桩器:负责修改插桩目标的工具
  • 修改目标:代码、数据、进程状态、系统调用等被修改对象
  • 插桩逻辑:插入的新逻辑,用于记录信息或改变执行

评价插桩技术时,常看三个方面:

  • 粒度:系统调用级、函数级、基本块级、指令级
  • 适用范围:是否只适用于动态链接库函数,是否支持静态链接程序
  • 副作用:是否影响语义,是否显著降低性能,是否破坏寄存器状态

系统调用级插桩

系统调用是程序访问操作系统资源的入口,例如文件读写、网络通信、进程创建等。

通过系统调用级插桩,可以观察程序是否:

  • 打开敏感文件
  • 读写特定路径
  • 连接网络地址
  • 创建进程或线程
  • 修改文件权限

基于 ptrace 的系统调用级插桩流程:

1
2
3
4
5
6
7
8
9
10
11
声明要追踪的系统调用

目标程序执行 syscall

内核暂停目标程序

插桩器读取系统调用号和参数

插桩器记录或修改参数/返回值

目标程序继续执行

系统调用级插桩适合观察系统资源访问,但不能可靠拦截所有库函数行为。例如 malloc 是 libc 函数,不等同于一次固定的系统调用。

GOT 表函数级插桩

动态链接库函数调用通常通过 PLT/GOT 完成。

大致过程:

  1. 程序调用 malloc@plt
  2. PLT 表项读取 GOT 表项中的真实函数地址
  3. 程序跳转到 GOT 表项指向的地址

如果把 malloc 对应 GOT 表项改成钩子函数地址,那么后续对 malloc 的调用就会先进入钩子函数。

示例:

1
2
3
4
5
630 <malloc@plt>:
630: 90000110 adrp x16, 20000 <GOT_START>
634: f9401211 ldr x17, [x16, #32]
638: 91008210 add x16, x16, #0x20
63c: d61f0220 br x17

修改前:

1
GOT[malloc] = 0x30004   # malloc

修改后:

1
GOT[malloc] = 0x40004   # record_a

这样程序调用 malloc 时会先跳转到 record_a,由 record_a 打印参数寄存器或记录信息,再跳回真实 malloc

基于 GOT 表插桩的一般步骤:

  1. 获取程序在内存中的加载基地址
  2. 根据 ELF 头和程序头表定位 DYNAMIC
  3. 通过 DYNAMIC 段定位 .symtab.rel.plt.strtab
  4. 遍历动态链接函数表项,找到目标函数对应的重定位项
  5. 根据重定位项计算目标函数 GOT 表项地址
  6. 获取目标函数在 libc 中的真实地址
  7. 修改 GOT 表项为钩子函数地址
  8. 修改钩子函数中的函数指针,使其最终跳回真实目标函数

实验中的 Task 1 就是用 GDB 对指定函数进行 GOT 表插桩:

  • 学号最后一位为奇数时目标函数为 malloc
  • 学号最后一位为偶数时目标函数为 free
  • 钩子函数已在源码中给出,需要自行找出
  • 钩子函数负责打印传参寄存器,结束时通过函数指针跳回目标函数本体

需要注意延迟绑定:

  • 程序如果使用 lazy binding,第一次调用导入函数时 GOT 表项可能还指向动态解析逻辑
  • 如果太早修改 GOT 表,可能被首次解析覆盖
  • 实践时要确认 GOT 表项当前是否已经解析为真实 libc 函数地址

指令级插桩

系统调用级和函数级插桩不能覆盖所有需求。例如控制流扁平化反混淆时,常常需要记录基本块跳转边或间接跳转目标。

指令级插桩会修改目标指令,使插桩逻辑能获取每条关键指令执行时的上下文。

例如监控间接跳转目标:

1
BR X1      // 跳转到 X1 指定的地址

可以在 BR X1 前插入逻辑:

1
2
3
MOV X0, X1
BL Print
BR X1

这样每次执行间接跳转前都会打印目标地址。

基于跳板的指令级插桩

实际插桩时通常不直接在原位置插入多条指令,而是把目标指令改成跳转到插桩逻辑的跳板。

1
2
3
4
5
6
7
8
9
0007d0  ldr w0, [sp, #12]
0007d4 b 0x444 // trampoline
0007d8 ...

000444 b save_state
... // 功能性插桩逻辑
0004d4 b recover_state
0004d8 cmp w0, #0x0 // 原 0x7d4 指令
0004dc b 0x7d8 // 跳回原控制流

实现步骤:

  1. 记录目标待插桩指令
  2. 记录目标指令下一条指令地址
  3. 分配插桩逻辑内存空间
  4. 拷贝上下文保存和恢复逻辑
  5. 生成实际功能逻辑,例如打印寄存器
  6. 在插桩逻辑末尾执行原始指令
  7. 跳回原始指令的下一条地址
  8. 把原始位置指令改成跳板跳转

关键点是保存和恢复上下文。插桩逻辑不能破坏目标程序原本依赖的寄存器、栈和标志状态。

反动态分析

动态分析会改变程序运行环境,因此目标程序可以检测或干扰调试器。这类技术称为反动态分析,常见包括反调试、反插桩、反模拟执行等。

本节主要关注反调试。

检查 TracerPid

Linux 的 /proc/<pid>/status 中包含 TracerPid 字段。

未被调试时:

1
TracerPid: 0

被调试时,该字段可能变为调试器进程的 PID:

1
TracerPid: 16614

程序可以读取自己的状态文件,并根据 TracerPid 判断是否被调试:

1
2
3
4
5
6
int fd = open("/proc/self/status", O_RDONLY);
read(fd, buf, sizeof(buf));
char *p = strstr(buf, "TracerPid");
if (p != NULL && strcmp(parsed_value, "0") != 0) {
// debugger detected
}

对应的关键 API 或 syscall:

  • open
  • read
  • strstr
  • strcmp

逆向时可以在这些位置下断点,观察程序是否正在读取 TracerPid

接管异常处理

调试器依赖异常接管程序。目标程序也可以主动注册自己的异常处理逻辑,从而干扰调试器。

例如:

1
2
3
4
5
6
7
sigset_t sigs;

sigemptyset(&sigs);
sigaddset(&sigs, SIGINT);
sigprocmask(SIG_BLOCK, &sigs, NULL);

signal(SIGTRAP, &anti_gdb_entry);

含义:

  • 屏蔽 SIGINT,阻止外部中断影响程序
  • 注册自己的 SIGTRAP 处理函数,干预调试断点触发后的控制流

如果关键逻辑被放在异常处理函数里,简单跳过反调试逻辑可能导致程序功能缺失。

自 Trace

Linux 中一个线程同一时间只能被一个 tracer 接管。自 Trace 利用这一点实现反调试。

常见流程:

  1. 程序 fork 出子进程
  2. 子进程请求父进程 ptrace 自己
  3. 父进程成为子进程的 tracer
  4. 外部调试器再尝试调试子进程时失败

有些实现还会检查父进程是否真的是自己的 tracer,如果发现 TracerPid 异常,就退出或触发干扰逻辑。

对抗反调试

对抗反调试的一般步骤:

  1. 根据反调试原理定位检测逻辑
  2. 分析检测逻辑的上下文
  3. 修改程序数据或控制流
  4. 让检测结果变为“没有调试器”
  5. 继续执行并验证程序行为

定位方式:

  • 找反调试相关 API 或 syscall
  • ptraceopenreadstrstrstrcmpsignalsigprocmaskexit 下断
  • 观察是否访问 /proc/self/status
  • 观察是否处理 SIGTRAP

绕过方式:

  • 关闭反调试代码逻辑
  • 修改 API 或 syscall 的返回值
  • 修改检测结果的处理逻辑
  • 修改 open 参数,让程序读取伪造的 status 文件
  • 修改 strcmp 或检测函数返回值,使其认为 TracerPid 为 0
  • 在即将 exit 前断下,跳过退出路径

实践中基础反调试任务的思路:

1
2
3
4
5
6
7
8
9
目标:让程序继续运行并输出目标字符串

定位 ptrace 检测或 exit 路径

在检测返回值或 exit 前设置断点

修改返回值或跳过退出逻辑

继续运行并观察输出

例如:

1
2
3
4
5
(gdb) b ptrace
(gdb) c
(gdb) finish
(gdb) set $x0 = 0
(gdb) c

如果程序通过 ptrace 返回值判断调试器是否存在,可以在 ptrace 返回后把返回值改成期望的成功值。

也可以在即将退出的位置跳过:

1
2
3
4
(gdb) b exit
(gdb) c
(gdb) return
(gdb) c

具体修改点需要结合当前二进制的反汇编和实际控制流判断,不能只机械套用命令。

动态分析的一般流程

面对一个新的目标程序,可以按以下流程分析:

  1. 静态浏览程序结构
    • 查看字符串、导入函数、符号、函数列表、交叉引用
    • 初步定位输入、输出、加密、校验、网络、文件相关逻辑
  2. 选择动态入口
    • main、关键库函数、系统调用或可疑函数上下断点
    • 如果已知数据地址,优先考虑数据断点
  3. 运行到断点并观察现场
    • 查看 PC/SP/FP
    • 查看参数寄存器 x0-x7
    • 查看返回值寄存器 x0
    • 查看栈和目标内存
    • 查看调用栈
  4. 跟踪执行
    • 单步进入关键逻辑
    • 步过不关心的库函数
    • 使用 finish 跑到当前函数返回
    • 在返回点观察输出
  5. 修改状态验证假设
    • 修改寄存器参数
    • 修改内存数据
    • 修改返回值
    • 跳过检测或退出路径
  6. 自动化重复操作
    • 把稳定的断点和观察动作写成 GDB 脚本
    • 对高频函数考虑插桩
    • 对反调试逻辑单独写绕过脚本

动态分析不是替代静态分析,而是把静态分析中的猜测放到真实运行状态中验证。对于逆向任务,常见模式是先静态定位大概位置,再动态确认关键数据流,最后回到静态代码中整理完整逻辑。

$ discussion
# Comments
waline