【Day 09】Hook 的奇妙冒险 - Ring3 Hook

环境

  • Windows 10 21H1
  • Visual Studio 2019
  • x64dbg Aug 2 2020, 13:56:14

简介

Hook 这项技巧用於各种作业系统,可以达到拦截程序的效果。它也同时是红队与蓝队常用的手段,毕竟「攻防本一体」,红队希望能利用 Hook 达到获得机敏资讯或绕过验证和侦测等等效果,而篮队则是为了可以有效监控特定 Process。

原理上也十分浅显易懂,基本上就是把原本程序要执行的部分改掉,把 Instruction Pointer 跳到自己定义的程序段。那具体上是要改成什麽,要怎麽把自己的程序放到目标 Process,程序里面又要做些什麽,这些在下面会说明。

既然出现了 Hook 这个技巧,当然也会有侦测 Hook 的技术,而有了侦测 Hook 的技术,就也会有绕过侦测 Hook 技术的 Hook 技巧,如此循环...

Ring3 Hook vs Ring0 Hook

所谓 Ring3(User Mode) Hook,就是在不会碰到 Kernel API 的条件下所完成的 Hook。

根据上图,以 Hook CreateFile 为例,在呼叫此函数时,其实是使用 Kernel32.dll 所 Export 的函数 CreateFile。Ring3 Hook 就可以把 CreateFile 修改成 jmp <address>,而 address 就是我们所要更换执行的程序。

继续 Hook CreateFile 的例子,Ring0(Kernel Mode) Hook 则是要再往後一点。在呼叫 Kernel32.dll 中的 CreateFile 後,其中又会呼叫 Ntdll.dll 的 NtWriteFile,再往下走会去 SSDT(System Services Descriptor Table) 查表并执行同名的 Kernel API - NtWriteFile。因为 SSDT 有存放函数地址,因此 Ring0 Hook 可以透过修改 SSDT 达到 Hook 效果。

其实 Ring0 Hook 还不只可以 Hook SSDT,还有许多手法可以达到相同功能。但是在 Windows XP、Windows Server 2003 版本後,因为 PatchGuard 的缘故,在一般的情况下无法做 Kernel Patch。而之後陆续出现绕过 PatchGuard 的方法,以及微软的修补,与再度的绕过就又是另一个故事了。

这篇文主要还是针对 Ring3 Hook 讲解,介绍我觉得不错的 Hook Library,并说明其实作原理。

Hook Library

以下找了几个开源的 Hook Library,到了最近都还有在更新,自己试了也觉得不错。

这次的 POC 将会使用 MinHook 来实作简单的 Hook。

实作

实作流程

  1. 定义要 Hook 的函数,因为等等要把它改成自己的函数,所以最好参数可以对应原本的函数。这次的目标是 MessageBoxW,对这个 API 不熟的可以看看 MSDN: MessageBoxW
  2. 将目标函数的资讯记下来,做好 Hook 的前置作业。
  3. 启用 Hook,将目标函数前几 Byte 改掉,jmp 到自己定义的函数。
  4. 停用 Hook,把目标函数改回来

POC

程序专案放在我的 GitHub zeze-zeze/2021iThome

#include <Windows.h>
#include "MinHook.h"

#if defined _M_X64
#pragma comment(lib, "libMinHook.x64.lib")
#elif defined _M_IX86
#pragma comment(lib, "libMinHook.x86.lib")
#endif

// 1. 定义要 Hook 的函数,因为等等要把它改成自己的函数,所以最好参数可以对应原本的函数。
typedef int (WINAPI* MESSAGEBOXW)(HWND, LPCWSTR, LPCWSTR, UINT);
MESSAGEBOXW fpMessageBoxW = NULL;

int WINAPI DetourMessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType) {
    return fpMessageBoxW(hWnd, L"Hooked\n", lpCaption, uType);
}

int main() {
    // 2. 将目标函数的资讯记下来,做好 Hook 的前置作业。
    if (MH_Initialize() != MH_OK) {
        return 1;
    }
    if (MH_CreateHook(&MessageBoxW, &DetourMessageBoxW, reinterpret_cast<LPVOID*>(&fpMessageBoxW)) != MH_OK) {
        return 1;
    }

    // 3. 启用 Hook,将目标函数前几 Byte 改掉,jmp 到自己定义的函数。
    if (MH_EnableHook(&MessageBoxW) != MH_OK)
    {
        return 1;
    }

    // 测试目标函数被改掉後有没有变成自己定义的函数,这边应该要跳出 Hooked
    MessageBoxW(NULL, L"Not hooked\n", L"MinHook Example", MB_OK);

    // 4. 停用 Hook,把目标函数改回来
    if (MH_DisableHook(&MessageBoxW) != MH_OK) {
        return 1;
    }

    // 测试目标函数有没有被改回来,这边应该要跳出 Not hooked
    MessageBoxW(NULL, L"Not hooked\n", L"MinHook Example", MB_OK);

    if (MH_Uninitialize() != MH_OK) {
        return 1;
    }
    return 0;
}

开 x64dbg 看看

基本上在 MSDN 最下面都会说明该函数适用於什麽环境,存在哪个 Library、DLL 中,以下是 MessageBoxW 的资讯

因此可以透过 x64dbg 找到函数在 Process 中的位址

这是在 Hook 之前的 MessageBoxW

在执行 MH_EnableHook 之後,可以看到第一个 Instruction 变成 jmp 了,这边注意被盖掉的 Instruction 是 sub rsp, 38; xor r11d, r11d;

到了这边大家可能会有个疑问,如果现在原本的 MessageBoxW 的前几个 Byte 被改掉了,难道在 DetourMessageBoxW 中呼叫 fpMessageBoxW 的时候不会出错吗?
这部分就继续往下追,下图是在 DetourMessageBoxW 准备呼叫 fpMessageBoxW 的状况

而 fpMessageBoxW 中就有把原本被盖掉的部分写到里面,也就是 sub rsp, 38; xor r11d, r11d;,最後後才跳回原本的 MessageBoxW 继续执行。

最後在执行 MH_DisableHook 又把函数改回来了,以上就是 Hook 的基本原理。

其他

如果不想使用 Hook Library,而是想要自己实作 Hook,可以去翻翻上面推荐的开源程序码。最基本的原理就是用 VirtualProtect 把函数的权限改成可写,因为一般因为 DEP/NX 保护无法直接写 Text Section。再来用 VirtualAlloc 申请一块记忆体将自己的程序写入。然而实作方法不只一种,上面说的只是其中一种,有时会为了不容易被侦测,甚至不使用 VirtualAlloc,而是使用 Code Caves。

接下来会介绍如何侦测 Hook,还有如何绕过侦测,有种像是玩捉迷藏的感觉。

参考资料


<<:  Day 12 - 阵列 b

>>:  [Day24]Funny Encryption Method

Day 0xB UVa948 Fibonaccimal Base

Virtual Judge ZeroJudge 题意 输入十进位的数字,输出对应的费氏进位表示法 ...

Twinkle Tray 多显示器屏幕亮度调节工具

Twinkle Tray是一款支持多显示器的屏幕亮度调节工具,让你可以在一块屏幕上调节所有的显示器亮...

Day13:终於要进去新手村了-Javascript-判断式基本结构-if…else

今天要来提到的是判断式的部分,在JS中判断式有两种语法:if...else和 switch。 这篇我...

Day 29 - Vanilla JS Countdown Timer

前言 JS 30 是由加拿大的全端工程师 Wes Bos 免费提供的 JavaScript 简单应用...

追求JS小姊姊系列 Day7 -- 郑列展现的工具力(中)

前情提要 郑列展现了自己的工具力(快速找杂物),但似乎还有别的? 郑列:我看你是完全不懂啊,我还有别...