https://www.cnblogs.com/theseventhson/p/14324562.html 之前这里做了接受消息的hook,用的就是最初级的hook方式: jmp到我们自己的处理逻辑。上面也分析了,这种方式缺点非常明显;最牛逼的神级hook:VT读写分离前面已经介绍过了,今天继续介绍高级的hook方式:硬件断点;
现代软件开发,尤其是大型软件开发,绝对不可能一步到位,开发期间肯定存在各种bug。为了方便找到这些bug,软件上有专门的调试机制,比如在某行代码下软件断点,然后步过、步进等。这里软件断点本质就是在用户指定的地址改写成0xCC,也就是int 3指令,cpu执行到这里后就产生异常,然后由中断向量表的3号routine来处理这个异常。除了软件断点,x86架构的cpu也支持设置硬件断点,整个图示图下:
![[Rootkit] 无痕 hook - 硬件断点 [Rootkit] 无痕 hook - 硬件断点](/default/index/img?u=L2RlZmF1bHQvaW5kZXgvaW1nP3U9YUhSMGNITTZMeTlwYldjdFlteHZaeTVqYzJSdWFXMW5MbU51THpJd01qRXdOakV3TWpBMU1qRTRNakU1TG5CdVp6OTRMVzl6Y3kxd2NtOWpaWE56UFdsdFlXZGxMM2RoZEdWeWJXRnlheXgwZVhCbFgxcHRSblZhTTNCdldsYzFibUZIVm5Ca1Iyc3NjMmhoWkc5M1h6RXdMSFJsZUhSZllVaFNNR05JVFRaTWVUbHBZa2M1Ymt4dFRucGFSelIxWW0xV01Fd3hUbkJpVnpsMVRucHJOQ3h6YVhwbFh6RTJMR052Ykc5eVgwWkdSa1pHUml4MFh6Y3c=)
和硬件调试相关的寄存器一共有7个:DR0-DR3分别设置需要断下的地址,DR7可以控制DR0-DR3是否有效。如果需要启用这4个调试寄存器,DR7要设置为0b01010101,也就是L0\L1L2\L3都要为1;
正式介绍代码前,先介绍一个重要的结构体:PCONTEXT,如下:
typedef struct DECLSPEC_NOINITALL _CONTEXT {
DWORD ContextFlags;
DWORD Dr0;
DWORD Dr1;
DWORD Dr2;
DWORD Dr3;
DWORD Dr6;
DWORD Dr7;
FLOATING_SAVE_AREA FloatSave;
DWORD SegGs;
DWORD SegFs;
DWORD SegEs;
DWORD SegDs;
DWORD Edi;
DWORD Esi;
DWORD Ebx;
DWORD Edx;
DWORD Ecx;
DWORD Eax;
DWORD Ebp;
DWORD Eip;
DWORD SegCs;
DWORD EFlags;
DWORD Esp;
DWORD SegSs;
BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
} CONTEXT;
typedef CONTEXT *PCONTEXT;
这个结构体保存了常用的所有寄存器,OD、x32dbg、x64dbg等常见的调试器都用了这个结构体读取某个时间点时进程的寄存器,分析人员也可以直接在调试器的界面更改寄存器的值,非常方便。这些功能都是通过读写PCONTEXT结构体实现的。那么问题来了,怎么才能得到PCONTEXT结构体了?
PCONTEXT为调试而生,为了得到这个结构体,就要想办法产生异常;windwos操作系统专门针对异常的处理有一整套完整的机制,这里为了理解,简单介绍一下:windwos下3环进程运行时,如果遇到异常(比如除0),大致的处理顺序如下:
- 先看看有没有调试器(通过编译器运行exe也算),如果有,就发消息给调试器让其处理;
- 如果没有调试器,或则调试器没处理,进入进程自己的VEH继续处理。VEH本质是个双向链表,存储了异常的handler代码,此时windwos会挨个遍历这个链表执行这些handler(感觉原理和vmp很像,估计vmp借鉴了这里的思路)
- 如果VEH还没处理好,接着由线程继续处理。线程同样有个异常接管的链表,叫SEH;windows同样会遍历SEH来处理异常
- 如果SEH还没处理好,继续给线程的UEH传递,UEH只有一个处理函数了
- 如果UEH还没处理好,就回到进程的VCH处理;
基于windwos开发的应用数以万计,微软绝对不可能出厂时就考虑到所有的异常,其各种handler不太可能处理所有的异常,所以微软又开放了接口,让开发人员自定义异常的handler;对于开发人员来说,肯定是越靠前越好,所以这里选择VEH来添加自定义的handler(调试器是最先收到异常通知的,但外挂在正常使用时不太可能有调试的功能,除非开发人员自己单独开发调试器的功能,这样成本太高了)。windwos开放了一个API,叫AddVectoredExceptionHandler,可以给VEH添加用户自定义的异常处理handler,如下:
AddVectoredExceptionHandler(1, PvectoredExceptionHandler)
函数有两个参数:第一个参数如果不是0,那么自定义的handler最先执行;如果是0,那么自定义的handler最后执行。这里我们当然希望自己的handler最先执行了,所以设置成1;另一个参数就是自定义的handler了,这个函数的原型:
LONG PvectoredExceptionHandler(
_EXCEPTION_POINTERS *ExceptionInfo
)
{...}
继续追踪这个函数的参数,如下:
typedef struct _EXCEPTION_POINTERS {
PEXCEPTION_RECORD ExceptionRecord;
PCONTEXT ContextRecord;
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;
这里终于得到了我们想要的PCONTEXT;这个PCONTEXT只有在程序出异常时windwos才会在VEH暴露出来,开发人员才能进一步修改DR寄存器地值,所以这里要先人为产生软件异常(比如设置0xCC),让后由我们自定义的handler接管,得到PCONTEXT后就能愉快的修改DR寄存器组了;
由于各种原因,完整的代码就不展示了,这里展示核心的片段:
LONG _stdcall PvectoredExceptionHandler(PEXCEPTION_POINTERS val)
{
unsigned _eip = val->ContextRecord->Eip;
if (val->ExceptionRecord->ExceptionCode == STATUS_BREAKPOINT)
{
PHOOKPOINT point = htdHook2Ptr->Points.FindPoint((LPVOID)_eip);
if (point)
{
if (point->GetHookBack2()(val->ContextRecord))
{
val->ContextRecord->Eip = (unsigned)point->CodeFix;
}
else
{
val->ContextRecord->Eip = (unsigned)point->AddressRet;
}
return EXCEPTION_CONTINUE_EXECUTION;
}
else return EXCEPTION_CONTINUE_SEARCH;
}
if (val->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP)
{
auto point=htdHook2Ptr->DbgPoints.FindPoint((LPVOID)_eip);
if (point)
{
if (point->DestCall(val->ContextRecord))
{
val->ContextRecord->Dr7 = 0;
val->ContextRecord->EFlags |= 0x100;
}
else
{
return EXCEPTION_CONTINUE_EXECUTION;
}
}
else
{
htdHook2Ptr->DbgPoints.OpenDbg(val->ContextRecord);
}
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_SEARCH;
}
void ThreadTrap()
{
_asm
{
mov eax,eax
mov eax,eax
mov eax,eax
}
}
bool InThread(HOOKREFS2)
{
htdHook2Ptr->UnHook((LPVOID)_EIP);
htdHook2Ptr->DbgPoints.OpenDbg(val);
return false;
}
void htdHook2::Init()
{
SetHook(ThreadTrap, 1, InThread, ThreadTrap);
DWORD dRet=(DWORD)ThreadTrap;
_asm call dRet;
}
htdHook2::htdHook2()
{
htdHook2Ptr = this;
PPointLast = &Points;
AddVectoredExceptionHandler(1, PvectoredExceptionHandler);
}
void htdHook2::SetHook(LPVOID Address, uchar len, HOOKBACK2 hookBack,LPVOID AddressRet)
{
DWORD dOld;
DWORD dNew;
VirtualProtect(Address, 0x1, PAGE_EXECUTE_READWRITE, &dOld);
PPointLast = PPointLast->AddPonit(Address, AddressRet, hookBack, len);
char* code = (char*)(Address);
code[0] = 0xCC;
VirtualProtect(Address, 0x1, dOld, &dNew);
}
bool htdHook2::SetHook(LPVOID Address, HOOKBACK2 hookBack, LPVOID AddressRet)
{
return DbgPoints.AddHookPoint(Address, hookBack, AddressRet);
}
void htdHook2::UnHook(LPVOID Address)
{
PHOOKPOINT _point=Points.FindPoint(Address);
if (_point)
{
_point->Recover();
_point->BackPoint->NextPoint = _point->NextPoint;
if (_point->NextPoint)_point->NextPoint->BackPoint = _point->BackPoint;
delete _point;
}
}
这里设置context的各个关键寄存器的值。为了给调试寄存器设置值,需要先得到PCONTEXT
void DBGPOINT::OpenDbg(PCONTEXT _context)
{
_context->Dr0 = (DWORD)Point[0].Address;
_context->Dr1 = (DWORD)Point[1].Address;
_context->Dr2 = (DWORD)Point[2].Address;
_context->Dr3 = (DWORD)Point[3].Address;
_context->Dr7 = 0b01010101;
}
自定义的消息hook代码:由于并未破坏机器码,所以不需要到处跳转和修复,代码少了很多,逻辑也明晰了很多!拦截到的消息直接在DiologBox的Edit打印出来:
CString GetMsgByAddress(DWORD memAddress)
{
CString tmp;
DWORD msgLength = *(DWORD*)(memAddress + 4);
if (msgLength > 0) {
WCHAR* msg = new WCHAR[msgLength + 1]{ 0 };
wmemcpy_s(msg, msgLength + 1, (WCHAR*)(*(DWORD*)memAddress), msgLength + 1);
tmp = msg;
delete[]msg;
}
return tmp;
}
CWndMain* pCWndMain{};
bool hookMsg(HOOKREFS2) {
CTime time = CTime::GetCurrentTime();
CString strTime = time.Format(_T("%Y-%m-%d %H:%M:%S"));
DWORD** msgAddress = (DWORD**)(val->Esp);
CString wid = GetMsgByAddress(**msgAddress + 0x40);
CString fullmsg = GetMsgByAddress(**msgAddress + 0x68);
CString isWid = GetMsgByAddress(**msgAddress + 0x164);
CString md5 = GetMsgByAddress(**msgAddress + 0x178);
msg = wid + fullmsg + isWid + md5;
msg.Format(_T("\r\nwid=%s, msg=%s,isWid=%s, md5=%s, time=%s\r\n"), wid, fullmsg, isWid, md5, strTime);
pCWndMain->EDIT_SHOWMSG.SetSel(-1,-1);
pCWndMain->EDIT_SHOWMSG.ReplaceSel(msg);
return true;
}
用x32dbg打开看:原程序的机器码完好无损,完全看不出被改过!
![[Rootkit] 无痕 hook - 硬件断点 [Rootkit] 无痕 hook - 硬件断点](/default/index/img?u=L2RlZmF1bHQvaW5kZXgvaW1nP3U9YUhSMGNITTZMeTlwYldjdFlteHZaeTVqYzJSdWFXMW5MbU51THpJd01qRXdOakV3TWpBMU5qTXpNelV6TG5CdVp6OTRMVzl6Y3kxd2NtOWpaWE56UFdsdFlXZGxMM2RoZEdWeWJXRnlheXgwZVhCbFgxcHRSblZhTTNCdldsYzFibUZIVm5Ca1Iyc3NjMmhoWkc5M1h6RXdMSFJsZUhSZllVaFNNR05JVFRaTWVUbHBZa2M1Ymt4dFRucGFSelIxWW0xV01Fd3hUbkJpVnpsMVRucHJOQ3h6YVhwbFh6RTJMR052Ykc5eVgwWkdSa1pHUml4MFh6Y3c=)
之前调试时人为下了软件断点,这里完全看不到硬件断点!
![[Rootkit] 无痕 hook - 硬件断点 [Rootkit] 无痕 hook - 硬件断点](/default/index/img?u=L2RlZmF1bHQvaW5kZXgvaW1nP3U9YUhSMGNITTZMeTlwYldjdFlteHZaeTVqYzJSdWFXMW5MbU51THpJd01qRXdOakV3TWpBMU5qUTVOekExTG5CdVp6OTRMVzl6Y3kxd2NtOWpaWE56UFdsdFlXZGxMM2RoZEdWeWJXRnlheXgwZVhCbFgxcHRSblZhTTNCdldsYzFibUZIVm5Ca1Iyc3NjMmhoWkc5M1h6RXdMSFJsZUhSZllVaFNNR05JVFRaTWVUbHBZa2M1Ymt4dFRucGFSelIxWW0xV01Fd3hUbkJpVnpsMVRucHJOQ3h6YVhwbFh6RTJMR052Ykc5eVgwWkdSa1pHUml4MFh6Y3c=)