科技一站

 找回密码
 立即注册
查看: 118|回复: 1

逆向工程-Hook技术

[复制链接]

1

主题

17

帖子

32

积分

新手上路

Rank: 1

积分
32
发表于 2023-3-10 21:45:36 | 显示全部楼层 |阅读模式
3.1 Hook 的基本概念

3.1.1 概念引入


  • Hook 的原意是“钩子”,非常形象地描述了它的作用。
  • 关键是通过一定手段埋下“钩子”,“钩”住我们关心的重要程序流程,然后根据需要对流程进行干预
  • Hook 技术极为强大,几乎所有的安全软件都在使用这种技术。
  • Hook 技术的应用平台不限于 Windows,在 Linux 以及其他移动平台、非本地代码等地方处处可见。
技术是每个安全研究者必须掌握的技能。
3.2 Hook 的类别

3.2.1 Address Hook

改的是函数地址或者偏移量(通常存放在各类的表中)
通过修改数据进行 Hook 的方法,这里的数据通常指的是一些函数地址(或者偏移量),通常存放在各类表结构中,例如:

  • IAT Hook(导入表,影响当前对该函数的调用)
  • EAT Hook(输出表,影响后续对该函数地址的获取)
结合上述两者,可以实现对某进程内部所有已加载和未加载的模块通过静态或者动态的方式调用 API 的行为进行拦截。
此外还有:C++ 的虚表、SSDT、ShadowSSDT 等,他们都属于这一类 Hook
C++虚表 虚函数 有个相应的表存这些地址,后面两个属于内核层的
本节课程仅仅介绍 Windows 用户态下的 Hook 技术,内核态下的相关 Hook 技术与用户态下的大同小异,请有兴趣的同学自行研究。
3.2.2 Inline Hook(重点!!!!!!!!!!!!!!)

改的是指令!!!!
Inline Hook 即直接修改指令的 Hook 方式,与 Address Hook 相比更容易理解,它的关键是转移程序的执行流程,特色是这种 Hook 方式将会直接对程序的代码进行修改,而 Address Hook 一般来说没有修改代码,而只是修改数据内容。
Call Hook 某种意义上也算是一种 Inline Hook,通过修改 Call 指令操作数实现。
基于异常处理的 Hook
将一个异常处理函数视为钩子,不常见,本节课程不作讨论。
3.2.3 Call Hook





3.2.4 基于异常处理的hook

3.3 Hook 实战

使用 Inline Hook 实现屏蔽任务管理器关闭进程的功能




实现任务管理器在详细信息里关闭不了进程
采用dll注入的方式hook,工具桌面的


(目前不学这个)
用vs建立动态链接库工程,主函数dllmain.cpp



用x64debug调试任务管理器 在



这个位置下断点,这个位置就是关闭进程的(测试:此处清3个栈元素就把要清的进程名出栈了)
3.3.1 编写 Hook 函数

bool Hook_TerminateProcess()
{

        void* addr = Find_TerminateProcess();
        _OpenProcessAddr = addr;

        if (addr == 0)
        {
                MessageBoxA(NULL, "Func addr not found.", NULL, 0);
                return false;
        }

        // 代码段一般不可写的,改写内存属性
       
        VirtualProtect((void*)addr, 5, PAGE_EXECUTE_READWRITE, &__old_protect);

        // 跳转偏移计算方式: 跳转偏移 = 目标地址 - ( 指令地址 + 5 )
        // 0xE9 为 JMP

        __jmp[0] = 0xE9;
        *(DWORD*)& __jmp[1] = (DWORD)((long long)MyTerminateProcess - (long long)addr - 5);
        // 保存原先字节
        memcpy(__old_jmp, (void*)addr, 5);
        // 替换原先字节
        memcpy((void*)addr, __jmp, 5);
}主要分为如下几个步骤:

  • 获取原函数地址(采用 Find_TerminateProcess 实现)
  • 改写内存属性,方便后续进行 Inline Hook 指令修改(采用 VirtualProtect 实现)
  • 进行指令改写,在改写指令前必须保存之前的指令(便于日后恢复)
其中,改写指令为一个远眺转,其计算方式为:
跳转偏移 = 目标地址 - ( 指令地址 + 5 )
执行完该函数后,目标函数的起始地址将会被一个远 JMP 替换,执行目标函数的时候会跳转到我们自己的函数上,我们将自己的函数称为“Detour 函数”。
VirtualProtect API 的定义如下:
FORCEINLINE
_Success_(return != FALSE)
BOOL
WINAPI
VirtualProtect(
    _In_  LPVOID lpAddress,
    _In_  SIZE_T dwSize,
    _In_  DWORD flNewProtect,
    _Out_ PDWORD lpflOldProtect
    )
{
    return VirtualProtectFromApp (lpAddress, dwSize, flNewProtect, lpflOldProtect);
}
可以发现,其内部是通过调用 VirtualProtectFromApp 实现进一步的功能,该函数的功能即为更改调用进程地址空间中指定页区域上的保护。
之所以要对 TerminateProcess 进行 Hook,是通过对任务管理器的调试得到的,使用 x32dbg 加载 32 位任务管理器(位于 WOW64 目录下),在 TerminateProcess API 处下一个软件断点,在任务管理器的详细列表中选中某一个进程,在右键菜单中选择“结束任务”,可以发现调试器中断在了 TerminateProcess 处。
本节只讨论对 Windows10 任务管理器“详细信息”中右键菜单中“结束任务”的一个绕过,在不同的系统中,任务管理器结束进程的方式均有所不同,大家需要自己进行区分,多调试,总结出最佳的 Hook 函数(例如有的版本的任务管理器可以通过 Hook OpenProcess 来实现相同的功能,只需要将 OpenProcess 中设置的权限位中的关闭进程的权限清除即可)。
3.3.2 实现 Find_TerminateProcess() 函数

编写代码如下:
void* Find_TerminateProcess()
{
        //寻找到 TerminateProcess 的地址
        void* addr = 0;
        //加载 kernel32.dll
        HMODULE hModule = LoadLibraryA("kernel32.dll");
        //获取 TerminateProcess 的地址
        addr = (void*)GetProcAddress(hModule, "TerminateProcess");
        return addr;
}这是一个获取函数地址的最基本方法,通过两个 API 调用(LoadLibraryA、GetProcAddress)一般情况下可以获得到我们需要的 API 的地址,之前有提到过,如果使用 GetProcAddress 获取到的地址不正确,可以自己按照老版本 sdk 的 GetProcAddress 函数实现一份自己的 GetProcAddress 函数进行调用。
涉及到的程序代码: [examples.7z]




mov edi,edi    =>微软称hotpatch





------------------------------进阶‼️-----------------------------------------------

4.1 更精准的 Inline Hook

4.1.1 要求


  • 实现防止任务管理器对某进程自身的结束
  • 要求不影响任务管理器结束其它进程的功能
4.1.2 Dll 注入程序编写(MyDllInject.exe

我们编写针对任务管理器的 Dll 注入程序,实现分为下列步骤:

  • 提权
  • 查找任务管理器窗口,获得窗口句柄
  • 根据窗口句柄获得进程句柄
  • 进行 Dll 注入
用以提权的代码如下:
void UpPriv()
{
        // 提权
        HANDLE hToken;
        LUID luid;
        TOKEN_PRIVILEGES tp;
        OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
        LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid);
        tp.PrivilegeCount = 1;
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
        tp.Privileges[0].Luid = luid;
        AdjustTokenPrivileges(hToken, 0, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL);
}Dll 注入的方式如下:

  • 调用 VirtualAllocEx 函数在目标进程中申请一段内存,属性为 PAGE_READWRITE,用来存放我们要注入的 Dll 的绝对路径
  • 调用 WriteProcessMemory 将 Dll 的绝对路径写入到目标进程中使用 VirtualAllocEx 函数申请的空间中
  • 调用 GetProcAddress 获得 LoadLibraryA 函数的地址
  • 调用 CreateRemoteThread 在目标进程中创建一个新的 LoadLibraryA  线程,线程的参数设置为使用 VirtualAllocEx 申请的空间中存放的字符串的指针
Dll 注入的代码如下所示:
BOOL DoInjection(char* DllPath, HANDLE hProcess)
{
        DWORD BufSize = strlen(DllPath) + 1;
        LPVOID AllocAddr = VirtualAllocEx(hProcess, NULL, BufSize, MEM_COMMIT, PAGE_READWRITE);
        WriteProcessMemory(hProcess, AllocAddr, DllPath, BufSize, NULL);
        PTHREAD_START_ROUTINE pfnStartAddr = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryA");

        HANDLE hRemoteThread;
        hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, pfnStartAddr, AllocAddr, 0, NULL);
        if (hRemoteThread)
        {
                return true;
        }
        else
        {
                MessageBox(NULL, TEXT("ERROR."), TEXT("INFO"), MB_OK);
                return false;
        }
}后面简单地通过 CreateRemoteThread 是否成功来判定我们注入 Dll 是否成功,实际上这种判断是非常不可靠的。我们可以设计一些进程间通讯的手段,当目标 Dll 成功注入到目标进程中的时候,传递一个消息给我们的注入程序,这种方式更加稳重。
4.1.3 Inline Hook Dll 编写

实现该功能,我们的 Dll 与之前的 Dll 的区别主要在于 MyTerminateProcess 函数的编写上:
BOOL __stdcall MyTerminateProcess( // 自己的 detours 函数
        HANDLE hProcess,
        UINT uExitCode
)
{
        char Buffer[MAX_PATH];
        char* ToProtect = (char*)"F:\\xxxx\\MyDllInject.exe"; // 绝对路径
        GetModuleFileNameExA(hProcess, 0, Buffer, MAX_PATH);
        if (!strcmp((const char*)Buffer, ToProtect)) {
                MessageBoxA(0, "You can't kill me!", "INFO", MB_OK);
                return FALSE;
        }
        else {
                UnHook(); // 脱钩
                BOOL ret = TerminateProcess(hProcess, uExitCode); // 调用原函数
                Hook_TerminateProcess(); // 恢复钩子
                return ret;
        }
}在函数中主要逻辑分为两部分:

  • 判断当前试图关闭的进程的二进制程序绝对路径是否为需要保护的程序的绝对路径
  • 如果是,则简单的返回 FALSE,不进行 TerminateProcess 操作
  • 如果不是,则:

    • 首先进行 UnHook() 脱钩操作
    • 调用原函数
    • 恢复钩子,将原函数的返回值返回

必须首先进行脱钩操作!!!
如果不进行脱钩操作,则会不断地陷入死循环中,因为原函数已经被 Hook 到了我们自己的 detours 函数,会形成自己调用自己的现象,导致死循环出现。
UnHook 函数实现如下:
void UnHook()
{
        // 恢复事先保存好的字节
        memcpy((void*)_OpenProcessAddr, __old_jmp, 5);
        // 恢复原内存属性
        DWORD p;
        VirtualProtect((void*)_OpenProcessAddr, 5, __old_protect, &p);
}进行脱钩后必须要进行恢复操作!!!
如果不进行恢复操作,则钩子将会失效,下一次对函数的调用将不能被我们监控。
大家可以思考,有没有一种实现 Inline Hook 的方式是可以不进行脱钩操作的呢?
4.2 Detours 库的使用(主要用这个!!!)

4.2.1 Detours 介绍

已在 GitHub 上被开源,地址:https://github.com/microsoft/Detours。
是微软官方的 Hook 软件包,用于监视和检测 Windows 上的 API 调用(实际上不仅仅限于对 API 函数的调用监控,还可以实现任意函数调用的监控,得益于非常易于使用的调用方式)。
Detours 可以用于拦截 ARM,x86,x64 和 IA64 计算机上的二进制函数。 Detours 最常用于拦截应用程序中的 Win32 API 调用,例如添加调试用的工具、 拦截代码在运行时动作等等。 通过跳转到用户提供的回调函数的无条件来替换目标功能的前几条指令。 来自目标功能的指令将被放置在“蹦床”上, 蹦床的地址位于目标指针中。
Detours 在运行时进行 Hook,目标函数的代码仅仅在内存中被修改(而不是被修改了二进制文件),这使得我们能以很细的粒度进行二进制程序的分析。
4.2.2 Detours 的基本使用方法

一般的调用框架如下4个步骤

  • DetourTransactionBegin();
  • DetourUpdateThread(GetCurrentThread());
  • DetourAttach() / DetourDetach()   最重要的一步!!!
  • DetourTransactionCommit()
需要遵循以上调用规则,可以保证线程安全等问题,例如:
bool InitHook() {
        DetourTransactionBegin();
        DetourUpdateThread(GetCurrentThread());

        OrgTerminateProcess = (BOOL(__stdcall *)(HANDLE,int))Find_TerminateProcess();
        DetourAttach(&(PVOID&)OrgTerminateProcess, MyTerminateProcess);
    //第一个参数函数指针必须声明为当前存在程序中的一个变量,为了之后方便免脱钩操作,,修改成安全调用点,,后面就不需要脱钩了。。。。。。

        int error = DetourTransactionCommit();
        return !error ? true : false;
}

bool DetachHook() {
        DetourTransactionBegin();
        DetourUpdateThread(GetCurrentThread());

        DetourDetach(&(PVOID&)OrgTerminateProcess, MyTerminateProcess);

        int error = DetourTransactionCommit();
        return !error ? true : false;
}代码中的 OrgTerminateProcess 是一个函数指针,其声明如下:
static BOOL(__stdcall* OrgTerminateProcess)(HANDLE hProcess, int uExitCode) = nullptr;当我们调用完 DetourAttach 之后该指针会被 Detours 库修改为一个安全函数的地址,我们可以在自己的 detour 函数中直接调用该函数,来实现对原函数的调用:
BOOL __stdcall MyTerminateProcess(
        HANDLE hProcess,
        UINT uExitCode
)
{
        char Buffer[MAX_PATH];
        char* ToProtect = (char*)"F:\\xxx\\MyDllInject.exe"; // 绝对路径
        GetModuleFileNameExA(hProcess, 0, Buffer, MAX_PATH);
        if (!strcmp((const char*)Buffer, ToProtect)) {
                MessageBoxA(0, "You can't kill me!", "INFO", MB_OK);
                return FALSE;
        }
        else {
                BOOL ret = OrgTerminateProcess(hProcess, uExitCode);
                return ret;
        }
}请大家结合实例,动手实践,与之前自己编写的 Inline Hook 的例子进行对比,思考为什么在这里不需要进行脱钩操作。
4.3 多线程同步问题引入

假设两个线程 A、B 同时调用了被 Hook 的函数,那么有一种可能的执行顺序如下:



很明显,B 在运行原函数的时候会发生致命错误。
解决方法:
hotpatch 可以有效防止多线程冲撞



mov edi,edi
detours  +2
或者 在其他地方保存一下被破坏的代码
解决多线程同步的方法???查查
涉及到的程序代码: [examples.7z]
回复

使用道具 举报

1

主题

13

帖子

26

积分

新手上路

Rank: 1

积分
26
发表于 2025-5-30 08:24:54 | 显示全部楼层
前排支持下了哦~
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|科技一站

GMT+8, 2025-8-21 18:46 , Processed in 0.104839 second(s), 20 queries .

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表