前言
本篇将是学习调试相关的最后一篇,也是Windows(XP)内核基础的最后一篇了。本篇介绍调试相关最后的内容,单步步入与单步步过。对于经常用OD的人来说,其实就是相当于F7与F8的功能,F7可以单步执行每一行指令,F8可以跳过call指令,接下来就学习一下它们的原理和简单的实现。
单步步入
实现原理
想要实现单步步入,有很多手段,可以通过不断的下软件断点,每执行一行,就下一个INT3,然后恢复再重新执行。虽然这是可行的办法,但是过于复杂,因此Intel在设计CPU时考虑到了这一点,调试程序是必不可少的手段,因而在Eflags里设置了一个TF位。
单步步入的实现就是利用了这个TF位。在前一篇学习硬件断点时提到过,有两种情况会导致单步异常,一种情况是触发了硬件断点,另一种情况则是Eflags的TF位被置为了1。因此调试寄存器Dr6被专门用来判断当前的单步异常属于哪一类。
当在调试器中执行单步操作时,就会将Eflags的TF位设置为1,从而产生单步异常,接下来的步骤,与硬件断点的执行流程是一样的,查找1号中断处理函数。并最终将该类型调试事件发送给调试器。
单步步入函数
单步步入的实现,就是将Context记录的Eflags的TF置1。
1 | VOID SetSingleStep() |
处理函数
由于单步步入与硬件断点触发的异常都属于单步异常,因此这两种异常使用同一个处理函数。仅需判断一下Dr6的值即可。
1 | BOOL SingleStepExceptionProc(EXCEPTION_DEBUG_INFO * pExceptionInfo) |
除此之外,有了单步步入后,也可以进一步补充WaitForUserCommand函数
1 | BOOL WaitForUserCommand() |
单步步过
实现原理
单步步过对应OD的F8,也就是遇到call指令的时候不跟进去,直接跳过。实现的原理是遇到call指令(好几种)后,计算当前指令的长度,然后在(当前EIP+当前指令长度)的地方下断(也就是call指令的下一行),然后执行CPU,便可以实现单步步过。实现过程有一点小复杂,所以这里没有下断过程与处理函数。
反调试技巧
针对单步步过也有一种反调试技巧,可以设置多段call调用,而逆向的人不可能会跟进每一个函数里,就会使用单步步过,这时可以修改call函数里堆栈的返回地址,让函数返回的别的位置,这样逆向的人再尝试单步步过,就会跟丢了,从而达到反调试的目的。
总结
参考资料
参考笔记:
- 张嘉杰笔记
- My classmates笔记
参考教程:
- 海哥逆向中级预习班