选择 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 调试器,可以进行动态调试和静态分析,深入了解程序的结构和运行过程,但这章未对源程序进行修改,只是在调试器中改变了标志寄存器的值。