detours内在原理分析

这里我以MessageBox为经典的例子,实践下如何使用Window API

MessageBox函数说明:

int MessageBox(
  HWND    hWnd,
  LPCTSTR lpText,
  LPCTSTR lpCaption,
  UINT    uType
);

这里我们用visual stdio新建一个c++的项目,来学习下如何使用该API:

HookTest.cpp

#include <windows.h>
#include <iostream>
using namespace std;
int main()
{
    int rtCode = MessageBox(NULL, (LPCWSTR)L"内存地址越界,程序已经被终止!", (LPCWSTR)L"程序出现了错误", MB_ICONASTERISK| MB_OKCANCEL);
    cout << rtCode << endl;
    switch (rtCode) {
        case 1:
            cout << "选择了确定按钮!" << endl;
            break;
        case 2:
            cout << "选择了取消按钮!" << endl;
            break;
        default:
            cout << "其他操作!" << endl;
    }

    return 0;
}

 

 

一般推荐直接使用windows.h头文件,避免出现一些其他的问题,windows.h包括了其他的window头文件

0x4 Windows API Hooking

了解了Win API的调用过程之后,我们可以来学习hook(挂钩)的技术。

API Hooking是一种我们可以检测和修改API调用的行为和流程的技术。

流程图大致如下:

 

 

那么这种技术的实现原理是什么呢?

hook的技术可能有非常多种,笔者这里先以x86环境下的inline hook技术作为讲解,帮助萌新入门。

这里针对理解这个,笔者比较喜欢先run成功再debug分析原理。

0x4.1 实现hook MessageBoxA

选择x86的方式编译,x64的话会失败。

#include <iostream>
#include <Windows.h>

FARPROC messageBoxAddress = NULL;
SIZE_T bytesWritten = 0;
char messageBoxOriginalBytes[6] = {};

int __stdcall HookedMessageBox(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {

    // print intercepted values from the MessageBoxA function
    std::cout << "Ohai from the hooked function\n";
    std::cout << "Text: " << (LPCSTR)lpText << "\nCaption: " << (LPCSTR)lpCaption << std::endl;

    // unpatch MessageBoxA
    WriteProcessMemory(GetCurrentProcess(), (LPVOID)messageBoxAddress, messageBoxOriginalBytes, sizeof(messageBoxOriginalBytes), &bytesWritten);

    // call the original MessageBoxA
    return MessageBoxA(NULL, lpText, lpCaption, uType);
}

int main()
{
    // show messagebox before hooking
    MessageBoxA(NULL, "hi", "hi", MB_OK);

    HINSTANCE library = LoadLibraryA("user32.dll");
    SIZE_T bytesRead = 0;

    // get address of the MessageBox function in memory
    messageBoxAddress = GetProcAddress(library, "MessageBoxA");

    // save the first 6 bytes of the original MessageBoxA function - will need for unhooking
    ReadProcessMemory(GetCurrentProcess(), messageBoxAddress, messageBoxOriginalBytes, 6, &bytesRead);

    // create a patch "push <address of new MessageBoxA); ret"
    void *hookedMessageBoxAddress = &HookedMessageBox;
    char patch[6] = { 0 };
    memcpy_s(patch, 1, "\x68", 1);
    memcpy_s(patch + 1, 4, &hookedMessageBoxAddress, 4);
    memcpy_s(patch + 5, 1, "\xC3", 1);

    // patch the MessageBoxA
    WriteProcessMemory(GetCurrentProcess(), (LPVOID)messageBoxAddress, patch, sizeof(patch), &bytesWritten);

    // show messagebox after hooking
    MessageBoxA(NULL, "hi", "hi", MB_OK);

    return 0;
}

这里代码中第一次没hook,正常调用原始的,然后经过hook自身线程之后,在调用就会被hook,从而进入我们自定义的执行逻辑:

 

 

0x4.2 分析Hook的原理

程序首先使用LoadLibrary加载模块(user32.dll),然后返回句柄。

HINSTANCE library = LoadLibraryA("user32.dll");

接着使用GetProcAddress获取模块dll指定导出函数的地址

messageBoxAddress = GetProcAddress(library, "MessageBoxA");

接着调用ReadProcessMemory读取当前进程的内存空间中MessageBoxA函数的开头前6个字节存在于messageBoxOriginalBytes字节数组。

ReadProcessMemory(GetCurrentProcess(), messageBoxAddress, messageBoxOriginalBytes, 6, &bytesRead);

接着在这里就是实现patch内存空间,修改执行流程的操作了,这里直接打一个断点debug

这里先用patch字节数组存储了一些指令,具体是什么debug看就行了,其实也很简单就是jmp hookedMessageBoxAddress

// create a patch "push <address of new MessageBoxA); ret"
    void *hookedMessageBoxAddress = &HookedMessageBox;
    char patch[6] = { 0 };
    memcpy_s(patch, 1, "\x68", 1);
    memcpy_s(patch + 1, 4, &hookedMessageBoxAddress, 4);
    memcpy_s(patch + 5, 1, "\xC3", 1);

写好patch数组,之后开始修改进程的内存空间,修改指令。

WriteProcessMemory(GetCurrentProcess(), (LPVOID)messageBoxAddress, patch, sizeof(patch), &bytesWritten);

主要是修改(patch)了messageBoxA这个导出函数在内存位置的前6个字节为我们定义的指令,至于是啥没关系,我们存储下来,后面再unpatch回来即可了。

patch之后呢?

通过将hookedMessageBoxAddress的地址压入了栈顶,然后ret,其实本质就是pop eip, jmp eip

\x68就是push,\xc3就是ret,然后32位的程序,地址刚好4字节,patch数组的构造原理就是这样。

然后我们重新调用,hook的MessageBoxA(NULL, "hi", "hi", MB_OK);,就会进入HookedMessageBox

int __stdcall HookedMessageBox(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {

    // print intercepted values from the MessageBoxA function
    std::cout << "Ohai from the hooked function\n";
    std::cout << "Text: " << (LPCSTR)lpText << "\nCaption: " << (LPCSTR)lpCaption << std::endl;

    // unpatch MessageBoxA
    WriteProcessMemory(GetCurrentProcess(), (LPVOID)messageBoxAddress, messageBoxOriginalBytes, sizeof(messageBoxOriginalBytes), &bytesWritten);

    // call the original MessageBoxA
    return MessageBoxA(NULL, lpText, lpCaption, uType);
}

这个就很简单了,执行hook想要执行的操作,然后unhook,然后正常调用就行了。

这种劫持方法,可以说真的蛮简洁的,也非常易懂,比较暴力,没有过多的计算。

0x4.2 探讨32位和64位的区别

pass

 

posted @ 2023-01-02 19:32  bonelee  阅读(1172)  评论(0编辑  收藏  举报