16.2 内存断点

在16.1节中,我们介绍了INT3断点的设置与删除,在分析的过程中我们发现,如果将INT3断点设置在非代码段内,就会抛出错误提示信息,因为INT3断点属于执行断点,对于数据的读/写操作而言,INT3断点是无效的。INT3断点有局限性,而内存断点正好弥补了这个不足。顾名思义,内存断点是用于内存监视的断点,它可以对内存数据的访问和写入进行监控。例如,对地址0x00401000设置了写入断点,当此段内存发生修改时,会产生异常并由OllyDBG捕获。通过对OllyDBG的分析,可总结出内存断点的实现流程。

要想分析内存断点的实现,首先需要确定其位置,内存断点的设置也是通过消息来完成的。与INT3断点的不同之处是,内存断点可以在反汇编窗口与内存窗口这两个窗口中进行设置。在调用设置内存断点函数前,也需要进行断点类型的检查。

数据窗口中内存断点的类型如下:

0x 7E访问断点

0x 7F内存写入断点

0x 80清除内存断点

反汇编窗口内存断点的类型如下:

0x 23访问断点

0x 24内存写入断点

0x 25清除内存断点

这些断点类型决定了在调用设置内存断点函数时传入的参数。以数据窗口中的内存访问断点为例,其代码分析如下:


cmp edi,7Eh

JXX 0xXXXXXXXX;检查断点类型

mov eax,[ebp+var_54]

sub eax,[ebp+var_50]

push eax;监视长度

mov edx,[ebp+var_50]

push edx;断点首地址

push 3;断点属性

call_Setmembreakpoint;设置内存断点函数


根据断点的类型,为设置内存断点的函数_Setmembreakpoint配置参数。_Setmembreakpoint的第一个参数为断点属性。代码清单16-5对函数_Setmembreakpoint进行了分析。

代码清单16-5 设置内存断点函数_Setmembreakpoint—IDA分析


;_Setmembreakpoint实现分析

_Setmembreakpoint proc near;函数入口

004192D8 arg_0=dword ptr 8

004192D8 arg_4=dword ptr 0Ch

004192D8 arg_8=dword ptr 10h

;保存环境代码分析略

004192E5 mov edi,[ebp+arg_8];断点长度

004192E8 mov esi,[ebp+arg_4];断点所在内存首地址

004192EB mov ebx,[ebp+arg_0];断点类型标识符

004192EE jnz loc_41938D

004192F4 cmp VersionInformation.dwPlatformId,2

004192FB jz loc_41938D

00419301 push esi

00419302 call_Findmemory;查找此处内存是否存在

00419307 pop ecx

00419308 cmp esi,80000000h;检查是否为系统占用内存

0041930E jb short loc_419334;跳转失败进入警告提示部分

;警告提示部分略

loc_419334:;地址标号

00419334 test eax, eax;检查是否为资源数据占用内存

00419336 jz short loc_419363;跳转失败进入警告提示部分

;警告提示部分略

loc_419363:;地址标号

00419363 test eax, eax;检查是否为栈数据占用内存

00419365 jz short loc_41938D;跳转失败进入警告提示部分

;警告提示部分略

loc_41938D:;地址标号

;此函数将会修改属性页,并对修改结果进行相关检查

0041938D call sub_418E24

00419392 test eax, eax;检查是否修改属性成功

00419394 jz short loc_41939E;成功则跳转

00419396 or eax,0FFFFFFFFh

00419399 jmp loc_41941E

loc_41939E:;地址标号

0041939E mov eax, esi;设置内存断点信息结构

004193A0 mov edx, edi

004193A2 mov dword_4D813C, eax;填写内存断点结构第二项

004193A7 mov ecx, eax

004193A9 add eax, edx

004193AB and ecx,0FFFFF000h

004193B1 add eax,0FFFh

004193B6 mov dword_4D8140,edx;填写内存断点结构第三项

004193BC and eax,0FFFFF000h

004193C1 mov dword_4D8144,ecx;填写内存断点结构第四项

004193C7 test bh,10h

004193CA mov dword_4D8148,eax;填写内存断点结构第五项

004193CF setnz al

004193D2 and eax,1

004193D5 and ebx,3

004193D8 mov dword_4D8138,eax

004193DD mov dword_4D8D5C,1

004193E7 test ebx, ebx

004193E9 jz short loc_4193EF

004193EB test edi, edi

004193ED jnz short loc_4193F3

loc_4193EF:;地址标号

004193EF xor eax, eax

004193F1 jmp short loc_41941E

loc_4193F3:;地址标号

004193F3 cmp ebx,2

004193F6 jnz short loc_419404

004193F8 mov dword_4D814C,20h;填写内存断点结构第六项

00419402 jmp short loc_41940E

loc_419404:

00419404 mov dword_4D814C,1

loc_41940E:;地址标号

0041940E cmp dword_4D5A5C,3;检查是否在运行状态

00419415 jnz short loc_41941C;如果没有运行,则跳转到函数结尾并返回

;通过此函数设置内存属性,如果是访问断点,则将内存属性修改为不可访问

;于是,当执行到此断点处就会触发异常,由OllyDBG捕获进行处理

00419417 call sub_419034;重命名为SetMemPorperty

loc_41941C:;地址标号

0041941C xor eax, eax

loc_41941E:;地址标号

;还原环境分析略

00419422 retn

_Setmembreakpoint endp


代码清单16-5的主要功能是检查断点所处的内存位置,并通过修改内存属性来制造异常信息,由OllyDBG捕获并处理,从而实现断点的功能。在上述代码中,在成功设置了内存属性后,会对内存断点结构执行一些赋值操作,其结构定义如下:


struct tagBreakPoint{

DWORD dwUnknow;

DWORD dwBreakPointAddr;//内存断点所在的首地址

DWORD dwLen;//设置内存断点长度

DWORD dwBeginMemAddr;//内存断点首地址所处内存分页

DWORD dwEndMemAddr;//内存断点末尾地址所处内存分页

DWORD dwType;//断点类型:0x01访问断点,0x20写入断点

DWORD dwUnknow;

};


OllyDBG通过此结果来记录内存断点信息,每次设置新的内存断点后,OllyDBG都会覆盖此结构,因此只能记录一份内存断点。接下来将会根据tagBreakPoint结构中所记录的内存断点信息调用地址标号sub_418E24处的代码来完成内存页属性的修改过程,将其重命名为SetMemProperty,过程分析如代码清单16-6所示。

代码清单16-6 SetMemProperty分析—IDA分析


SetMemProperty proc near

00419034 buffer=byte ptr-22Ch

00419034 var_12C=byte ptr-12Ch

00419034 var_2C=byte ptr-2Ch

00419034 var_20=dword ptr-20h

00419034 var_18=dword ptr-18h

;保存环境部分略

0041903E mov ebp, offset dword_4D8D58

00419043 cmp hProcess,0;检查进程句柄

0041904A jz short loc_41905E

0041904C cmp dword_4D8140,0;检查断点长度

00419053 jz short loc_41905E

00419055 cmp dword_4D8134,0;检查标记

0041905C jz short loc_419065

loc_41905E:;地址标号

0041905E xor eax, eax

00419060 jmp loc_4192CB;跳转到结束处

loc_419065:;地址标号

;检查VirtualQuery

00419065 cmp dword_4D5A14,0;重命名地址标号为VirtualQuery

0041906C jz short loc_419077

;检查VirtualProtectEx

0041906E cmp dword_4D5A18,0;重命名地址标号为VirtualProtectEx

00419075 jnz short loc_41907F;若正确则跳过下面的错误处理

;错误处理部分略

loc_41907F:;地址标号

0041907F xor edx, edx

00419081 mov[ebp+0],edx

00419084 mov eax, dword_4D8144

00419089 mov ebx, eax

0041908B and dword_4D814C,0FFFFFEFFh

00419095 push eax

00419096 call_Findmemory;查找此处内存是否存在

0041909B pop ecx

0041909C test eax, eax;检查是否取得成功

0041909E jz loc_4191C9;成功则跳转失败,继续检查

004190A4 test byte ptr[eax+0Bh],20h;检查是否为TY_GUARDED保护

004190A8 jz loc_4191C9

004190AE or dword_4D814C,100h;将写入标志与0x100进行位或运算

004190B8 jmp loc_4191C9;跳转到循环比较处

loc_4190BD:;循环起始地址

004190BD push 1Ch

004190BF lea eax,[esp+230h+var_2C]

004190C6 push eax

004190C7 push ebx

004190C8 mov edx, hProcess

004190CE push edx

004190CF call VirtualQuery;查看进程的内存属性信息

004190D5 mov edx,[esp+22Ch+var_20]

004190DC mov ecx, edx;内存页起始地址

004190DE add ecx, ebx;加上属性页最小单位(0x1000)

004190E0 mov eax, dword_4D8148;使用eax保存内存页结尾地址

004190E5 cmp ecx, eax;检查断点是否包含在此内存页中

004190E7 jnb short loc_4190ED;如果不包含,则跳转失败

004190E9 mov esi, edx;内存对齐最小单位

004190EB jmp short loc_4190F1

loc_4190ED:;地址标号

004190ED mov esi, eax

004190EF sub esi, ebx;获取内存断点的范围

loc_4190F1:;地址标号

;部分代码分析略

loc_419189:;地址标号

00419189 mov edi, dword_4D814C

loc_41918F:;地址标号

0041918F mov eax,[ebp+0]

00419192 mov edx, hProcess

00419198 shl eax,2

0041919B add eax, offset dword_4D8158

004191A1 push eax

004191A2 push edi

004191A3 push esi

004191A4 push ebx

004191A5 push edx

004191A6 call VirtualProtectEx;修改断点所在的内存页属性

004191AC test eax, eax

004191AE jz short loc_4191DE;如果设置失败,则跳转到错误处理

004191B0 mov ecx,[ebp+0]

004191B3 mov dword_4D8558[ecx*4],edi

loc_4191BA:;地址标号

004191BA mov eax,[ebp+0];保存原内存页属性,用于还原

004191BD mov dword_4D8958[eax*4],esi

004191C4 add ebx, esi

004191C6 inc dword ptr[ebp+0]

loc_4191C9:;地址标号

;检查内存断点是否超过最大长度(0x1000*0x100),若超过,则设置内存断点失败

;另外,检查是否已经设置完内存断点范围内的所有内存页,若没有,则继续设置

004191C9 cmp dword ptr[ebp+0],100h

004191D0 jge short loc_4191DE

004191D2 cmp ebx, dword_4D8148;检查内存页属性是否已经设置完毕

;若内存断点尚未设置完毕,则跳转回循环起始处继续设置

004191D8 jb loc_4190BD

loc_4191DE:;地址标号

;内存断点所属内存页的相关检查

004191DE cmp ebx, dword_4D8144

004191E4 jnz short loc_41924A;若设置失败,跳转到错误处理

;错误检查和错误提示相关代码分析略

004192D5 retn

SetMemProperty endp


对内存属性的修改将会影响整个内存页的属性,当内存断点所设置的范围超出一个内存页的大小时,就会影响到多个内存页。因此,代码清单16-6检查并记录了内存断点所影响的内存页。

内存断点的设置过程主要依靠两个API来完成:VirtualQuery和VirtualProtectEx。通过VirtualQuery来获取原内存页的属性,以便于还原;通过VirtualProtectEx修改内存页的属性,以制造内存访问异常。被调试的目标程序发生异常后,首先处理这个异常的是调试器,因此OllyDBG可以成功捕获这个异常。内存断点的处理过程同样是由异常处理部分来完成,这将会在16.4节中进行详细分析。