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节将对这部分内容进行分析。