内存断点和硬件断点

1. 内存断点

A) Windows系统中的内存采用分页机制,内存模型如下所示:

保护模式下的内存模型

32位逻辑地址=16位段选择器+偏移地址
16位段选择器=13位全局描述符表/局部描述符表索引+1位GDT/LDTflag+2位的访问控制权限
由16位得到段选择器索引到全局描述符表得到64位的段描述符,段描述符中包含了线性段基址
线性段基址+偏移地址(逻辑地址后半段)得到线性地址空间的一个32位线性地址
32位线性地址=10位页目录索引+10位页表索引+12位物理页内偏移
根据页目录索引找到相应页表地址,然后加上页表偏移得到一个物理地址空间中的一个基地址,该物理基址加上物理页内偏移即是逻辑地址所映射的物理地址。

B) 每个内存页都是有属性的:

比如用来存放数据的页要设置成不可执行的,这样可以防止缓冲区溢出等攻击

C) 内存断点的基本原理:

    内存断点的实现方式是将你欲下断地址所在的内存页增加一个名为PAGE_NOACCESS的属性,这个属性会把当前内存页设为禁止任何形式的访问,如果进行访问会触发一个内存访问异常。在这同时,OD开始捕获目标程序中出现的这个异常,并判断触发这个异常的位置是否跟你下断的地址相同,如果相同则内存断点触发,暂停被调试程序的运行,否则放行。
补充|:
    1.内存断点很消耗资源,因为PAGE_NOACCESS属性一设置就是一整个内存页无法访问,那么当程序访问该内存页中非断点地址的内容同样会触发异常,这时OD收到异常后需要进行特殊处理,临时放行,非常消耗资源,甚至这使得内存断点在调试很多大型程序时慢到近乎不可用。
    2.虽然内存断点的效率经常很不理想,但是因为仅仅是修改了一个内存属性,所以内存断点可以下数量非常多、单断点范围非常大。这是它的优势。
    3.只在写入时断下的内存断点通常是将内存属性设为PAGE_EXECUTE_READ,也就是不可写来实现的。对这种属性的内存进行写操作将会触发异常。

2. 硬件断点

    硬件断点是由硬件提供给我们的调试寄存器组,我们可以对这些硬件寄存器设置相应的值,然后让硬件帮我们断在需要下断点的地址。
Intel 80386以上的CPU 提供了调试寄存器以用于软件调试。386和486拥有6个(另外两个保留)调试寄存器:Dr0 ~ Dr7。
    Dr0,Dr1,Dr2,Dr3是用于设置硬件断点的,由于只有4个硬件断点寄存器,所以同时最多只能设置4个硬件断点。产生的异常是STATUS_SINGLE_STEP(单步异常)。
Dr7是一些控制位,用于控制断点的方式

这里写图片描述

    位0 L0和位1 G0:用于控制Dr0是全局断点还是局部断点,如果G0为1则是全局断点,如果L0为1则为局部断点。G0,L0 ~G3,L3分别用于控制Dr0~Dr3。
    LE和GE:P6 family和之后的IA32处理器都不支持这两位。当设置时,使得处理器会检测触发数据断点的精确的指令。当其中一个被设置的时候,处理器会放慢执行速度,这样当命令执行的时候可以通知这些数据断点。建议在设置数据断点时需要设置其中一个。切换任务时LE会被清除而GE不会被清除。为了兼容性,Intel建议使用精确断点时把LE和GE都设置为1。  
    LEN0到LEN3:指定调试地址寄存器DR0到DR3对应断点所下断的长度。如果R/Wx位为0(表示执行断点),则LENx位也必须为0(表示1字节),否则会产生不确定的行为。LEN0到LEN3其可能的取值如下:
(1)00 1字节
(2)01 2字节
(3)10 保留
(4)11 4字节
R/W0到R/W3:指定各个断点的触发条件。它们对应于DR0到DR3中的地址以及DR6中的4个断点条件标志。可能的取值如下:
(1) 00 只执行
(2) 01 写入数据断点
(3) 10 I/O端口断点(只用于pentium+,需设置CR4的DE位,DE是CR4的第3位 )
(4) 11 读或写数据断点
    GD位:用于保护DRx,如果GD位为1,则对Drx的任何访问都会导致进入1号调试陷阱(int 1)。


补充|:
    1.寄存器数量的限制导致硬件断点最多只能同时存在4个,并且od在特定设置或者插件的影响下可能内部还会占用一两个用来辅助程序调试,导致可用数量十分有限。
    3.由于cpu的直接支持,硬件断点的效率是非常高的,给一个程序设置了硬件断点,在不触发的情况下,不会有肉眼可见的效率影响,毕竟只是写了个寄存器而已。

下面将使用OD来分析一个简单的程序:

源码:

.data
    szFmt db 'EAX=%d; ECX=%d; EDX=%d', 0

.code
start:
    mov eax, 11h
    mov ecx, 22h
    mov edx, 33h
    push eax
    push eax
    push ebx
    push ebx
    push edx
    push edx
    pop eax
    pop eax
    pop ebx
    pop ebx
    pop edx
    pop edx
    invoke crt_printf, addr szFmt, eax, ecx, edx
    ret
end start

在进行调试前查看硬件断点地址列表可知,此时没有创建过硬件断点:

这里写图片描述

快速找到入口点,当程序执行到如图所示时:
这里写图片描述

此时sp指向0019FF80,此处存放的是00000011,将内存数据跟随到0019FF80,并在此处设置硬件断点,断点类型为访问类型:
这里写图片描述

此时查看硬件断点:
这里写图片描述

在0X0019FF80地址处设置了一个硬件断点,按下F9,程序将在下次访问该地址数据的指令的下一条处断下,如图:
这里写图片描述
**程序断在了push EDX指令处,此时SP指向0x0019FF84,它的上一条指令 pop EDX访问了地址
0x0019FF80,即设下硬件断点的地方。**

文章参考了博客:
http://www.cnblogs.com/qintangtao/p/5686148.html
http://www.cnblogs.com/DreamOfGalaxy/articles/4491857.html

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在VEH机制中使用硬件断点可以帮助我们在特定的内存地址上设置断点,当程序执行到该地址时触发断点异常。以下是一个使用硬件断点的VEH例子: ```c++ #include <Windows.h> // Vectored Exception Handler函数 LONG WINAPI VehHandler(PEXCEPTION_POINTERS pExceptionInfo) { // 判断是否是硬件断点异常 if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) { // 处理硬件断点触发逻辑 // ... // 取消硬件断点,恢复程序执行 CONTEXT* pContext = pExceptionInfo->ContextRecord; pContext->Dr0 = 0; pContext->Dr7 &= ~0x00000001; // 返回处理结果,终止异常处理链 return EXCEPTION_EXECUTE_HANDLER; } // 返回继续搜索异常处理链 return EXCEPTION_CONTINUE_SEARCH; } int main() { // 注册Vectored Exception Handler PVOID pHandler = AddVectoredExceptionHandler(1, VehHandler); if (pHandler == NULL) { // 处理注册失败情况 // ... return 1; } // 设置硬件断点 CONTEXT context; memset(&context, 0, sizeof(CONTEXT)); context.ContextFlags = CONTEXT_DEBUG_REGISTERS; context.Dr0 = 0x12345678; // 设置断点地址 context.Dr7 = 0x00000001; // 设置断点条件和长度 if (!SetThreadContext(GetCurrentThread(), &context)) { // 处理设置硬件断点失败情况 // ... RemoveVectoredExceptionHandler(pHandler); return 1; } // 触发硬件断点 int* p = reinterpret_cast<int*>(0x12345678); *p = 42; // 移除Vectored Exception Handler RemoveVectoredExceptionHandler(pHandler); return 0; } ``` 在上述代码中,我们首先定义了一个VEH处理函数`VehHandler`,当异常发生时会被调用。在该函数中,我们判断异常类型是否为硬件断点异常(EXCEPTION_SINGLE_STEP),如果是,则执行我们定义的硬件断点触发逻辑。在本例中,我们取消硬件断点(清除Dr0和Dr7寄存器的相应位),并返回`EXCEPTION_EXECUTE_HANDLER`来终止异常处理链。 在`main`函数中,我们首先注册VEH处理函数,然后使用`SetThreadContext`函数设置硬件断点。通过设置`Dr0`寄存器为要设置断点的地址,设置`Dr7`寄存器来指定断点条件和长度。如果设置成功,则触发硬件断点,程序会在指定地址处触发断点异常。最后,移除VEH处理函数并结束程序。 请注意,硬件断点的使用需要注意调试器的干扰,因为调试器可能会使用硬件断点进行调试操作。因此,在实际应用中需要谨慎使用硬件断点,并根据具体需求进行详细的异常处理和错误处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值