7、动态分析工具(上):OllyDbg/x64dbg基础使用

动态分析,说白了就是让程序跑起来,然后我们在旁边盯着它的一举一动。静态分析像是在看剧本,动态分析则是直接看演员在台上表演。我个人觉得,真正有意思的逆向工作,往往是从动态分析开始的。

今天我们要聊的两把利器——OllyDbg和它的接班人x64dbg。嗯,这里要注意,OllyDbg已经有些年头了,但它处理32位程序的能力依然很能打。而x64dbg,顾名思义,是专门为64位世界准备的。我在项目中遇到过不少次,拿着OllyDbg去调64位的程序,结果打不开,那种尴尬你懂的。

7.1 界面布局:别被密密麻麻的窗口吓到

第一次打开调试器,你可能会觉得眼花缭乱。别慌,核心就四个窗口:

窗口名称 作用 我的习惯
反汇编窗口 显示CPU指令,这是主战场 我一般会把它拉大,占屏幕一半
寄存器窗口 显示CPU寄存器的当前值 重点关注EAX/RAX,返回值常在这里
堆栈窗口 显示函数调用栈和局部变量 看函数参数时必看这里
内存转储窗口 查看任意内存地址的内容 字符串、数据结构都在这

你想想看,这四个窗口其实对应了程序的四个维度:代码在做什么(反汇编)、CPU的状态(寄存器)、调用关系(堆栈)、数据长什么样(内存)。搞懂这四块,你就掌握了调试器的命脉。

我的小技巧:在x64dbg里,按Ctrl + 鼠标滚轮可以缩放字体大小。年纪大了眼睛花,这个功能救了我很多次。

7.2 断点:让程序在你想停的地方停下来

断点就是给程序设个路障。程序跑到这里,就会乖乖停下来等你检查。OllyDbg和x64dbg都支持好几种断点,但最常用的就三种:

7.2.1 普通断点(INT3断点)

这是最基础的断点。调试器会在你指定的地址写入一个0xCC指令(也就是INT3中断)。程序执行到这里,就会触发异常,调试器就接管了控制权。

怎么设?简单:在反汇编窗口找到你想停的那行指令,按F2。再按一次F2取消。就这么简单。

避坑指南:我曾经在调试一个加壳程序时,设了普通断点后程序直接崩溃了。后来才发现,壳程序会校验代码完整性,发现0xCC就自杀了。这时候就得用硬件断点。

7.2.2 内存断点

内存断点不是停在某条指令上,而是停在某个内存被访问的时候。比如你想知道某个全局变量什么时候被修改了,就可以在它身上设内存断点。

操作步骤:

  1. 在内存转储窗口找到目标地址
  2. 选中一段内存区域
  3. 右键 → 断点 → 内存访问/内存写入

内存断点分两种:内存访问断点(读或写都触发)和内存写入断点(只有写才触发)。我个人习惯先用写入断点,因为大部分时候我们关心的是谁改了数据。

注意:内存断点很消耗性能。设太多的话,程序会慢得像蜗牛。我一般只同时设1-2个内存断点。

7.2.3 硬件断点

硬件断点是个好东西。它利用CPU的调试寄存器(DR0-DR3)来实现,不会修改程序代码,所以能绕过很多反调试检测。

在x64dbg里,选中一条指令,右键 → 断点 → 硬件断点,然后选择执行、写入或访问。硬件断点最多只能设4个,因为CPU只有4个调试寄存器。

为什么会这样?因为硬件断点是在CPU层面实现的,不是软件模拟的。所以它更快、更隐蔽,但也更稀缺。

断点类型 优点 缺点 适用场景
普通断点 无限数量,操作简单 容易被检测,修改代码 调试自己的程序
内存断点 监控数据访问 性能差,只能设一个 追踪数据流向
硬件断点 隐蔽,不修改代码 最多4个 对抗反调试

7.3 单步执行:一步一步看程序怎么走

设好断点后,程序停下来了。接下来怎么走?单步执行就是让你一步一步地看程序执行。

常用的几个快捷键:

  • F7(步进):执行一条指令,如果遇到CALL就进入函数内部
  • F8(步过):执行一条指令,如果遇到CALL就整个执行完,不进去
  • F9(运行):直接运行到下一个断点
  • Ctrl + F9(执行到返回):一直运行到当前函数返回

我刚开始学逆向的时候,有个坏习惯:不管遇到什么CALL都按F7进去看。结果经常钻进一个几百行的系统函数里,半天出不来。后来学乖了,先按F8步过,如果觉得这个函数有问题,再重新调试进去看。

经验之谈:调试时多观察寄存器窗口。比如你按F8步过一条CALL后,EAX的值变了,那这个函数很可能返回了一个重要结果。这时候就该考虑重新调试,用F7进去看看了。

7.4 修改内存与寄存器:调试器不只是看的,还能改

调试器最强大的地方在于——你可以改变程序的运行状态。这就像你在看一部电影,突然可以改写剧本了。

7.4.1 修改寄存器

在寄存器窗口,双击某个寄存器的值,就可以直接修改。比如你想跳过某个条件判断,可以把ZF标志位从0改成1。

我记得有一次,我在分析一个软件的注册验证。它把用户输入的注册码经过一系列计算后,跟正确的注册码比较。比较结果存在EAX里,如果是0就表示失败。我直接在比较指令后面把EAX改成1,程序就认为注册成功了。虽然这只是临时绕过,但帮我确认了验证逻辑的位置。

7.4.2 修改内存

在内存转储窗口,你可以直接修改任意地址的数据。右键 → 编辑,或者直接按Ctrl + E。

举个例子:

// 假设程序里有个判断
if (is_admin == 0) {
    printf("你没有管理员权限\n");
    exit(0);
}

// 在内存中找到is_admin变量
// 地址: 0x004A3B10
// 当前值: 00
// 改成: 01
// 程序就认为你是管理员了

修改内存时要注意字节序。x86是小端序,比如你要写一个DWORD值0x12345678,在内存里看到的是78 56 34 12。我第一次改内存时没注意这个,折腾了半天才发现是顺序搞反了。

警告:修改内存和寄存器只是临时生效。程序重启后,一切都会恢复原样。如果你想永久修改,得把修改后的代码或数据写回到可执行文件里——那就是另一门学问了(静态补丁)。

7.5 知识体系总览

说了这么多,我们来梳理一下本章的核心逻辑。下面这张图可以帮你快速建立知识框架:

动态分析工具核心知识体系 OllyDbg / x64dbg 界面布局 反汇编窗口 寄存器窗口 堆栈窗口 内存转储窗口 断点机制 普通断点 INT3, 无限数量 内存断点 监控数据访问 硬件断点 最多4个, 隐蔽 单步执行 F7 步进 进入CALL内部 F8 步过 跳过CALL 修改内存与寄存器 修改寄存器 改变程序流程 修改内存 改变数据值

这张图把今天讲的内容串起来了。你想想看,界面布局是操作基础,断点是暂停程序的手段,单步执行是精细控制,修改能力则是逆向的终极武器。四者缺一不可。

好了,今天的内容就到这里。OllyDbg和x64dbg的功能远不止这些,但掌握了界面、断点、单步执行和修改能力,你已经可以应付大部分逆向场景了。剩下的,就是在实战中慢慢积累经验了。