DLL劫持并使用MinHook

首发于Enaium的个人博客


测试用例

首先我使用CLion写了一个简单的程序,这个程序会加载一个dinput8.dll,然后调用一个函数显示一段文字,然后等待用户按下任意键。这个程序的代码如下:

#include<windows.h>
#include<iostream>


int display(const char *text) {
    std::cout << text << std::endl;
    std::cout << "Press any key to continue..." << std::endl;
    std::cin.ignore();
    return 0;
}

int main() {
    LoadLibrary("dinput8.dll");
    printf("Address: %p\n", display);
    display("Hello World!");
}

我们所做的就是 Hook 这个display函数,然后在这个函数被调用时,将这个文字改为另一个文字。之后将项目进行编译,别忘了在CMakeLists.txt中添加set(CMAKE_EXE_LINKER_FLAGS "-static"),这样我们就可以得到一个静态链接的可执行文件。之后我们就可以开始 Hook 这个函数了。

DLL 劫持

首先我们需要了解一下 DLL 的加载机制,Windows 系统在加载 DLL 时,会按照一定的顺序搜索 DLL 文件,如果找到了就加载,如果没有找到就会报错。这个顺序是怎么样的呢?我们可以通过查看官方文档来了解。简单来说就是首先搜索应用程序目录,然后搜索系统目录,最后搜索环境变量中指定的路径。所以我们可以使用这个机制来劫持 DLL。

包装 DLL

上面我们知道了 DLL 的加载机制,那么我们就可以知道如何让程序加载我们自己的 DLL,加载我们的 DLL 后,我们需要让这个 DLL 也拥有原 DLL 的功能,这里我们需要对原 DLL 进行包装。我们可以使用wrap_dll这个工具来包装 DLL。这是个Python脚本,所以你必须安装了Python,之后这个脚本还需要dumpbin.exeundname.exe这两个工具,这两个工具是 Visual Studio 自带的,所以你需要安装了Visual Studio,并且需要将C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.40.33807\bin\Hostx64\x64设置到环境变量中。之后你就可以使用这个脚本了。

python .\wrap_dll.py "C:\Windows\System32\dinput8.dll"

运行完成这个条命令后,你会得到一个dinput8项目,这个就是我们包装的 DLL。我们可以先把项目中的empty.hhook_macro.h给删掉,这两个文件对我们没有用。

MinHook

MinHook 是一个轻量级的钩子库,可以用来 Hook 函数。我们可以在minhook下载到这个库。下载完成后我们在 dinput8 这个项目中新建一个文件夹MinHook,然后将 MinHook 的include文件夹和src文件夹复制到这个文件夹中。之后我们在CMakeLists.txt中添加这个库。

ADD_LIBRARY(
    dinput8
    SHARED
    dinput8_asm.asm
    dinput8.cpp
    dinput8.def
    MinHook/include/MinHook.h
    MinHook/src/hde/hde32.c
    MinHook/src/hde/hde32.h
    MinHook/src/hde/hde64.c
    MinHook/src/hde/hde64.h
    MinHook/src/hde/pstdint.h
    MinHook/src/hde/table32.h
    MinHook/src/hde/table64.h
    MinHook/src/buffer.c
    MinHook/src/buffer.h
    MinHook/src/hook.c
    MinHook/src/trampoline.c
    MinHook/src/trampoline.h
)

之后我们就可以开始 Hook 这个函数了。

Hook 函数

首先我们需要再项目中执行cmake .,之后会生成一个Visual Studio的项目,我们使用Visual Studio打开这个项目,这里需要注意一下,需要通过打开解决方案的方式打开这个项目,不要直接打开这个项目文件夹。之后我们打开dinput8.cpp,删掉_hook_setup这个函数,接着我们需要将加载库的地址改为系统的LoadLibrary函数,这样我们就可以加载原 DLL 了。之后我们就可以开始 Hook 这个函数了。

#include <windows.h>
#include <stdio.h>

HINSTANCE mHinst = 0, mHinstDLL = 0;

extern "C" UINT_PTR mProcs[6] = {0};

LPCSTR mImportNames[] = {
  "DirectInput8Create",
  "DllCanUnloadNow",
  "DllGetClassObject",
  "DllRegisterServer",
  "DllUnregisterServer",
  "GetdfDIJoystick",
};

#ifndef _DEBUG
inline void log_info(const char* info) {
}
#else
FILE* debug;
inline void log_info(const char* info) {
  fprintf(debug, "%s\n", info);
  fflush(debug);
}
#endif

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
  mHinst = hinstDLL;
  if (fdwReason == DLL_PROCESS_ATTACH) {
    mHinstDLL = LoadLibrary("C:/Windows/System32/dinput8.dll");
    if (!mHinstDLL) {
      return FALSE;
    }
    for (int i = 0; i < 6; ++i) {
      mProcs[i] = (UINT_PTR)GetProcAddress(mHinstDLL, mImportNames[i]);
    }

#ifdef _DEBUG
    debug = fopen("./debug.log", "a");
#endif
  } else if (fdwReason == DLL_PROCESS_DETACH) {
#ifdef _DEBUG
    fclose(debug);
#endif
    FreeLibrary(mHinstDLL);
  }
  return TRUE;
}

extern "C" void DirectInput8Create_wrapper();
extern "C" void DllCanUnloadNow_wrapper();
extern "C" void DllGetClassObject_wrapper();
extern "C" void DllRegisterServer_wrapper();
extern "C" void DllUnregisterServer_wrapper();
extern "C" void GetdfDIJoystick_wrapper();

接着我们可以在当fdwReason等于DLL_PROCESS_ATTACH时,也就是当 DLL 被加载时,我们进行 Hook。

首先我们需要编写一个原函数的函数指针,这个函数指针的类型需要和原函数的类型一样,这里我们使用typedef来定义这个函数指针。

typedef int (*display_t)(const char*);

之后我们需要定义一个函数指针,这个函数指针用来指向我们 Hook 后的函数。

display_t display = nullptr;

之后我们创建一个Hook函数,这个函数的参数和返回值都需要和原函数一样。

int display_hook(const char *text) {
    return display("Hello MinHook!");
}

最后我们使用MinHookHook这个函数。

首先我们需要初始化MinHook,之后使用MH_CreateHook来创建一个 Hook,传入原函数的地址,Hook 函数的地址,和一个函数指针的指针,之后我们就可以使用MH_EnableHook来启用这个 Hook 了。

MH_Initialize();
MH_CreateHook((LPVOID)0x00007ff62fb51634, &display_hook, reinterpret_cast<LPVOID*>(&display));
MH_EnableHook(nullptr);

这里的0x00007ff62fb51634是我们运行这个测试程序后得到的display函数的地址,你可以通过打印这个函数的地址来得到这个地址。除了这个方法我们还可以使用x64dbg这个工具来得到这个地址。

我们打开x64dbg通过符号切换到这个程序,之后使用字符串搜索Hello World!,之后我们找到call这个指令,然后我们就可以得到这个函数的地址了。

20241103121643
在这里插入图片描述

20241103122021

之后编译我们的项目,然后将生成的 DLL 放到我们的测试程序的目录下,之后我们就可以运行这个程序了。

20241103122428

项目地址: dll-hijack-minhook

<think>好的,用户现在的问题是,他们有一个Python脚本需要调用同目录下的kernel32.dll,而系统system32目录下也有同名的DLL。他们想知道如何让Python优先使用同文件夹下的kernel32.dll而不是系统自带的那个。 首先,我需要回忆Windows的DLL搜索顺序。根据之前的讨论,Windows默认会先搜索应用程序目录,也就是Python脚本所在的目录,然后是系统目录如System32。不过用户的情况似乎相反,系统目录的DLL被加载了,而不是本地的。这可能涉及到KnownDLLs机制,因为kernel32.dll通常被列在KnownDLLs中,导致系统强制从System32加载。 接下来,我需要确认KnownDLLs的影响。如果kernel32.dll在KnownDLLs列表中,那么无论应用程序目录是否存在该DLL,系统都会直接使用System32中的版本。这时候即使用户将kernel32.dll放在同目录下,系统也不会加载它,因为KnownDLLs有更高的优先级,甚至可能绕过常规的搜索顺序。 用户可能已经尝试过将DLL放在同目录下,但没有生效,所以才来询问。需要验证是否真的可以覆盖KnownDLLs中的DLL。根据微软文档,KnownDLLs的设计是为了防止DLL劫持攻击,因此系统会强制加载系统目录中的版本,无法通过常规方法覆盖。这意味着即使用户把kernel32.dll放在同目录,也无法替换系统的版本,因为这是被系统安全机制限制的。 接下来,用户的实际需求可能是什么?他们可能误以为需要替换kernel32.dll来实现某些功能,但实际上kernel32.dll是Windows的核心系统文件,替换它可能会导致系统不稳定或者安全风险。正确的做法应该是检查是否真的需要替换该DLL,或者是否有其他方法实现他们的需求,比如使用不同的函数或库。 如果用户确实需要加载自定义的DLL,可能需要考虑以下替代方案:比如将DLL重命名为不同的名称,然后在代码中显式加载。例如,将kernel32.dll改名为mykernel32.dll,然后使用ctypes的CDLL或WinDLL函数加载重命名后的DLL。这样可以绕过系统对KnownDLLs的限制,因为文件名不同,系统不会将其视为KnownDLL。 另外,用户可能需要了解修改系统DLL的风险,尤其是像kernel32.dll这样的关键文件。替换系统DLL可能导致应用程序崩溃或系统不稳定,因此除非有特殊需求,否则不建议这样做。 此外,用户的问题可能源于对DLL加载机制的误解。他们可能认为只要将DLL放在同目录下就能覆盖系统目录的版本,但实际上由于KnownDLLs的存在,某些核心DLL无法被覆盖。因此,需要教育用户关于KnownDLLs的作用和限制,以及为什么他们的尝试没有成功。 总结来说,解决这个问题的正确方法不是替换系统的kernel32.dll,而是通过其他方式加载所需的函数或库,比如使用重命名的DLL或者寻找其他API替代方案。同时,需要提醒用户注意系统文件的安全性和稳定性,避免不必要的修改。</think>--- 要让Python优先使用当前目录下的`kernel32.dll`而非系统目录(如`System32`)中的版本,需理解Windows的DLL加载机制采取特定方法。以下是详细分析和解决方案: --- ### **1. 问题背景** - **系统限制**:`kernel32.dll`是Windows核心系统文件,默认会被系统强制从`System32`目录加载(通过`KnownDLLs`机制),**无法通过常规路径覆盖**。 - **常规DLL加载顺序**: Windows默认优先加载应用程序目录的DLL,但以下情况例外: 1. 若DLL在`KnownDLLs`列表中(注册表定义),则直接使用系统目录版本。 2. 若DLL已被其他进程加载到内存,则复用现有实例。 --- ### **2. 为什么直接替换`kernel32.dll`不可行?** - **KnownDLLs机制**: `kernel32.dll`被列入注册表`HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs`,系统会绕过常规搜索顺序,直接加载`System32`中的版本。 - **安全风险**: 强制替换系统核心DLL可能导致系统崩溃或安全漏洞,因此Windows禁止此类操作。 --- ### **3. 替代解决方案** #### **(1) 重命名DLL显式加载** 若需加载自定义实现的函数,可重命名DLL(如`my_kernel32.dll`),通过`ctypes`显式指定路径加载: ```python import ctypes import os # 获取当前脚本所在目录 current_dir = os.path.dirname(os.path.abspath(__file__)) dll_path = os.path.join(current_dir, "my_kernel32.dll") # 显式加载自定义DLL try: my_kernel32 = ctypes.WinDLL(dll_path) except Exception as e: print(f"加载失败: {e}") ``` #### **(2) 修改DLL搜索路径(不适用于KnownDLLs)** 对于非`KnownDLLs`列表中的DLL,可通过以下方式临时添加搜索路径: ```python import os import sys import ctypes # 将当前目录添加到DLL搜索路径 os.environ["PATH"] = os.path.dirname(__file__) + os.pathsep + os.environ["PATH"] # 加载DLL(仅对非KnownDLLs有效) dll = ctypes.CDLL("kernel32.dll") # 仍可能加载系统版本! ``` **注意**:此方法对`kernel32.dll`无效,因其受`KnownDLLs`保护。 --- ### **4. 特殊情况:调试或逆向工程** 若需劫持`kernel32.dll`函数(如调试或注入),需通过底层机制实现: - **DLL注入**:通过注入器强制目标进程加载自定义DLL。 - **Hook API**:使用`Detours`或`MinHook`等库拦截函数调用。 - **内核模式驱动**:修改内核行为(高风险,需驱动程序签名)。 --- ### **5. 重要提醒** - **合法性**:修改系统DLL可能违反Windows许可协议。 - **兼容性**:自定义`kernel32.dll`可能导致程序与系统不兼容。 - **安全风险**:恶意篡改系统DLL可能触发安全软件拦截。 --- ### **6. 推荐做法** - **避免替换系统DLL**:重构代码,依赖标准API或合法第三方库。 - **使用合法中间层**:若需扩展功能,通过合法COM组件或代理DLL实现。 - **咨询微软文档**:参考[Windows DLL搜索规则](https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order)。 --- 若仍有特殊需求,请进一步说明具体场景(如自定义函数实现、调试目的等),以便提供更精准的方案。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Enaium

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值