VEH+硬件断点实现无痕HOOK

hook的分类

hook方式有多种,这里做了一个系统性的总结对比,如下:

在这里插入图片描述

实际上第二种和第三种属于同一类型的hook,都是利用异常处理函数来处理,只是触发异常的方式不同

硬件断点hook原理

想要实现硬件断点hook,需要实现下面几个步骤

  1. 设置硬件断点
  2. 注册veh异常处理函数
  3. 编写异常处理函数,实现自己的hook代码

设置硬件断点

首先来说一下关于硬件断点hook的原理,在Windows API中存在一个重要的结构体PCONTEXT

typedef struct DECLSPEC_NOINITALL _CONTEXT {

    //
    // The flags values within this flag control the contents of
    // a CONTEXT record.
    //
    // If the context record is used as an input parameter, then
    // for each portion of the context record controlled by a flag
    // whose value is set, it is assumed that that portion of the
    // context record contains valid context. If the context record
    // is being used to modify a threads context, then only that
    // portion of the threads context will be modified.
    //
    // If the context record is used as an IN OUT parameter to capture
    // the context of a thread, then only those portions of the thread's
    // context corresponding to set flags will be returned.
    //
    // The context record is never used as an OUT only parameter.
    //

    DWORD ContextFlags;

    //
    // This section is specified/returned if CONTEXT_DEBUG_REGISTERS is
    // set in ContextFlags.  Note that CONTEXT_DEBUG_REGISTERS is NOT
    // included in CONTEXT_FULL.
    //

    DWORD   Dr0;
    DWORD   Dr1;
    DWORD   Dr2;
    DWORD   Dr3;
    DWORD   Dr6;
    DWORD   Dr7;

    //
    // This section is specified/returned if the
    // ContextFlags word contians the flag CONTEXT_FLOATING_POINT.
    //

    FLOATING_SAVE_AREA FloatSave;

    //
    // This section is specified/returned if the
    // ContextFlags word contians the flag CONTEXT_SEGMENTS.
    //

    DWORD   SegGs;
    DWORD   SegFs;
    DWORD   SegEs;
    DWORD   SegDs;

    //
    // This section is specified/returned if the
    // ContextFlags word contians the flag CONTEXT_INTEGER.
    //

    DWORD   Edi;
    DWORD   Esi;
    DWORD   Ebx;
    DWORD   Edx;
    DWORD   Ecx;
    DWORD   Eax;

    //
    // This section is specified/returned if the
    // ContextFlags word contians the flag CONTEXT_CONTROL.
    //

    DWORD   Ebp;
    DWORD   Eip;
    DWORD   SegCs;              // MUST BE SANITIZED
    DWORD   EFlags;             // MUST BE SANITIZED
    DWORD   Esp;
    DWORD   SegSs;

    //
    // This section is specified/returned if the ContextFlags word
    // contains the flag CONTEXT_EXTENDED_REGISTERS.
    // The format and contexts are processor specific
    //

    BYTE    ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];

} CONTEXT;

typedef CONTEXT *PCONTEXT;

这个结构体里保存着所有的寄存器信息,其中和硬件断点相关的字段有下面几个

DWORD   Dr0;
DWORD   Dr1;
DWORD   Dr2;
DWORD   Dr3;
DWORD   Dr6;
DWORD   Dr7;

和硬件调试相关的寄存器一共有6个。如果我们能获取到线程的Context环境,并且修改dr寄存器,就能给需要hook的地址下硬件断点。

注册VEH

接下来就进入到第二步,注册异常处理函数。当程序执行到hook点时,触发硬件断点,从而触发EXCEPTION_SINGLE_STEP异常,那么我们就可以自己注册一个VEH异常处理函数来处理这个异常,那么就可以在处理完成之后编写自己的hook代码

windwos操作系统专门针对异常的处理有一整套完整的机制,这里为了理解,简单介绍一下:windwos下3环进程运行时,如果遇到异常,大致的处理顺序如下:

  1. 先看看有没有调试器(通过编译器运行exe也算),如果有,就发消息给调试器让其处理;
  2. 如果没有调试器,或则调试器没处理,进入进程自己的VEH继续处理。VEH本质是个双向链表,存储了异常的handler代码,此时windwos会挨个遍历这个链表执行这些handler(感觉原理和vmp很像,估计vmp借鉴了这里的思路)
  3. 如果VEH还没处理好,接着由线程继续处理。线程同样有个异常接管的链表,叫SEH;windows同样会遍历SEH来处理异常
  4. 如果SEH还没处理好,继续给线程的UEH传递,UEH只有一个处理函数了
  5. 如果UEH还没处理好,就回到进程的VCH处理

基于windwos开发的应用数以万计,微软绝对不可能出厂时就考虑到所有的异常,其各种handler不太可能处理所有的异常,所以微软又开放了接口,让开发人员自定义异常的handler;对于开发人员来说,肯定是越靠前越好,所以这里选择VEH来添加自定义的handler(调试器是最先收到异常通知的,但外挂在正常使用时不太可能有调试的功能,除非开发人员自己单独开发调试器的功能,这样成本太高了)。windwos开放了一个API,叫AddVectoredExceptionHandler,可以给VEH添加用户自定义的异常处理handler,如下

AddVectoredExceptionHandler(1, PvectoredExceptionHandler)

函数有两个参数:第一个参数如果不是0,那么自定义的handler最先执行;如果是0,那么自定义的handler最后执行。这里我们当然希望自己的handler最先执行了,所以设置成1;另一个参数就是自定义的回调函数。

代码实现VEH无痕Hook

首先我们需要获取到当前的线程环境结构体

void SetSehHook()
{

	//遍历线程 通过openthread获取到线程环境后设置硬件断点
	HANDLE hTool32 = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
	if (hTool32!= INVALID_HANDLE_VALUE)
	{
		//线程环境结构体
		THREADENTRY32 thread_entry32;

		thread_entry32.dwSize = sizeof(THREADENTRY32);

		HANDLE hHookThread = NULL;

		//遍历线程
		if (Thread32First(hTool32,&thread_entry32))
		{
			do 
			{
				//如果线程父进程ID为当前进程ID
				if (thread_entry32.th32OwnerProcessID==GetCurrentProcessId())
				{
					hHookThread = OpenThread(THREAD_SET_CONTEXT | THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION, FALSE, thread_entry32.th32ThreadID);
					

					SuspendThread(hHookThread);//暂停线程

					//设置硬件断点
					CONTEXT thread_context = { CONTEXT_DEBUG_REGISTERS };
					thread_context.Dr0 = g_HookAddr;
					thread_context.Dr7 = 0x405;

					//设置线程环境 这里抛异常了

					DWORD oldprotect;
					VirtualProtect((LPVOID)g_HookAddr, 5, PAGE_EXECUTE_READWRITE, &oldprotect);//修改PTE p=1 r/w1=0

					SetThreadContext(hHookThread, &thread_context);

					ResumeThread(hHookThread);//线程跑起来吧~~~


					CloseHandle(hHookThread);
				}

			} while (Thread32Next(hTool32, &thread_entry32));
			


		}
		CloseHandle(hTool32);

	}

}

这里通过遍历线程,设置线程环境的方式来给所有的线程设置硬件断点。接着注册VEH异常处理函数

 AddVectoredExceptionHandler(1, (PVECTORED_EXCEPTION_HANDLER)ExceptionFilter);//添加VEH异常处理

接着编写回调函数

LONG NTAPI  ExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo)
{

	//判断当前异常码是否为硬件断点异常
	if (ExceptionInfo->ExceptionRecord->ExceptionCode== EXCEPTION_SINGLE_STEP)
	{
		
		//判断发生异常的地址是否和hook的地址一致
		if ((DWORD)ExceptionInfo->ExceptionRecord->ExceptionAddress == g_HookAddr)
		{

			//获取当前线程上下文
			PCONTEXT pcontext = ExceptionInfo->ContextRecord;

			//获取聊天记录
			RecvMsg(pcontext);
            
			//修复EIP 
			pcontext->Eip=(DWORD)&OriginalFunc;

			
			//异常处理完成 让程序继续执行
			return EXCEPTION_CONTINUE_EXECUTION;

		}
	}
	return EXCEPTION_CONTINUE_SEARCH;
}

最后需要修复EIP,让程序正常运行,我这里时让EIP指向一个逻函数,再通过裸函数跳转到目标返回地址


void __declspec(naked) OriginalFunc(void)
{
	__asm
	{
		//调用被覆盖的call
		call OverReciveMsgCallAddr;
		//跳转到返回地址
		jmp RetkReciveMsgAddr;

	}
}

说说一路踩过的坑

整个过程从原理看上去很简单,但实际操作起来会遇到几个坑。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wMQoCzM3-1632471774460)(VEH+硬件断点实现无痕HOOK.assets/image-20210924154835965.png)]

遇到的第一个问题,当我直接注入dll到目标进程时,AddVectoredExceptionHandler这个函数直接崩溃,但是我用VS调试dll的时候,就不报错了,并且回调函数正常执行。

实际上这个问题的原因是由于调用了OutputDebugStringA造成的。

在内部,调试字符串作为异常处理。OutputDebugString 使用 DBG_PRINTEXCEPTION_C(定义为 0x40010006)和字符串地址和大小作为异常参数调用 RaiseException。

OutputDebugString是通过抛异常的方式来实现的,如果在VEH相关的地方调用这个函数就会导致无限递归,然后导致堆栈溢出

第二个问题是硬件断点无法设置成功,后来在看雪的评论区找到了答案。

SetThreadContext:
Do not try to set the context for a running thread; the results are unpredictable.

这是CSDN  上的对SetThreadContext 解释。
尤其是对自身的上下文进行设置,这很可能使得自身线程崩溃或者得不到想要的结果

历遍所有线程,SuspendThread,然后SetThreadContext ,再 ResumeThread ,同时排除自身线程。

需要同时遍历所有的线程,并且排除自身进程设置硬件断点,才能达到全局硬断的效果

实际效果

在这里插入图片描述

我这里是hook了微信的接收消息,但是由于不能使用OutputDebugStringA函数,所以用读写文本的方式来输出

在这里插入图片描述

小结

利用这种方式可以实现在不破坏代码的前提下进行hook,可以完美的避开crc检测,不容易被分析人员发现。由于是在异常处理函数中实现的hook逻辑,还能顺手给微信加一个反调试,防止别人逆向自己写的程序。

附上完整工程链接:
https://download.csdn.net/download/qq_38474570/24415530?spm=1001.2014.3001.5503

关于veh hook的对抗

既然这种方式那么隐蔽,那么假如我们调试的程序采用了类似的hook或者反调试手段,应该怎么处理呢?实际上处理的方式有两种

  1. 再编写一个VEH异常处理函数。veh是异常处理链,系统每次都先调用最顶层的那个,再根据最顶层那个的返回值来决定是否调下一个。我们只要再注册一个异常处理函数,返回处理成功不调用下一个,就能把之前的veh顶下去
  2. OD设置系统断点断下, 再下断AddVectoredExceptionHandler

参考文章

xxxx(六):吊打CRC32检测的无痕hook:https://www.cnblogs.com/theseventhson/p/14399097.html

SEH + 硬件断点HOOK:https://blog.csdn.net/weixin_42052102/article/details/83719791

关于VEH+硬件断点打内存补丁:https://bbs.pediy.com/thread-154035.htm

https://stackoverflow.com/questions/25634376/why-does-addvectoredexceptionhandler-crash-my-dll

  • 11
    点赞
  • 62
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
以下是一个使用 Windows VEH(Vectored Exception Handling)实现硬件断点的示例: ```cpp #include <windows.h> // 全局变量用于保存旧的异常处理器 PVECTORED_EXCEPTION_HANDLER g_pPrevExceptionHandler = nullptr; // Vectored Exception Handler 回调函数 LONG CALLBACK VehHandler(EXCEPTION_POINTERS* pExceptionPointers) { // 检查是否是硬件断点异常(EXCEPTION_SINGLE_STEP) if (pExceptionPointers->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) { // 处理硬件断点异常的逻辑 // ... // 可以在这里进行一些自定义的操作,如记录断点发生的位置、执行特定的代码等 // 恢复标志寄存器,以便继续执行 pExceptionPointers->ContextRecord->EFlags &= ~0x100; // 通过清除 TF(Trap Flag)位恢复标志寄存器 // 返回处理完成的标志 return EXCEPTION_CONTINUE_EXECUTION; } // 如果不是硬件断点异常,交给之前的异常处理器继续处理 if (g_pPrevExceptionHandler) { return g_pPrevExceptionHandler(pExceptionPointers); } // 如果没有之前的异常处理器,则返回异常处理完成的标志 return EXCEPTION_CONTINUE_SEARCH; } // 设置硬件断点 bool SetHardwareBreakpoint(void* address) { // 注册 Vectored Exception Handler g_pPrevExceptionHandler = AddVectoredExceptionHandler(1, VehHandler); if (!g_pPrevExceptionHandler) { // 注册失败 return false; } // 设置硬件断点 CONTEXT context; memset(&context, 0, sizeof(CONTEXT)); context.ContextFlags = CONTEXT_DEBUG_REGISTERS; // 获取当前线程的上下文 if (!GetThreadContext(GetCurrentThread(), &context)) { // 获取上下文失败 return false; } // 设置硬件断点 context.Dr0 = (DWORD_PTR)address; context.Dr7 |= 1; // 启用第一个硬件断点 // 设置上下文 if (!SetThreadContext(GetCurrentThread(), &context)) { // 设置上下文失败 return false; } // 设置成功 return true; } int main() { void* breakpointAddress = nullptr; // 设置硬件断点的地址 if (SetHardwareBreakpoint(breakpointAddress)) { // 成功设置硬件断点 // ... } else { // 设置硬件断点失败 // ... } return 0; } ``` 这个示例代码中,我们首先定义了一个全局变量 `g_pPrevExceptionHandler`,用于保存旧的异常处理器。然后,在 `VehHandler` 函数中,我们判断是否是硬件断点异常(EXCEPTION_SINGLE_STEP),如果是,则执行自定义的处理逻辑,并恢复标志寄存器以便继续执行。如果不是硬件断点异常,则将其交给之前的异常处理器继续处理。 在 `SetHardwareBreakpoint` 函数中,我们首先注册 Vectored Exception Handler,并保存旧的异常处理器。然后,获取当前线程的上下文,设置硬件断点的地址和相关的标志,最后将上下文设置回线程中。如果所有步骤都成功,则表示成功设置了硬件断点。 请注意,硬件断点的设置和使用可能会受到操作系统和硬件平台的限制,具体实现可能会有所不同。确保在实际使用时参考相关的文档和API参考以获取更详细和准确的信息。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鬼手56

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值