不久前写的一个小例子
利用硬件断点异常的钩子方式
截获到异常后,转到异常处理程序中处理
流程(1) (2) (3)代表执行步骤
code:---------------------------------------------
#include <windows.h>
//消息输出
void WINAPI MsgXXX(DWORD id)
{
char szid[100];
wsprintf(szid,"错误码:%08Xh",id);
::MessageBox(NULL,szid,"XXX",0);
}
int main()
{
__asm
{
push offset perThread_Handler //异常处理程序入口 (1)
push fs:[0]
mov fs:[0],esp //修改异常处理
xor eax,eax
mov eax,[eax] //这里故意制造一个内存违规异常 读0 异常码 0xC0000005 (2)
mov eax,eax //写几条无用指令留点空隙 便于调试
mov eax,eax
mov eax,eax
mov eax,eax
mov eax,eax
mov eax,eax
mov eax,eax
//异常处理程序
perThread_Handler:
push ebp (3)从2出现了内存异常 直接转到异常处理程序 & (10)下面的硬件异常同样会到这里
mov ebp,esp
push ecx
mov ecx,dword ptr[ebp+08h] //获取错误码指针
mov ecx,dword ptr[ecx] //获取错误码
cmp ecx,0C0000005h //比较是否为制造的内存错误
je memError (4)确定为内存异常 跳到memError
cmp ecx,080000004h //比较是否为硬件断点
je memDRx
mov eax,1 //都不是 那就是程序自身异常 交由程序自身异常处理程序处理
jmp End
//如果是制造的那个内存错误
memError: (5)从4跳到了这里 这里设置硬件断点
push ecx
call MsgXXX
mov eax,dword ptr [ebp+10h] //获取线程寄存器结构
mov ecx,offset xxooxx //模拟一个硬件断点地址
mov dword ptr [eax+04h],ecx //把断点地址放入到DR0
mov ecx,101h //DR7参数
mov dword ptr [eax+18h],ecx //把参数放到DR7
mov ecx,offset NewAddr //异常处理程序结束后要返回的地址
mov dword ptr [eax+0B8h],ecx//这里把EIP修改成新地址
mov eax,0 //返回0 表示异常处理程序处理完后继续处理
jmp End (6)
//硬件断点异常
memDRx:
push ecx
call MsgXXX
mov eax,dword ptr [ebp+10h] //获取结构
mov ecx,offset NewAddr //设置新地址
mov dword ptr [eax+0B8h],ecx//修改EIP
mov eax,0
jmp End
End:
pop ecx (7)这里ret后 会到NewAddr 地址处 因为设置了新EIP是这里
pop ebp
ret
//新地址 设置完异常后到这里
NewAddr: (8) 这里继续执行
mov eax,eax
push 1
call MsgXXX
mov eax,eax
mov eax,eax
mov eax,eax
mov eax,eax
mov eax,eax
mov eax,eax
mov eax,eax
mov eax,eax
mov eax,eax
mov eax,eax
//硬件异常模拟地址 到这里会出现硬件单步异常
xxooxx: (9)到这里 会触发硬件断点异常 会跳回到 异常处理程序
mov ebx,ebx
mov ebx,ebx
mov ebx,ebx
mov ebx,ebx
mov ebx,ebx
mov ebx,ebx (.?)所以永远到不了这里
mov ebx,ebx
mov ebx,ebx
mov ebx,ebx
}
return 0;
}
看上去逻辑很乱 但是调试一下就真相大白了
同样可以做很猥琐的事