调试器
通过调试器可以跟踪一个进程的运行时状态,称之为动态分析技术。大多数调试器都具备基本功能:运行、暂停执行和单步执行,除此之外还包括:设置断点、修改寄存器和内存数据值、捕获发生在目标进程中的异常事件。
1.通用寄存器
1.1 X86通用寄存器
X86通用寄存器一共8个,它们的特性如下。
EAX(累加器):用于协助执行一些常见的运算操作,以及存放函数返回值。因此可以基于存储在EAX中的值来判断一个函数调用所执行的操作是成功还是失败了。
ECX:计数器,是重复(REP)前缀指令和LOOP指令的内定计数器。
EDX:总是被用来放整数除法产生的余数。
EBX:基地址寄存器。
ESP(栈指针寄存器),指向当前的栈顶。
EBP(栈基址寄存器),这些寄存器用于控制函数调用和相关栈的操作。
ESI:源变址寄存器,存储着输入数据流的位置信息。
EDI:目的变址寄存器,指向数据操作结果的存放位置。
1.2 X64通用寄存器
x64新增了8个寄存器r8到r15,加上x86原有8个,现在它一共拥有16个寄存器。分别是:RAX、RBX、RCX、RDX、RSI、RDI、RBP、RSP、R8、R9、R10、R11、R12、R13、R14、R15。x64主要借鉴了RISC处理器的一些特点,增加了通用寄存器的个数,为了兼容历史版本,导致了通用寄存器命名不规范。
rax作为函数返回值使用。
rsp栈指针寄存器,指向栈顶。
rdi,rsi,rdx,rcx,r8,r9 用于函数参数,依次对应第1、第2参数…;从第七个参数开始通过栈传递,所以说如果函数的参数不超过6个,那么所有参数都是通过寄存器来传递的。
rbx,rbp,r12,r13,r14,r15用于数据存储,遵循被调用者使用规则,调用子函数之前要备份它,以防他被修改。
r10,r11 也用作数据存储,遵循调用者使用规则,使用之前要先保存原值。
注:rip寄存器,始终指向当前正在执行的指令,实时的反映当前代码所执行到的位置。
2.断点
2.1软断点
软断点实质是一个单字节长的指令,该指令可以使被调试的目标进程暂停执行并且将控制权转交给调试器的异常处理。
下面看一个例子:
汇编指令“MOV EAX, EBX”,CPU不能识别,需要转为操作码(x86)“8BC3”。假设其内存地址是0x11223344,常见的表示:
0x11223344: 8BC3 MOV EAX, EBX
INT3中断指令的操作码是0xCC,因此设置断点后:
0x11223344: CCC3 MOV EAX, EBX
防止软断点技术:检测自己的在内存中运行代码的CRC校验值,一旦失败就直接退出,从而限制他人对程序进行动态分析。
2.2硬件断点
硬件断点的设置通过使用特定的寄存器,即调试寄存器。不需要对进程做任何修改。
DR0到DR3用于储存所设的硬件断点地址,所以同时只能设置4个硬件断点。
DR4和DR5保留,DR6是调试状态寄存器,记录上一次断点触发所产生的调试事件类型信息。
DR7本质上是硬件断点的激活开关,还存储着各个断点的触发条件信息。
2.3内存断点
内存断点本质不是真的断点,只是改变了一个内存区域或一个内存页的访问权限。通过保护页可以实现内存断点机制,当进程访问到一个特定区块时,就可以让进程暂停。