选择 IDA 调试器#
ida 支持多个调试器。

ida 支持的调试器
选择 Local Windows debugger 调试器
打开菜单栏调试器 - 调试器选项,可以设置一些调试器的功能。

ida 调试器选项
调试器界面功能#
勾选进程入口点暂停,并选择事件条件,点击确定。

对之前的关键代码 jinx 重命名及改色

修改后如下,按 X 键查看哪里引用了这个函数:

对引用点进行着色。

在 0x00401243 处设置断点,鼠标放到这一行,右键 - 添加断点,添加断点后变成红色背景。

通过菜单栏调试器-启动进程(F9),开启调试。如果在本地调试可执行程序会弹出如下警告窗口。

注:
在加载器分析程序的时候,程序不会在本地执行,但是调试则不是。如果程序是一个病毒或者共他危险的恶意软件时,需要特别的当心。这时候需要使用远程调试器 (REMOTE DEBUGGER)在虛拟机种执行程序。
点击是继续调试。
由于设置了让调试器在入口处暂停,如下图,ida 暂停在入口处(0x00401000)。

调整右上角的通用寄存器和标志寄存器窗口,以便查看。

上图中,可以看到这些寄存器的值。调整正常之后,打开菜单栏窗口-保存桌面并勾选default选项,保存为调试器默认的窗口设置。那么之后,运行调试器的时候,程序按照默认设置运行,如需修改也可以。
在通用寄存器下方可放置堆栈视图。

左侧和最下方是反汇编及 16 进制窗口。

在反汇编视图下方是当前的内存地址以及文件偏移(FILE OFFSET),这个偏移是指可执行文件偏移。

在 ida 中,默认 G 键是转向一个内存地址的快捷键,按 G 输入 0x401389,转向之前设置的断点。

所有静态分析、重命名等改变的内容都会保存。

打开菜单栏视图 - 打开子视图 - 段,发现加载器加载了 3 个区段。

加载了 0x401000 上的 code 字段,以及下方的 data 和.idata 字段,对这些区段的修改都会保存,除此之外的修改都不会保存,因为他们是调试器加载的区段,不会保存到 IDA 数据库中。
上图中的 L 标志,表示这个程序被加载器和调试器同时加载,既能实施静态分析,同时在动态调试时也不会丢失静态分析的信息。
下面介绍了一些小工具栏。
1、菜单栏 view(视图)-toolbars(工具类)-jump(跳转),再通过保存桌面功能将它设置为默认启用。

按返回键可以返回之前的程序入口处。

在菜单栏 debugger(调试器)-breakpoints(断点)-breakpoint list(断点列表)中,能够查看所有的程序断点。

点击任意一处就可以跳转到对应的断点处。
条件跳转指令与标志寄存器#
目前调试器执行到了程序入口,并且设置了两个断点,按 F9 继续运行。

此时运行了 crackme.exe 程序,打开目标程序 Help-register 菜单栏输入用户名和密码。

点击 ok

上图中,左边闪烁的红色箭头表示程序继续执行的路径,此时 eax 的值是 0x6c。

0x6c 转换为字符串就是 l。


上图中,al 寄存器与 0x41 进行比较,判断是否小于 0x41(A),下方的绿色代码块中 al 寄存器也和 0x5a 进行了比较。
| asm | condition | operation |
|---|---|---|
| JA | z=0 and c=0 | jump if above(如果大于则跳转) |
| JAE | c=0 | jump if above or equal(如果大于或等于则跳转) |
| JB | c=1 | jump if below (如果低于则跳转) |
| JBE | z=1 or c=1 | jump if below or equal(如果小于等于则跳转) |
| JC | c=1 | jump if carry(如果进位则跳转) |
| JECXZ | ecx=0 | jump if ecx is 0 (如果 ecx 为 0,则跳转) |
| JE | z=1 | jump if equal(如果相等就跳转) |
| JZ | z=1 | jump if zero (如果为零则跳转) |
| JNE | z=0 | jump if not equal (如果不相等就跳转) |
| JNZ | z=0 | jump if not zero (如果非零则跳转) |
| JO | 超出范围 | jump if overflow |
| JP | 有偶数个 1 位(操作结果中二进制中 1 的个数,01110000) | jump if parity |
| JPE | 偶数校验 | jump if parity even |
| JNP | 没有偶数个 1 位 | jump if not parity |
| JPO | 奇数校验 | jump if parity odd |
| JS | 符号位为 1 | jump if sign(如果有标志则跳转) |
| JNS | 符号位为 0 | jump if not sign(如果没有标志则跳转) |
| JL/JNGE | 符号位与溢出位相同 | jump if less or not greater/equal |
| JLE/JNG | z=1 or 符号位与溢出位相同 | jump if less or equal/not greater |
| JG/JNLE | z=0 and 符号位与溢出位相同 | jump is greater/not less or equal |
条件跳转指令及跳转条件
下面转到 ida 标志寄存器视图,如下图所示。

根据表格可知,如果 CF=0,就不跳转,程序往绿色代码块执行,那么触发这个 C 标志的数学条件是什么呢?
C 标志代表无符号整数运算有错误这一信息,如果把 cmp 指令看作是不保存结果的减法算法,那么0x6c - 0x41 = 0x2b,结果是正数,如果 al 是 0x30,那么0x30 - 0x41 = -0x11。

-0x11 是一个负数,程序只能继续使用它对应的 16 进制运行。

如上图所示,-0x11 的 16 进制是 0xffffffef,10 进制是 4294967279,这个值很大,而且0x30-0x41也不会是正数。
那么如何知道操作中有没有考虑正负号呢。这取决于使用的跳转类型,JB 是用于无符号数比较后的跳转指令,对应的有符号数的指令是 JL。
当使用 JB 指令时,通常是用它来检测无符号整数是否小于某个值。如果运算结果产生了借位,就表示第二个操作数(被比较数)比第一个操作数(比较数)大,此时 JB 标志位为 1,就可以进行跳转。否则,如果运算结果没有产生借位,则 JB 标志位为 0,即不满足条件,就不进行跳转。
示例:
mov al, 150 ;把150赋值给 al
cmp al, 255 ;比较 al 和 255
jb smaller ;如果 al小于255,就跳转到smaller标签处
;执行其他操作
smaller:
;如果 al小于等于255,就会跳转到这里
在这个示例中,如果 al 中的值小于 255,则 CF 标志位为 1,就会跳转到 smaller 标签处。否则,如果 al 中的值大于 255,则 CF 标志位为 0,不跳转,执行其他操作。(总而言之,如果al比255小,则跳转)
对于比较是否带符号需要通过它后面的条件跳转指令来确定。
无符号跳转
| 符号 | 描述 | 标志位 |
|---|---|---|
| JE/JZ | Jumps if equal or zero | zf |
| JNE/JNZ | Jumps if not equal or zero | zf |
| JA/JNBE | Jumps if above or not below or equal | zf,cf |
| JB/JNAE | Jumps if below or not above or equal | cf |
| JBE/JNA | Jumps if below or equal or not above | cf,af |
无符号跳转
上图中的指令都不考虑正负号,每一种跳转都有对应的有符号跳转。
| 符号 | 描述 | 标志位 |
|---|---|---|
| JE/JZ | Jumps if equal or zero | zf |
| JNE/JNZ | Jumps if not equal or zero | zf |
| JG/JNLE | Jumps if greater or not less or equal | zf,sf,of |
| JGE/JNL | Jumps if greater or not equal or less | sf,of |
| JL/JNGE | Jumps if less or not grater or equal | sf,of |
| JLE/JNG | Jumps if less or equal or not greater | zf,sf,of |
有符号跳转
上面两个图中,JE(是否相等)出现在两个图中,因为在这个特例中,符号并不重要,如果两个数相等,zf=1,意味着标志触发。
有符号跳转中JG = Jumps if greater,在无符号跳转中它对应的指令是JA,Jumps if above。
继续调试程序发现,ida 会在每一个设置的断点上暂停,执行了一个循环,每个循环会读取在目标程序输入的用户名的一个字符并和 0x41 进行比较,如果任何一个字符小于 0x41,就会报错,由于输入的是 lca,每个值都比 0x41 大,所以不显示报错。
尝试输入数字看看。

此时,就跳转到红色区块,也就是 jb 跳转的地方,因为第一个比较的字符是 2(0x32),2 肯定比 0x41 小。


同时触发了 C 标志位,CF=1,因为 0x32 - 0x41 无符号数相减,结果是负数,这将触发 C 标志位。

在 C 标志位右键单击,选择 Zero Value 将 C 标志清 0。


按 F9 继续进程,程序读取 22lca 的第二个字符,左侧红色箭头又开始闪烁。继续将 CF 标志位设为 0,之后的字符 lca 都会大于 0x41,不会触发标志位,程序都走左侧红色箭头。
字符检测完成后,程序来到最后一个跳转。

上图的 cmp 指令比较 eax 和 ebx,jz 判断比较是否相等,无符号,程序走向红色区块,因为这两个寄存器不相等。
![]()
由于不相等,所以 zf 标志未被触发。

如果手动触发 zf 标志,改变跳转方向,程序会转向绿色区块,显示注册成功。


SET IP#
set ip 只有在调试器模式下才有。
有时候也可以不直接修改跳转,可以将鼠标移动至想要执行的那个代码块上,右键单击,选择 SET EIP。
在 0x40124c 处,set eip,程序会从 0x40124c 处开始运行。

注:在 7.7 中这样操作,执行多次后报错如下:

提示 “尝试执行非法指令”


报错信息提示内存不可写,也许读取内容超出了范围。
总结#
通过使用 IDA 调试器,可以进行动态调试和静态分析,深入了解程序的结构和运行过程,但这章未对源程序进行修改,只是在调试器中改变了标志寄存器的值。