第16章 调试器OllyDBG的工作原理分析
在Windows平台下,大家耳熟能详的调试器当属OllyDBG,为了能够更加熟练地运用它,了解其工作原理是必不可少的。本章将对OllyDBG的断点工作原理、异常处理机制、调试文件的加载流程等进行详细分析。
OllyDBG的断点功能是基于异常处理来实现的,通过捕获程序执行过程中的异常信息来中断程序的执行流程。OllyDBG常用的断点类型有三种:INT3断点、内存断点、硬件断点。每种断点都是一种制造异常的方法,首先使程序在运行过程中产生错误,然后由OllyDBG的异常处理来接管,从而实现断点的功能。
16.1 INT3断点
INT3断点是最常用的断点,其工作流程是通过修改机器码为0xCC来制造异常。当程序执行0xCC代码时会触发INT3异常,OllyDBG将捕获此异常并等待用户的处理。跳过INT3断点则是将0xCC处的代码恢复,再次运行,以保证程序的正常运行。
OllyDBG设置INT3断点的快捷键是F2,这个快捷键将会出现在消息回调函数中。消息回调函数的首地址可通过查询窗口类的注册过程获取,先找到RegisterClass函数,然后顺藤摸瓜找到窗口类WNDCLASS中消息回调函数的赋值处。
在消息回调函数对快捷键F2的处理过程中可以找到INT3断点设置的函数地址为0x00419974处,将其重命名为SetINT3。使用IDA加载并分析OllyDBG,在IDA中使用地址查询快捷键G查看函数实现流程,如代码清单16-1所示。
代码清单16-1 SetINT3断点设置函数分析1—IDA分析
int__cdecl SetINT3(int arglist, int, int, int, int, int, int);函数类型识别
SetINT3 proc near;函数调用地址sub_41E604+130C↑sub_41E604+138D
00419974 buffer=byte ptr-408h;局部变量和参数地址标号定义
00419974 dest=byte ptr-208h
00419974 var_8=dword ptr-8
00419974 var_4=dword ptr-4
00419974 arglist=dword ptr 8
00419974 arg_4=dword ptr 0Ch
00419974 arg_8=dword ptr 10h
00419974 arg_C=dword ptr 14h
00419974 arg_10=dword ptr 18h
00419974 arg_14=dword ptr 1Ch
00419974 arg_18=dword ptr 20h
00419974
;部分代码分析略
00419980 mov edi,[ebp+arg_C]
00419983 mov ebx,[ebp+arglist];获取断点列表信息结构
00419986 cmp dword_4D57C4,0
0041998D jz short loc_4199ED;检查断点是否存在,存在则跳转
0041998F cmp[ebp+arg_4],71h;检查参数,此参数为键盘消息F2
00419993 jnz short loc_4199A7
00419995 cmp[ebp+arg_8],0
00419999 jnz short loc_4199A7
0041999B push ebx
;检查将要设置断点的地址处是否已经存在断点
0041999C call_Getbreakpointtype
004199A1 test ah,2;未设置断点,返回0x08
004199A4 pop ecx
004199A5 jnz short loc_4199ED;如果已设置断点,则跳转成功
代码清单16-1完成了前期的检查工作,这段代码中出现了一个断点结构类型arglist,此类型定义如下:
00000000 t_sorted struc;定义结构名称为t_sorted,结构大小为0x138
00000000 name[MAXPATH db 260 dup(?);描述结构名称
0000104 n dd?;数组元素个数
00000108 nmax dd?
0000010C selected dd?
00000110 seladdr dd?
00000114 itemsize dd?;数组中每个元素的大小
00000118 version dd?
0000011C data dd?;保存各元素的指针
00000120 sortfunc dd?
00000124 destfunc dd?
00000128 sort dd?
0000012C sorted dd?
00000130 index dd?
00000134 suppresserr dd?
00000138 t_sorted ends
对于t_sorted,我们暂时只需要了解结构中的n、itemsize、data。n用于表示数组元素的个数,itemsize用于表示数组中每个元素的大小,data用于保存各元素的指针。_Getbreakpointtype函数会根据t_sorted结构中已经记录的INT3断点信息来判断当前所设置的断点操作是设置断点还是删除断点。在设置断点操作时(快捷键F2),如果在t_sorted结构中没有记录,代码清单16-1中最后的条件跳转将失败,代码顺序向下执行,进入设置断点的实现流程中,具体分析如代码清单16-2所示。
代码清单16-2 SetINT3断点设置函数分析2—IDA分析
loc_4199A7:;地址标号
004199A7 push ebx;压入断点列表信息
004199A8 call_Findmodule;查找断点所在的模块的信息
004199AD pop ecx
004199AE mov esi, eax
004199B0 test eax, eax;检查模块查询结果
004199B2 jz short loc_4199C3;查询模块失败跳转
004199B4 cmp ebx,[esi+0Ch]
004199B7 jb short loc_4199C3;比较断点地址是否在查询的模块内
004199B9 mov edx,[esi+0Ch]
004199BC add edx,[esi+10h]
004199BF cmp ebx, edx;检查断点是否在代码段中
004199C1 jb short loc_4199ED;如果在代码段中,则跳转
loc_4199C3:;地址标号,此流程中显示断点警告信息
004199C3 push 2124h;uType
004199C8 push offset aU;"可疑的断点"
;压入字符串"您设置的断点……关闭这个警告信息。"的首地址
004199CD push offset aLIZTSU_Int3USL
004199D2 mov ecx, hWnd
004199D8 push ecx;hWnd
004199D9 call MessageBoxA
代码清单16-2对设置断点的地址进行了检查,首先判断断点是否设置在分析程序的模块中,其次检查断点是否设置在代码段内。这就是使用OllyDBG时在非代码段中设置断点会有警告提示的原因。
到这里,前期的检查工作就结束了,下面正式进入到INT3断点的设置流程中,具体分析如代码清单16-3所示。
代码清单16-3 SetINT3断点设置函数分析3—IDA分析
loc_4199ED:;地址标号
;部分代码分析略
004199FD push ebx
004199FE call_Getbreakpointtype;获取设置INT3断点地址处的内存页属性
00419A03 test ah,2
00419A06 pop ecx
00419A07 jz short loc_419A1D;如果已经设置了断点,则跳转失败
00419A09 push 0
00419A0B lea edx,[ebx+1]
00419A0E push edx
00419A0F push ebx
00419A10 call_Deletebreakpoints;删除断点信息
00419A15 add esp,0Ch
00419A18 jmp loc_419D5E
loc_419A1D:;地址标号
00419A1D cmp[ebp+arg_8],0
00419A21 jnz short loc_419A6D
00419A23 push 0;int
00419A25 push 0;char
00419A27 push 20200h;int
00419A2C push ebx;arglist
00419A2D call_Setbreakpointext;设置断点
00419A32 add esp,10h
00419A35 lea esi,[ebx+1]
00419A38 push 38h
00419A3A push esi
00419A3B push ebx
;将INT3断点信息表中的name属性的值修改为0x38
00419A3C call_Deletenamerange
;部分代码分析略
00419A68 jmp loc_419D5E
;部分代码分析略
loc_419D5E:;地址标号
00419D5E push 0
00419D60 push 0
00419D62 push 474h
00419D67 call_Broadcast;发送消息通知所有子窗口更新
00419D6C add esp,0Ch
00419D6F xor eax, eax
loc_419D71:
;恢复线程,函数返回部分略
00419D77 retn
00419D77 SetINT3 endp
代码清单16-3展示了INT3断点的设置与删除过程。通过查询断点信息表中的信息,检查设置断点处是否已经设置了INT3断点,如果设置了断点,则会删除该断点。如果没有设置断点,则设置INT3断点。INT3断点的设置由_Setbreakpointext完成,具体分析如代码清单16-4所示。
代码清单16-4_Setbreakpointext内存断点设置—IDA分析
;int__cdecl Setbreakpointext(char arglist, int, char, int)函数参数分析
00419560_Setbreakpointext proc near;函数入口
00419560 var_2C=dword ptr-2Ch
;局部变量和参数标号定义略
00419560 arg_C=dword ptr 14h
00419560 push ebp
;部分代码分析略
00419570 mov edi, dword ptr[ebp+arglist]
00419573 push 0
00419575 push edi;设置断点地址
00419576 call_Finddecode;查找断点所在代码区的位置
0041957B add esp,8
;断点检查代码分析略
0041963C push edi
0041963D push offset byte_4D7EE1
00419642 call_Findsorteddata;查找断点信息表中的数据
;部分代码分析略
00419669 mov[ebp+var_1F],edx
0041966C push ecx;arglist
0041966D push offset byte_4D7EE1;src
00419672 call_Addsorteddata;将断点信息添加到断点信息表中
loc_419750:;地址标号
00419750 push 2;char
00419752 push 1;n
00419754 push edi;arglist
00419755 lea eax,[ebx+0Ch];读取目标的1字节数据到缓冲区[ebx+0Ch]中
00419758 push eax;src
00419759 call_Readmemory;读取目标的内存信息
0041975E add esp,10h
00419761 cmp eax,1;检查读取结果
00419764 jz short loc_419799;如果读取失败,则删除断点信息表中的信息
;删除断点信息表操作略
loc_4197C9:;地址标号
004197C9 push 2;char
004197CB push 1;nSize
004197CD push edi;arglist
004197CE lea edx,[ebp+Buffer];将0xCC写入目标断点的地址中
004197D1 push edx;lpBuffer
004197D2 call_Writememory;写入INT3断点信息
004197D7 add esp,10h
004197DA cmp eax,1
004197DD jz short loc_419814;如果写入失败,则删除断点信息表中的信息,否则跳转
;部分代码分析略
loc_419814:;地址标号
00419814 or dword ptr[ebx+8],100h
0041981B jmp loc_4198A2;跳转到地址标号loc_4198A2处
;部分代码分析略
loc_4198A2:;地址标号
;部分代码分析略
004198F4 push(offset aNoaccess+8)
004198F9 push 38h
004198FB push edi
004198FC call_Insertname;设置INT3断点信息
00419901 add esp,0Ch
;操作同上,分析略
;刷新窗口,并还原环境,结束函数调用,分析略
00419973 retn
00419973_Setbreakpointext endp
以上分析了INT3断点的设置与删除过程。INT3断点是如何被触发的呢?要了解INT3断点的触发过程,需要掌握异常处理机制的相关知识,因此本书将触发过程与异常处理机制的内容(16.4节)放在一起进行详细分析。
OllyDBG实现INT3断点的主要流程为:检查INT3断点是否在记录的断点信息表中→将INT3断点信息记录到表中→记录INT3断点处的机器码信息→将INT3断点处的机器码修改为0xCC→设置断点信息表。