This page looks best with JavaScript enabled

无痕HOOK(硬断+VEH)

 ·  ☕ 3 min read · 👀... views

平常常用的三环Hook通常是InlineHook和虚Hook,这两种Hook方式均是通过修改内存来劫持控制流,InlineHook通过修改代码段上的代码而虚Hook通过修改虚表指针。因为均对内存做了修改(且往往Hook的位置都是敏感内存),很容易被诸如CRC之类的手段检测到。故学习了一下三环下的无痕HOOK,注意这里的无痕指的仅仅是不对代码段等内存产生修改,而不是不会被发现。

在x86架构下有一组特殊的寄存器叫做硬件断点寄存器,分别是Dr0-Dr7。其中Dr0-Dr3这四个寄存器用于于设置硬件断点的,Dr4和Dr5由系统保留,Dr6用于显示哪个硬件调试寄存器引发的断点,Dr7则是用于控制断点属性。如果我们能够设置硬件断点到我们想Hook的位置,那样程序运行到该处时就会抛出一个异常,转而进入异常处理函数;如果我们设置了与之匹配的异常处理函数,则可以在异常发生时劫持得到程序的控制流程流。 硬件断点寄存器可以通过修改线程上下文结构体(ThreadContext)来设置,而异常处理函数通常则是使用VEH。

下面来看一个demo,这个Demo实现了硬断MessageBoxA这个API

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
HMODULE hUser32 = GetModuleHandleA("user32.dll");
size_t hookaddr = (size_t)GetProcAddress(hUser32, "MessageBoxA");

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    {
        AddVectoredExceptionHandler(1, (PVECTORED_EXCEPTION_HANDLER)ExceptionHandler);
        if(SetHwBreakpoint() == FALSE)  printf("SetHwBreakpoint Error:%d\n", GetLastError());
        break;
    }
    case DLL_THREAD_ATTACH:     // 对每一个新创建的线程均添加硬件断点
    {
        SetHwBreakpoint();
    }
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

BOOL SetHwBreakpoint()
{
    CONTEXT ctx;
    ctx.ContextFlags = CONTEXT_ALL;
    GetThreadContext(GetCurrentThread(), &ctx);
    ctx.Dr0 = hookaddr;
    ctx.Dr7 = 0x1;
    return SetThreadContext(GetCurrentThread(), &ctx);
}

size_t NTAPI ExceptionHandler(EXCEPTION_POINTERS* ExceptionInfo)
{
    if ((size_t)ExceptionInfo->ExceptionRecord->ExceptionAddress == hookaddr)
    {
        printf("Hook!\n");
        return EXCEPTION_CONTINUE_EXECUTION;
    }
    else
    {
        //在异常handler里重设drx防止断点被意外清除
        ExceptionInfo->ContextRecord->Dr0 = hookaddr;
        ExceptionInfo->ContextRecord->Dr7 = 0x405;
        return EXCEPTION_CONTINUE_SEARCH;
    }
}

如果用心的去试一下,就会发现这个Demo在某些情况下是无法实现Hook的,比如在远线程注入该Dll的时候就发现Hook不住MessageboxA

为什么会这样呢?这里又是一个点,就是一个程序的上下文(寄存器等信息)是绑定于线程还是进程的。三环的远线程注入DLL本质是将Dll写入该进程的内存中,然后CreateRemoteThread在DllMain的位置创建了一个线程运行Dll的Main函数。DllMain函数中调用了SetHwBreakpoint对硬件断点寄存器做了修改,但此时这一条线程并不是程序原先运行代码的线程(主线程),而是我们远程创建的新线程,故设置出来的硬件断点仅对这条用于初始化dll的新线程有效。 正确的做法应该是枚举线程,对每一条线程均设置上硬件断点。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    {
        // DebugBreak();
        printf("hUser32:%p\n", hUser32);
        printf("hook:%p\n", hookaddr);

        AddVectoredExceptionHandler(1, (PVECTORED_EXCEPTION_HANDLER)ExceptionHandler);
        SetHBToAllThread();     // 为所有线程设置硬件断点
        break;
    }
    case DLL_THREAD_ATTACH:
    {
        // 为新创建的线程设置硬件断点
        SetHwBreakpoint(GetCurrentThread());
    }
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

void SetHwBreakpoint(HANDLE hThread)
{
    CONTEXT ctx;
    ctx.ContextFlags = CONTEXT_ALL;
    GetThreadContext(hThread, &ctx);
    ctx.Dr0 = hookaddr;
    ctx.Dr7 = 0x1;
    if (SetThreadContext(hThread, &ctx) == FALSE)
        printf("SetHwBreakpoint Error:%d\n", GetLastError());
}
// 枚举线程,并对每一条已存在的线程设置硬件断点
void SetHBToAllThread() {
    HANDLE hThreadShot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, GetCurrentProcessId());
    THREADENTRY32* threadInfo = new THREADENTRY32;
    HANDLE hThread = NULL;

    threadInfo->dwSize = sizeof(THREADENTRY32);
    int cnt = 0;

    while (Thread32Next(hThreadShot, threadInfo) != FALSE)
    {
        if (GetCurrentProcessId() == threadInfo->th32OwnerProcessID)
        {
            cnt++;
            printf("ThreadId:%x\n", threadInfo->th32ThreadID);
            hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, threadInfo->th32ThreadID);
            SetHwBreakpoint(hThread);
            CloseHandle(hThread);
        }
    }
    CloseHandle(hThreadShot);
}
Share on

Qfrost
WRITTEN BY
Qfrost
CTFer, Anti-Cheater, LLVM Committer