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节中进行详细分析。