反调试——4——硬件断点反调试

2021年10月1日 16点热度 0条评论

反调试——4——硬件断点反调试

 

 

首先需要明白什么是硬件断点,硬件断点其实是通过一个调试寄存器来实现的,这个调试寄存器是CPU上的东西,就是前面截图的这个东西,叫做Debug Registers,在intel手册卷3 17章第二节里面)。

DR0-DR3为设置断点的地址,DR4和DR5为保留内容。DR6为调试异常产生后显示的一些信息,DR7保存了断点是否启用、断点类型和长度等信息。

重点在DR0-DR3和DR7这四个里面。

DR0-DR3中存放的是断点的地址。

然后DR7根据各各字段的不同意义不同:

 

字段 意义
L0-L3 对于DR0-DR3存放的地址断点是否有效,并且是一个局部断点
G0-G3 对应DR0-DR3存放的地址是否有效,但是是一个全局断点(在Windows中用不了。)
LEN0-LEN3 对应DR0-DR3断点的长度。00表示1字节,01表示二字节,11表示四字节
RW0-RW3 对应DR0-DR3断点的类型。00表示执行断点,01表示写入断点,11表示读写断点。

获取硬点断点信息:

这里我们要使用到一个API:

BOOL GetThreadContext(
 HANDLE    hThread,
 LPCONTEXT lpContext
);

用这个API来获取当前线程的环境,肯定有人要问为什么是线程,不是进程,不是啥啥啥的。因为在操作系统中,线程才是真正执行代码流程的东西,进程只是一个分配资源的概念,而每个代码执行的时候,自己的寄存器或多或少都会受到影响,简单来说就是线程才能实实在在执行代码,如果用进程的话每个线程都会受到影响,而且进程也没有这个GetProcessContext得到进行环境寄存器的API。

参数:

参数 意义
hThread 线程句柄
lpContext 指向CONTEXT结构体的指针,需要注意的是context是一个输出参数就是通过这个API会修改CONTEXT结构体里的值,但是需要设置CONTEXT里的ContextFlags值来限制要获得什么内容,如果不设置则不能获得内容。

 

 

所以如果我们要获取Debug Register的内容,这里需要设置为CONTEXT_DEBUG_REGISTERS了。

简单实现硬件断点反调试:

#include<Windows.h>
#include<iostream>
using namespace std;


int main()
{

CONTEXT TestContext{ 0 };
TestContext.ContextFlags = CONTEXT_DEBUG_REGISTERS;
GetThreadContext(GetCurrentThread(), &TestContext);
if (TestContext.Dr0 != 0 || TestContext.Dr1 != 0 || TestContext.Dr2 != 0 || TestContext.Dr3 != 0)
{
MessageBoxA(NULL, "硬件断点检测成功,程序正在被调试!", "硬件断点检测", MB_OK);
}
else
{
MessageBoxA(NULL, "硬件断点检测失败", "硬件断点检测", MB_OK);
}

system("pause");
return 0;
}
#pragma once
#include<Windows.h>
#include<iostream>
using namespace std;
void MyTestExceptionFilter();
LONG WINAPI MyUnhandledExceptionFilter(_EXCEPTION_POINTERS* ExceptionInfo);

 

最后查看通过OD打下硬件断点的情况,和直接运行的情况:

采用异常实现硬件断点反调试:

如果说直接用前面那种来实现反调试就太辣鸡了,很容易就被识破,但是如果我们添加一个异常呢,这样就会好很多,因为异常要通过一些寄存器,一些不是上面那种直接调用API的办法,会麻烦一点。

采用异常过滤器来处理:

关于异常过滤器可以查看直接的博客:反调试——异常过滤器 - Sna1lGo - 博客园 (cnblogs.com)

这里我直接上代码了:

#include"Anti-debugging.h"
LONG WINAPI MyUnhandledExceptionFilter(_EXCEPTION_POINTERS* ExceptionInfo)
{
if (ExceptionInfo->ContextRecord->Dr0 != 0 || ExceptionInfo->ContextRecord->Dr1 != 0 || ExceptionInfo->ContextRecord->Dr2 != 0 ||
ExceptionInfo->ContextRecord->Dr3 != 0)
{
cout << "Fuck" << endl;
cout << "异常过滤器验证成功,程序正在被调试" << endl;
cout << "即将退出进程" << endl;
ExitProcess(0);
}
else
{
cout << "异常过滤器验证失败,程序有没有被调试不知道" << endl;
}

ExceptionInfo->ContextRecord->Eip += 3;

return EXCEPTION_CONTINUE_EXECUTION;
}

void MyTestExceptionFilter()
{
printf("test\n");
SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
int a = 0;
int b = 2 / a;
cout << "跳过了异常代码" << endl;
cout << "程序正常结束" << endl;
}
int main()
{
MyTestExceptionFilter();
system("pause");
return 0;
}

这里eip+3的原因是错误代码的硬编码是三个字节,跳过就好了。

但是这个用调试器就不行了,会一直在异常这里,不知道为什么。

采用SEH来处理:

EXCEPTION_DISPOSITION mySEH(struct _EXCEPTION_RECORD* ExceptionRecord,PVOID EstablisherFrame,PCONTEXT pcontext,PVOID DispatcherContext)
{
if (pcontext->Dr1 != 0 || pcontext->Dr2 != 0 || pcontext->Dr3 != 0 || pcontext->Dr0 != 0)
{
printf("SEH验证异常,程序正在被调试,即将退出程序\n");
ExitProcess(0);
}
else
{
printf("SEH验证正常\n");
}
pcontext->Eip += 3;

return ExceptionContinueExecution;
}
void TestSeh()
{
printf("test SEH\n");

__asm
{
push mySEH
mov eax,fs:[0]
push eax
mov fs:[0],esp
mov eax, 0
mov[eax], 1
}
cout << "SEH: 跳过了异常代码" << endl;
cout << "SEH: 程序正常结束" << endl;
}

然后通过调试就OK了,当调试器没有选择捕获你这个异常的时候,异常就是交给程序的VEH,SEH,异常过滤器来处理的。

 

采用VEH来处理:

 

LONG WINAPI MyPvectoredExceptionHandler(_EXCEPTION_POINTERS* ExceptionInfo)
{
if (ExceptionInfo->ContextRecord->Dr1 != 0 || ExceptionInfo->ContextRecord->Dr2 != 0 || ExceptionInfo->ContextRecord->Dr3 != 0 || ExceptionInfo->ContextRecord->Dr0 != 0)
{
printf("VEH 验证异常,程序正在被调试,即将退出程序\n");
ExitProcess(0);
}
else
{
printf("硬件断点的VEH验证正常,无硬件断点\n");
}
ExceptionInfo->ContextRecord->Eip += 3;
return EXCEPTION_CONTINUE_EXECUTION;
}
void TestVeh()
{
printf("test VEH\n");
AddVectoredExceptionHandler(1, MyPvectoredExceptionHandler);
__asm
{
mov eax, 0
mov[eax], 1
}
cout << "VEH: 跳过了异常代码" << endl;
cout << "VEH: 程序正常结束" << endl;
}

小结

首先需要理解硬件断点的原理,然后通过各种异常验证的时候比对硬件断点是否存在来进行验证。然后就是VEH,SEH,还有异常过滤器的使用方法。今天这个异常过滤器不知道为啥不行,明天再更新来解决这个异常过滤器的问题。