16.3 硬件断点
前面分析的两种断点都是通过软件的方式实现的,而硬件断点则是由CPU实现的。
硬件断点的实现过程由CPU中的调试寄存器来完成。硬件断点所监控的断点长度有限,分别为1、2、4,由于调试寄存器中只使用了2位数据来保存断点长度,因此有了下面这样的记录。
在调试寄存器中,使用3位数据记录断点的状态,根据不同的数据位的组成描述硬件断点的状态信息,如下所示:
000(0)—保留(暂时无用)
001(1)—执行断点
010(2)—访问断点
011(3)—写入断点
100(4)—保留(暂时无用)
101(5)—临时断点
110(6)—保留(暂时无用)
111(7)—保留(暂时无用)
使用IDA对OllyDBG进行分析,硬件断点的实现流程分为两部分:一部分为设置硬件断点_Sethardwarebreakpoint;另一部分为删除硬件断点_Deletehardwarebreakpoint。首先,我们通过代码清单16-7对_Sethardwarebreakpoint函数的分析来查看硬件断点的设置过程。
代码清单16-7_Sethardwarebreakpoint分析—IDA分析
_Sethardwarebreakpoint proc near;函数入口
00408690 Context=CONTEXT ptr-2DCh;保存寄存器信息的结构体
00408690 var_10=dword ptr-10h
00408690 var_C=dword ptr-0Ch
00408690 var_8=dword ptr-8
00408690 var_4=dword ptr-4
00408690 arg_0=dword ptr 8;获取断点首地址
00408690 arg_4=dword ptr 0Ch;断点长度
00408690 arg_8=dword ptr 10h;获取断点标识
;保存环境代码分析略
0040869C mov esi,[ebp+arg_8];获取断点标识
0040869F mov edi,[ebp+arg_0];获取断点首地址
;部分代码分析略
004086B3 loc_4086B3:
004086B3 cmp esi,1;检查断点类型
004086B6 jz short loc_4086C7;跳转到对应的处理
004086B8 cmp esi,5
004086BB jz short loc_4086C7
004086BD cmp esi,6
004086C0 jz short loc_4086C7
004086C2 cmp esi,7
004086C5 jnz short loc_4086D0;若以上断点类型都不是,则跳转
loc_4086C7:;地址标号,处理执行断点流程
004086C7 mov[ebp+arg_4],1;修改断点长度为1
004086CE jmp short loc_4086F1;跳转到断点长度处理流程
loc_4086D0:;地址标号
004086D0 cmp esi,4;比较断点类型
004086D3 jnz short loc_4086DD;跳转到对应处理
004086D5 and edi,0FFFFh
004086DB jmp short loc_4086F1;跳转到断点长度处理流程
loc_4086DD:;地址标号,此处处理断点类型为1、2、3的情况
004086DD test esi, esi
004086DF jz short loc_4086F1;跳转到断点长度处理流程
004086E1 mov edx,[ebp+arg_4];获取断点长度并保存到edx中
004086E4 dec edx
004086E5 test edx, edi
004086E7 jz short loc_4086F1;跳转到断点长度处理流程
004086E9 or eax,0FFFFFFFFh
004086EC jmp loc_4089E2;跳转到结尾地址
loc_4086F1:;地址标号,断点长度处理流程,对断点长度进行检查
004086F1 cmp[ebp+arg_4],1;检查长度是否为1
004086F5 jz short loc_40870B;若长度为1,则跳转
;长度检查代码分析略
00408703 or eax,0FFFFFFFFh
00408706 jmp loc_4089E2;长度不符,返回错误码-1
loc_40870B:;地址标号
;dword_4D8D70是断点表的首地址,此结构由24字节组成,各成员说明如下:
;0x00000000硬件断点的首地址
;0x00000004硬件断点的长度
;0x00000008硬件断点的标识
;0x0000000C~0x00000014未知信息
0040870B mov eax, offset dword_4D8D70
00408710 xor edx, edx
00408712 mov[ebp+var_8],edx
00408715 xor ebx, ebx
loc_408717:;地址标号
00408717 mov edx,[eax+8]
0040871A test edx, edx;查询表是否有记录
0040871C jz short loc_40875C;若没有,则跳转到下一个记录结构体
0040871E cmp esi, edx;比较断点类型是否相同
00408720 jnz short loc_40875C;若不同,则跳转到下一个记录结构体
;检查断点是否在已设断点的范围内,如果是则直接退出
00408722 cmp edi,[eax];比较断点首地址与断点表中记录的地址
00408724 jb short loc_40873B;若断点首地址小,则跳转
00408726 mov ecx,[eax]
00408728 mov edx,[ebp+arg_4]
0040872B add ecx,[eax+4]
0040872E add edx, edi
00408730 cmp ecx, edx;比较断点尾地址与断点表尾地址
00408732 jb short loc_40873B;若断点尾地址大,则跳转
00408734 xor eax, eax
00408736 jmp loc_4089E2;跳转到结束处
;部分断点检查代码分析略
0040876F xor ebx, ebx
00408771 mov eax, offset dword_4D8D78;获取硬件断点标识,对照硬件断点结构
loc_408776:
00408776 cmp dword ptr[eax],0;检查硬件断点结构表是否装满
00408779 jz short loc_408784;若未装满,则跳转
0040877B inc ebx;增加硬件断点个数
0040877C add eax,1Ch
0040877F cmp ebx,4;检查对硬件断点结构表的访问是否结束
00408782 jl short loc_408776;若可以继续访问,则跳转
loc_408784:;地址标号
00408784 cmp ebx,4;检查是否已经设置了4个硬件断点
00408787 jl short loc_4087C9;进入硬件断点设置流程
;断点信息表操作分析略
loc_4087C9:;地址标号
004087C9 cmp ebx,4;检查是否已经设置了4个硬件断点
004087CC jl short loc_4087E1;进入硬件断点设置流程
;设置失败处理流程分析略
loc_4087E1:;地址标号
004087E1 mov eax, ebx;在硬件断点结构表中记录硬件断点信息
004087E3 shl eax,3
004087E6 sub eax, ebx;调整下标值,偏移到表中空白记录处
004087E8 mov dword_4D8D70[eax*4],edi;保存硬件断点的首地址
004087EF mov edx,[ebp+arg_4]
004087F2 mov dword_4D8D74[eax*4],edx;保存硬件断点的长度
004087F9 mov dword_4D8D78[eax*4],esi;保存硬件断点的标识
loc_408800:;地址标号
00408800 cmp dword_4D5A5C,3;检查调试程序是否正在运行
00408807 jnz loc_4089E0;若没有运行,则直接结束
0040880D cmp esi,5;断点类型检查
00408810 jz loc_4089E0
00408816 cmp esi,6
00408819 jz loc_4089E0
0040881F cmp esi,7
00408822 jz loc_4089E0;跳向函数结束地址,不符合硬件断点条件
00408828 mov ecx, dword_4D7DB0
0040882E mov[ebp+var_C],ecx
00408831 cmp[ebp+var_C],0
00408835 jnz short loc_40884A;进入设置硬件断点的流程
;部分代码分析略
代码清单16-7对设置硬件断点进行了相关检查,OllyDBG使用了一个保存硬件断点信息的结构表记录每个硬件断点的相关信息。由于只能设置4个硬件断点,因此对已设置的断点数进行了检查。如果一切顺利,则会进入地址标号loc_40884A处执行硬件断点的设置工作。硬件断点的实现过程主要依赖GetThreadContext与SetThreadContext两个函数来完成。
首先通过GetThreadContext获取当前线程中寄存器的信息,再通过SetThreadContext设置当前线程中寄存器的信息来完成对调试寄存器的修改,以实现硬件断点。
前面分析了硬件断点的设置过程,接下来分析硬件断点的删除过程,见代码清单16-8对函数_Deletehardwarebreakpoint的分析。
代码清单16-8_Deletehardwarebreakpoint分析—IDA分析
_Deletehardwarebreakpoint proc near
004089EC Context=CONTEXT ptr-2D8h
004089EC var_C=dword ptr-0Ch
004089EC var_8=dword ptr-8
004089EC var_4=dword ptr-4
004089EC arg_0=dword ptr 8;保存硬件断点信息表序号
;部分代码分析略
004089FF mov eax,[ebp+arg_0]
00408A02 jz short loc_408A0D
00408A04 test eax, eax;检查断点信息表序号值是否小于0
00408A06 jl short loc_408A0D
00408A08 cmp eax,4;检查断点信息表序号值是否大于4
00408A0B jl short loc_408A15
loc_408A0D:;地址标号,结束函数调用
00408A0D or eax,0FFFFFFFFh;设置返回值
00408A10 jmp loc_408BFE;错误序号值,结束函数调用
loc_408A15:;地址标号
;标号计算部分略,edx中保存下标值,ecx被清0
00408A20 mov dword_4D8D70[edx*4],ecx;清空硬件断点首地址
00408A27 xor ecx, ecx
00408A29 mov dword_4D8D74[edx*4],ecx;清空硬件断点的长度
00408A30 mov dword_4D8D78[edx*4],eax;清空硬件断点的标志
00408A37 cmp dword_4D5A5C,3;检测进程是否还在运行
00408A3E jnz loc_408BFC;未运行则跳转,结束函数调用
00408A44 mov edx, dword_4D7DB0
00408A4A mov[ebp+var_8],edx
00408A4D cmp[ebp+var_8],0;查看线程信息是否存在
00408A51 jnz short loc_408A66
;删除硬件断点错误提示信息部分的代码分析略
loc_408A70:;循环遍历线程,并暂停线程
;获取线程信息部分的代码分析略
00408A72 push eax;hThread
00408A73 call SuspendThread;暂停线程,将线程挂起
;部分代码分析略
loc_408A7F:
00408A7F cmp ebx, dword_4D7D98;检查是否遍历了所有线程
00408A85 jl short loc_408A70;若没有遍历完,则继续循环遍历线程
;部分代码分析略
loc_408A97:;地址标号
00408A97 mov[ebp+Context.ContextFlags],10010h
00408AA1 lea ecx,[ebp+Context]
00408AA7 push ecx;lpContext
00408AA8 mov eax,[ebp+var_C]
00408AAB mov edx,[eax]
00408AAD push edx;hThread
00408AAE call GetThreadContext;获取线程环境信息
00408AB3 test eax, eax
00408AB5 jz loc_408BC7
00408ABB mov ecx, dword_4D8D70
00408AC1 mov eax, dword_4D8D8C
00408AC6 mov[ebp+Context.Dr0],ecx;修改线程环境信息
00408ACC mov[ebp+Context.Dr1],eax
00408AD2 mov edx, dword_4D8DA8
00408AD8 mov ecx, dword_4D8DC4
00408ADE mov eax, offset dword_4D8D78
00408AE3 mov[ebp+Context.Dr2],edx
00408AE9 xor edx, edx
00408AEB mov[ebp+Context.Dr3],ecx
00408AF1 mov esi,400h
loc_408AF6:;循环起始地址,修改硬件断点表中的信息
00408AF6 cmp dword ptr[eax],0
00408AF9 jz loc_408BA2
00408AFF mov ecx, edx
00408B01 add ecx, ecx
00408B03 mov edi,1
00408B08 shl edi, cl
00408B0A or esi, edi
00408B0C mov ecx,[eax]
00408B0E cmp ecx,7;switch 8 cases
00408B11 ja short loc_408B75;default
00408B13 jmp ds:off_408B1A[ecx*4];case块地址表
;这是switch跳转表,对应0~7的硬件断点标识的处理,主要是设置Context.Dr7
00408B1A off_408B1A dd offset loc_408B75
00408B1A dd offset loc_408B3A
00408B1A dd offset loc_408B48
00408B1A dd offset loc_408B57
00408B1A dd offset loc_408B66
00408B1A dd offset loc_408B3A
00408B1A dd offset loc_408B3A
00408B1A dd offset loc_408B3A
;case语句块的实现过程分析略
loc_408BA2:;地址标号
00408BA2 inc edx
00408BA3 add eax,1Ch
00408BA6 cmp edx,4
00408BA9 jl loc_408AF6
00408BAF mov[ebp+Context.Dr7],esi
00408BB5 lea eax,[ebp+Context]
00408BBB push eax;lpContext
00408BBC mov edx,[ebp+var_C]
00408BBF mov ecx,[edx]
00408BC1 push ecx;hThread
00408BC2 call SetThreadContext;设置线程环境
loc_408BC7:;地址标号
00408BC7 inc ebx
00408BC8 add[ebp+var_C],66Ch
loc_408BCF:;地址标号
00408BCF cmp ebx, dword_4D7D98;检查是否设置了所有线程
00408BD5 jl loc_408A97;若没有设置完毕,则跳转回循环起始处
00408BDB xor ebx, ebx
00408BDD mov eax,[ebp+var_8]
00408BE0 lea esi,[eax+0Ch]
00408BE3 jmp short loc_408BF4;开始设置线程断点
loc_408BE5:;地址标号,循环结构起始地址
00408BE5 mov eax,[esi]
00408BE7 push eax;hThread
00408BE8 call ResumeThread;恢复挂起线程
00408BED inc ebx
00408BEE add esi,66Ch
loc_408BF4:;地址标号
00408BF4 cmp ebx, dword_4D7D98;检查是否恢复了所有线程
00408BFA jl short loc_408BE5;若没有,则跳转到循环起始处
loc_408BFC:;地址标号
00408BFC xor eax, eax
loc_408BFE:;地址标号
;还原环境的代码分析略
00408C04 retn
_Deletehardwarebreakpoint endp
代码清单16-8分析了硬件断点的删除过程,这一过程与设置硬件断点的过程很相似,只是将设置硬件断点时修改的线程环境信息恢复到原始状态。代码清单16-7中省略了对硬件断点的具体设置过程的分析,实质上,设置硬件断点的过程与删除硬件断点的过程类似,读者可对照硬件断点的删除过程来对比分析硬件断点的设置过程。
到此为止,对三种断点的设置和删除过程的分析就告一段落。它们的实现过程有着许多相同之处:
保存修改前的数据
制造异常代码(各种断点实现异常的途径不同)
由OllyDBG异常处理进行捕获
还原修改后的数据
在掌握了OllyDBG的断点设置的相关知识后,大家就可以制作自己的MyOllyDBG了。设置好断点以后,OllyDBG又是如何捕获并处理它们的呢?16.4节将对这部分内容进行分析。