C++ Windows hook

1.前言
这个反调试需要hook,但实际上有很多的限制。比如win10/win11的超强patchgurad,让做这件事情变得困难。但是大致hook的一个模板记录,总体遵循几个步骤:模块首地址-》段首地址-》段内搜索函数特征-》找到函数-》hook。本篇看下。

2.概括
ntoskrnl.exe里面有个函数HvlGetQpcBias,这个函数可以适当的Hook来检测或者更改一些东西。.

__int64 HvlGetQpcBias(){  return *((_QWORD *)HvlpReferenceTscPage + 3);}

函数体

0: kd> x *!*HvlGetQpcBiasfffff803`22d91e40 nt!HvlGetQpcBias (HvlGetQpcBias)0: kd> uf fffff803`22d91e40nt!HvlGetQpcBias:fffff803`22d91e40 488b0589be9800  mov     rax,qword ptr [nt!HvlpReferenceTscPage (fffff803`2371dcd0)]fffff803`22d91e47 488b4018        mov     rax,qword ptr [rax+18h]fffff803`22d91e4b c3              ret

这里可以先获取ntoskrnl.exe模块首地址

ZwQuerySystemInformation(11, &length, 0, &length);const unsigned long tag = 'VMON';PSYSTEM_MODULE_INFORMATION system_modules = (PSYSTEM_MODULE_INFORMATION)ExAllocatePoolWithTag(NonPagedPool, length, tag);NTSTATUS status = ZwQuerySystemInformation(11, system_modules, length, 0);for (unsigned long long i = 0; i < system_modules->ulModuleCount; i++){   PSYSTEM_MODULE mod = &system_modules->Modules[i];   if (strstr(mod->ImageName, name))   {     addr = (unsigned long long)mod->Base;     if (size) *size = (unsigned long)mod->Size;     break;   }}ExFreePoolWithTag(system_modules, tag);return addr;

找到首地址后,找到它的text段,因为HvlGetQpcBias在text段,参考下面

.text:000000014036CA00  HvlGetQpcBias   proc near               ; DATA XREF: .pdata:00000001400E7F24↑o.text:000000014036CA00  ; HvlGetEnlightenmentInfo+46F↓o.text:000000014036CA00 48 8B 05 B1 12 9B 00  mov     rax, cs:HvlpReferenceTscPage.text:000000014036CA07 48 8B 40 18  mov     rax, [rax+18h].text:000000014036CA0B C3 retn.text:000000014036CA0B                               HvlGetQpcBias   endp

找text段

PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)addr;if (dos->e_magic != IMAGE_DOS_SIGNATURE) return 0;
PIMAGE_NT_HEADERS64 nt = (PIMAGE_NT_HEADERS64)(addr + dos->e_lfanew);if (nt->Signature != IMAGE_NT_SIGNATURE) return 0;
PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(nt);for (unsigned short i = 0; i < nt->FileHeader.NumberOfSections; i++){    PIMAGE_SECTION_HEADER p = &section[i];
   if (strstr((const char*)p->Name, name))   {      unsigned long long result = find_pattern(addr + p->VirtualAddress, p->Misc.VirtualSize, pattern, mask);     if (result) return result;   }}

找到text段之后,就可以搜索匹配特征了,先找HvlpReferenceTscPage。因为HvlGetQpcBias里面调用了它,需要在HvlGetQpcBias里面返回这个。

fffff803`22d91e40 488b0589be9800  mov     rax,qword ptr [nt!HvlpReferenceTscPage (fffff803`2371dcd0)]fffff803`22d91e47 488b4018        mov     rax,qword ptr [rax+18h]fffff803`22d91e4b c3              ret
特征码可以浓缩成如下:\x48\x8b\xCC\xCC\xCC\xCC\xCC\x48\x8b\xCC\xCC\C3
通过一个for循环,把函数HvlpReferenceTscPage地址找出来size是pSection->Misc.VirtualSize的长度,也就是text段在内存分配的长度。len也就是上面特征码所占的长度for (ULONG_PTR i = 0; i < size - len; i++){  BOOLEAN found = TRUE;  for (ULONG_PTR j = 0; j < len; j++)  {    if (pattern[j] != wildcard && pattern[j] != ((PCUCHAR)base)[i + j]) //只要特征码符合第一个不等于0xCC,并且相等,循环到len长度结束,就表示找到了函数指令lea所在的地址。    {      found = FALSE;      break;    }  }  if (found != FALSE)  {    *hvlp = (PUCHAR)base + i;    return STATUS_SUCCESS;  }}
hvlp函数HvlpReferenceTscPage动态内存的地址

因为hvlp是指令lea所在的地址,所以这里需要计算出函数HvlpReferenceTscPage真正的地址

//这里的+7是lea指令占7字节,加了之后到lea指令下一个地址//把lea指令下一个地址加上上一个地址的机器码*(ULONG*)(hvlp + 3)就等于HvlpReferenceTscPage//这里的hvlp+3因为lea指令占7个字节,前3个字节是前置码,opcode,以及mdo/rm。后面的四个是计算lea指令的立即数,所以这里加3
hvlp = hvlp + 7+*(ULONG*)(hvlp + 3)

HvlGetQpcBias函数也以同样的方法找到其动态内存地址(地址用hvgq表示),然后进行如下操作

同上面一样hvgq = hvlp + 7+*(ULONG*)(hvgq + 3)

可以用下面的函数来hook HvlGetQpcBias.

 __int64 Hookhvgq(){   if (ExGetPreviousMode() != KernelMode)//如果不是内核态调试,则hook,否则不hook   {     hook();   }   return *((ULONG64*)(*((ULONG64*)HvlpReferenceTscPage)) + 3);//这里加3是因为HvlGetQpcBias函数返回值是 *((_QWORD *)HvlpReferenceTscPage + 3);}

这里的hook()函数其实是搜索栈顶栈底,通过特征码找到

SystemCallIndex, SystemCallFunction,然后替换相应的函数。代码太多,这点下一篇看下