内存断点原理:
内存断点原理,通过将内存断点所在内存页的属性修改为内存断点属性(non-access or non-writable),程序执行时,对目标内存页中所有数据的访问或写,都会抛出异常,OD通过截获此异常,然后对比,存储在某一内存的内存断点信息表的地址,判断是否匹配内存断点地址范围,匹配则中断程序执行,否则,继续执行。
IDA静态分析:
本例利用IDA和OD动静结合,分析OD内存断点基本流程,以及内存断点存储在哪儿段内存地址。
首先将OD丢进IDA中,在程序函数列表中搜索breakpoint字符串,如图:
我们仅分析内存断点,通过function name 可知:_setmembreakpoint是我们需要分析的函数。跳到该函数分析,入口点部分截图:
可知,该函数有3个参数,通过F5(hexray),其反编译伪代码:
signed int __cdecl Setmembreakpoint(__int16 a1, unsigned int a2, int a3) { _DWORD *v3; // [email protected] signed int result; // [email protected] if ( !dword_4D8138 && VersionInformation.dwPlatformId != 2 ) { v3 = Findmemory(a2); if ( a2 < 0x80000000 ) //判断内存断点地址是否在用户内存中(0x800000~0xffffffff属于系统内核内存区域) { if ( v3 && *((_BYTE *)v3 + 11) & 1 ) //判断内存断点是否在资源内存区域 { if ( MessageBoxA( hWnd, "You are going to set memory breakpoint on resource. This breakpoint, when hit within system DLL, may freeze Windows or cause system crash. Do you really want to set this breakpoint?", "Memory breakpoint on resource", 0x14u) != 6 ) return -1; } else { if ( v3 && *((_BYTE *)v3 + 11) & 4 ) //判断内存断点地址是否在堆栈内存区域 { MessageBoxA( hWnd, "You are going to set memory breakpoint on stack. This doesn't work on Win95-based operating systems.", "Memory breakpoint on stack", 0x10u); return -1; } } } else { if ( MessageBoxA( hWnd, "You are going to set memory breakpoint in system area. This breakpoint may freeze Windows or cause system crash. Do you really want to set this breakpoint?", "Memory breakpoint in system area", 0x14u) != 6 ) return -1; } } if ( sub_418E24() ) { result = -1; } else { dword_4D813C = a2; dword_4D8140 = a3; dword_4D8144 = a2 & 0xFFFFF000; dword_4D8148 = (a3 + a2 + 4095) & 0xFFFFF000; dword_4D8138 = (HIBYTE(a1) & 0x10) != 0; dword_4D8D5C = 1; if ( a1 & 3 && a3 ) { if ( (a1 & 3) == 2 ) dword_4D814C = 32; else dword_4D814C = 1; if ( dword_4D5A5C == 3 ) sub_419034(); result = 0; } else { result = 0; } } return result; }
OD调试OD动态分析:
我们通过OD来调试OD,分析setmembreakpoint函数的流程。
将OD丢进OD中,并在setmembreakpoint入口地址下断点(F2),并运行被调试的OD。然后在被调试的OD程序中加载调试一个任意程序,并在某一地址下内存访问断点,如图(被调试的od程序中,在红线0x401377处下了内存断点):
下完该内存断点,调试程序OD立即捕获,并中断在setmembreakpoint的入口地址,然后我们通过单步,查看该函数的三个参数分别是神马:
发现esi中的值即我们在被调试程序中所下的内存断点地址,edi为内存断点的字节长度(0x401377处指令为6bytes)。ebx值其实是内存断点的属性(访问:0x00000003,写:0x00000002),这里我们之前下的是内存访问断点,所以ebx:0x00000003。
继续单步执行,在0x4192fb处跳转到0x41938d。先不管这里为什么会调转。继续单步,发现之后的汇编操作将我们所下的内存断点信息存储到了某段内存中,如图:
当前eax=0x401377,即我们的内存断点地址。
分析汇编:
004193A2 |. A3 3C814D00 mov dword ptr [4D813C], eax //将内存断点地址存储到0x4d813c处 004193A7 |. 8BC8 mov ecx, eax //ecx=eax=0x401377 内存断点地址 004193A9 |. 03C2 add eax, edx //edx为内存断点字节长度,这里是取内存断点结束地址,本例中,eax=0x401377+6=0x40137d 004193AB |. 81E1 00F0FFFF and ecx, FFFFF000 //相当于去该内存地址所在的内存页起始地址 ecx=0x401000 004193B1 |. 05 FF0F0000 add eax, 0FFF //将内存断点结束地址加上了一个内存页大小 004193B6 |. 8915 40814D00 mov dword ptr [4D8140], edx //将内存断点长度存储到0x4d8140 004193BC |. 25 00F0FFFF and eax, FFFFF000 //取该内存地址所在的内存页地址 004193C1 |. 890D 44814D00 mov dword ptr [4D8144], ecx //将内存断点坐在内存也的起始地址存储到0x4d8144处 004193C7 |. F6C7 10 test bh, 10 //设置zf属性,执行后,zf=1 004193CA |. A3 48814D00 mov dword ptr [4D8148], eax //本例中eax=402000 004193CF |. 0F95C0 setne al //if zf=1 then al=0, if zf=0 then al=1 004193D2 |. 83E0 01 and eax, 1 //之后 eax=0x00000000 004193D5 |. 83E3 03 and ebx, 3 //ebx之前存储的是内存断点属性(访问or 写) 004193D8 |. A3 38814D00 mov dword ptr [4D8138], eax //存储这个不知道有何用途 004193DD |. C705 5C8D4D00>mov dword ptr [4D8D5C], 1 004193E7 |. 85DB test ebx, ebx 004193E9 |. 74 04 je short 004193EF 004193EB |. 85FF test edi, edi //edi存的是内存断点字节长度 004193ED |. 75 04 jnz short 004193F3 //跳转成功 004193EF |> 33C0 xor eax, eax 004193F1 |. EB 2B jmp short 0041941E 004193F3 |> 83FB 02 cmp ebx, 2 //ebx与2 比较,判断是否是内存写属性 004193F6 |. 75 0C jnz short 00419404 //不是则跳转到0x419404(访问属性) 004193F8 |. C705 4C814D00>mov dword ptr [4D814C], 20 //内存写属性,则将0x20存储在0x4d814c 00419402 |. EB 0A jmp short 0041940E 00419404 |> C705 4C814D00>mov dword ptr [4D814C], 1 //内存访问属性,将0x01存储在0x4d814c 0041940E |> 833D 5C5A4D00>cmp dword ptr [4D5A5C], 3 00419415 |. 75 05 jnz short 0041941C 00419417 |. E8 18FCFFFF call 00419034 0041941C |> 33C0 xor eax, eax 0041941E |> 5F pop edi 0041941F |. 5E pop esi 00419420 |. 5B pop ebx 00419421 |. 5D pop ebp 00419422 \. C3 retn
通过对上面的汇编代码的分析,我们基本上可以定位内存断点信息的存储地址:0x4d8138~0x4d814f ,本例如图:
0x4d8138~0x40813b:0x00000000 未知
0x4d813c~0x4d813f: 0x00401377 内存断点起始地址
0x4d8140~0x4d8143: 0x00000006 内存断点长度
0x4d8144~0x4d8147: 0x00401000 内存断点地址所在的内存页起始地址
0x4d8148~0x4d814b: 0x00402000 内存断点尾部地址+内存也大小地址后,所在的内存页起始地址(这里有所疑问,为什么要加上0xfff,而不是直接取内存断点尾部地址所在的内存页起始地址,本例中,应该也是0x401000)。
0x4d814c~0x4d814f:0x00000001 内存断点属性(访问:0x00000001,写:0x00000020,与ebx区分开)
对该段内存的赋值其实在初始化一个结构体,该结构体存储内存断点相关信息:
Typedef struct membrakpointinfo
{
Dword unrecognized;//0x4d8138~0x40813b 未知
Dwordmemaddr; //0x4d813c~0x4d813f 内存断点起始地址
Dword size; //0x4d8140~0x4d8143:0x00000006 内存断点长度
Dword base1; //0x4d8144~0x4d8147内存断点地址所在的内存页起始地址
Dword base2; //0x4d8148~0x4d814b:0x00402000 内存断点尾部地址+内存也大小地址后,所在的内存页起始地址
Dword memtype; //0x4d814c~0x4d814f 内存断点属性
}
参考文章:http://bbs.pediy.com/showthread.php?t=65221