调试器的原理(第8课)

本文介绍的原理以大家所熟知的OllyDbg为例进行讲解。

 

Ollydbg的断点功能是基于异常处理来实现的,通过捕获程序执行过程中的异常信息来中断程序的执行流程。Ollydbg常用的断点类型有三种:INT3断点,内存断点,硬件断点。每种断点都是一种制造异常的方法,首先使程序在运行过程中产生错误,然后由Ollydbg的异常处理来接管,从而实现断点的功能。

 

1、加载调试程序

调试程序的第一步就是使用OllyDbg来加载程序,加载的过程是通过创建新进程来完成的。OllyDbg通过CreateProcess以调试的方式开启新进程。在创建调成程序前,OllyDbg需要进行一些必要的检查工作。

 

首先是针对快捷方式的检查。OllyDbg根据可执行程序的后缀名来判断分析程序是否为一个快捷方式,如果是快捷方式,则会找到这个快捷方式所对应的可执行程序的全路径。通过检查DOS头与NT头来判定分析文件是否为合法的PE文件。当调试文件为DLL动态库时,Olly/Dbg会使用自带的LoadDll.exe将Dll文件进行加载。当调试文件为exe可执行程序时,会跳过Dll文件的处理部分,直接获取相关的配置文件信息并进行加载和调试。

 

2、异常处理机制

异常就是程序运行过程中产生的错误。OllyDbg利用异常机制捕获调试程序在运行过程中产生的异常,对异常进行排查,从而实现断点功能,使程序暂停运行。OllyDbg将异常处理过程放置在一个大消息循环中,捕获异常的流程如下:

  • 进入消息循环
  • 利用WaitForDebugEvent函数捕获异常信息,如果捕获失败,则回到循环起始处
  • 捕获到异常,率先由OllyDbg插件进行异常处理
  • 检查是否为调试异常,如果不是,则继续执行程序,回到循环起始处
  • 如果是调试异常,则进行相关检查,进入断点异常处理函数中

 

当进入最后一步时,程序已经被成功断下,调试程序出于挂起状态,等待调试者的处理。异常处理首先检查调试事件类型,如果调试信息为异常,则进入异常处理部分,判断异常类型。先判断异常是否为INT3断点所产生的,如果是,则通过跳转指令执行对应的代码。下面介绍INT3断点的捕获过程:OllyDbg将调试程序停留在正确的INT3断点处,在显示反汇编代码的过程中,没有直接显示断点处机器码0xCC或0xCD,而是通过查找断点信息表中所对应的原机器码的信息来进行显示,以防止因修改指令造成的指令混乱。

 

在调试人员发出在此运行的指令后,OllyDbg将会先修复INT3断点处的内存数据,然后再次运行修复后的指令代码。INT3断点处的指令被执行后,此处将会被再次设置为INT3断点。

 

如果检测INT3断点失败,则会开始内存断点的异常检查。内存断点的设置过程是通过修改内存属性来达到触发异常的目的的。因此,内存断点的触发便是内存访问类错误。其流程如下:

  • 得到线程信息
  • 跳转到相应的异常处理分支
  • 若得到线程信息,则根据线程信息的eip进行赋值,否则根据异常地址进行赋值
  • 得到异常所处的模块的信息,并解析反汇编信息,以进行相关检查
  • 若模块为自解压(SFX)模式,则进行相应的检查以及错误处理
  • 检查内存断点是否在dll中,弹出提示窗口,并将断点去除
  • 最后调整优先级并退出

 

硬件断点的捕获过程是由调试寄存器来完成的,因此OllyDbg没有捕获处理过程。

 

3、INT3断点

INT3断点是最常用的断点,其工作流程时通过修改机器码为0xCC来制造异常。当程序执行0xCC代码时会触发INT3异常,OllyDbg将捕获此异常并等待用户的处理。跳过INT3断点则是将0xCC处的代码恢复,在此运行,以保证程序的正常运行。

 

OllyDbg实现INT3断点的主要流程如下:

  • 检查INT3断点是否在记录的断点信息表中
  • 将INT3断点信息记录到表中
  • 记录INT3断点处的机器码信息
  • 将INT3断点处的机器码修改为0xCC
  • 设置断点信息表

 

4、内存断点

内存断点用来监控内存,它可以对内存数据的访问和写入进行监控。内存断点的设置主要依靠两个API来完成:VirtualQuery和VirtualProtectEx。通过VirtualQuery来获取原内存页的属性,以便于还原;通过VirtualProtectEx修改内存页属性,以制造内存访问异常。被调试的目标进程发生异常后,首先处理这个异常的是调试器。因此调试器可以成功捕获这个异常。内存断点的处理过程是由异常处理部分来完成。

 

5、硬件断点

在寄存器中,有一些寄存器专门用于调试,称为调试寄存器,调试寄存器一共有8个:Dr0-Dr7;对于Dr0-Dr3四个寄存器,作用是存放中断的地址,Dr4和Dr5一般不使用,保留,Dr6和Dr7这两个寄存器的作用是用来记录Dr0-Dr3中下断的地址的属性,比如:对这个401000是硬件读还是写,或者是执行;是对字节还是对字,或者是双字。

 

关于硬件断点的详细信息,请参阅断点部分的硬件断点知识。

 

6、单步执行

SEH即结构化异常处理(Structured Exception Handling),当程序出现错误时,系统把当前的一些信息压入堆栈,然后转入我们设置好的异常处理程序中执行,在异常处理程序中我们可以终止程序或者修复异常后继续执行。

 

异常处理处理分两种,顶层异常处理和线程异常处理,下面介绍的是线程异常处理。每个线程的FS: [0]处都是一个指向包含异常处理程序的结构的指针,这个结构又可以指向下一个结构,从而形成一个异常处理程序链。当发生异常时,系统就沿着这条链执行下去,直到异常被处理为止。

 

下面以最常见的OllyDbg调试器为例讲解调试器单步执行时的工作方式。

 

当在调试器中选择“步过”某条指令时,程序自动在下一 条语句停下来,这其实也属于一种中断,而且可以说是最常用的一种形式了,当我们需要对某段语句详细分析,想找出程序的执行流程和注册算法时必须要进行这一 步。是80386以上的INTEL CPU中EFLAGS寄存器,其中的TF标志位表示单步中断。当TF为1时,CPU执行完一条指令后会产生单步异常,进入异常处理程序后TF自动置0。调试器通过处理这个单步异常实现对程序的中断控制。持续地把TF置1,程序就可以每执行一句中断一次,从而实现调试器的单步跟踪功能。

 

单步执行中包含StepIn和StepOver两种:

 

StepIn:

  • StepIn即逐条语句执行,遇到函数调用时进入函数内部,其实现方式如下:
  • 通过调试符号获取当前指令对应的行信息,并保存该行的信息。
  • 设置TF位,开始CPU的单步执行。
  • 在处理单步执行异常时,获取当前指令对应的行信息,与1)中保存的行信息进行比较。如果相同,表示仍然在同一行上,转到2);如果不相同,表示已到了不同的行,结束StepIn。

 

StepOver:

StepOver即逐条语句执行,遇到函数调用时不进入函数内部,其实现方式如下:

  • 通过调试符号获取当前指令对应的行信息,并保存该行的信息。
  • 检查当前指令是否CALL指令。如果是,则在下一条指令设置一个断点,然后让被调试进程继续运行;如果不是,则设置TF位,开始CPU的单步执行,跳到4)。
  • 处理断点异常时,恢复断点所在指令第一个字节的内容。然后获取当前指令对应的行信息,与1)中保存的行信息进行比较,如果相同,跳到2);否则停止StepOver。
  • 处理单步执行异常时,获取当前指令对应的行信息,与①中保存的行信息进行比较。如果相同,跳到2);否则停止StepOver。
头像
  • ¥ 59.8元
  • 市场价:99.8元
  • ¥ 189.0元
  • 市场价:269.0元
  • ¥ 199.0元
  • 市场价:199.0元
  • ¥ 199.0元
  • 市场价:179.0元

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: