Windows API Hooking with MS Detours

API Hook技术

    API HOOK技术是一种用于改变API执行结果的技术。
    API HOOK技术能够在控制流将要进入某些API函数时,将控制流转到自定义的函数上。在这个自定义的函数中,API HOOK技术使用者能够获取传入该API函数的参数并根据这些参数执行任意操作。在这个自定义的函数结束时,API HOOK技术使用者可以选择使得控制流转移到原API函数也可以根据API返回值类型直接返回对应的值,从而结束该API的调用

Microsoft Research Detours Package介绍

Detours is a software package for monitoring and instrumenting API calls on Windows. Detours has been used by many ISVs and is also used by product teams at Microsoft. Detours is now available under a standard open source license (MIT). This simplifies licensing for programmers using Detours and allows the community to support Detours using open source tools and processes.

Detours is compatible with the Windows NT family of operating systems: Windows NT, Windows XP, Windows Server 2003, Windows 7, Windows 8, and Windows 10. It cannot be used by Windows Store apps because Detours requires APIs not available to those applications.

Detours 4.0.1 supports x86, x64 and other Windows-compatible processors (IA64 and ARM). It includes support for either 32-bit or 64-bit processes.

Detours开源工具库API Hook工作原理

Detours开源工具库定义的函数概念

    Detours开源工具库定义了三个函数概念:

  • Target函数(Target Fun):要拦截的Windows API函数。Detours工具库会修改Target函数在内存中的机器指令(一般为将前5个字节修改为跳转到Detour 函数的机器指令)。
  • Detour 函数(Detour Fun):API HOOK技术使用者“自定义的函数”。在控制流将要进入API函数时,API HOOK技术将控制流转移到Detour函数上
  • Trampoline函数(蹦床函数,Trampoline Fun):Trampoline函数的开头保存了Target函数的部分指令(一般为前5个字节)。Detour函数结束后,API HOOK技术使用者可以选择使控制流转移到Trampoline函数,Trampoline函数在执行完函数开头保存的Target函数的部分指令后,将控制流转向Target函数修改后的位置(一般为Target函数5个字节之后)。这样便保留了原来API函数的功能。因此得名Trampoline函数(蹦床函数)。

Detours开源工具库的工作步骤

  • 修改Target函数:使用一个无条件转移指令替换Target函数首部的几条指令(一般为5个字节),将控制流直接转移到一个用户自己实现的截获函数(Detour函数)中。Target函数中被替换的指令被保存在Trampoline函数中。

Detours在Target函数的开头加入jmp Address_of_Detour_Function指令(共5个字节),把对Target函数的调用引导到Detour函数。同时,把Target函数的开头的5个字节(push ebp……push esi)以及jmp Address_of_Target_ Function+5(共10个字节)作为Trampoline函数的内Target函数原始的未被Hook的API函数)。

原来的被调用Target函数的函数体(二进制机器指令)需要至少有5个字节以上,才能被改写为Target函数。这是因为改写后的Target函数,第一条是一个跳转到Detour函数的语句,需要覆盖5个字节,之后才是原始的被调用函数剩余的未被覆盖的内容。

同时,微软的说明文档中,Trampoline函数的函数体为:从原来被调用函数拷贝被覆盖的这前5个字节,再加一个无条件跳转指令,就完成了Trampoline函数。因此,前5个字节必须是完整指令(当第5个字节和第6个字节不能是一条不可分割的指令时,会导致Trampoline函数执行错误,复制前5个字节后,一条完整的指令被硬性分割开来,造成程序崩溃)。如果第5字节和第6个字节是不可分割指令,需要调整拷贝到Trampoline函数的字节个数(具体拷贝多少,可以查看目标函数的汇编代码得到)。

修改后的Target函数是目标函数的修改版本(一般来说,Target函数前五个字节的机器指令被改了),不能在Detour函数中直接调用,需要通过对Trampoline函数的调用来达到间接调用

  • 生成Trampoline函数:Trampoline函数的指令包括Target函数中被替换的代码以及一个重新跳转到目标函数正确位置(一般为Target函数5个字节之后)的无条件分支。这样便保留了原来API函数的功能。

Trampoline函数默认分配了32个字节。一般来说,Trampoline函数的内容,就是拷贝的Target函数的前5个字节,加上一个JMP Address_of_ Target _ Function+5指令,共10个字节。此函数仅供Detour函数调用,执行完前5个字节的指令后,再绝对跳转到修改后的目标函数(Target 函数)的第6个字节,继续执行原目标函数功能。

  • 自定义Detour函数:Detour函数可以替换Target函数,或者通过执行Trampoline函数时,将Target函数作为子程序来调用的办法,在保留原来目标函数功能基础上,来完成扩展功能

Detour函数是API Hook技术使用者自定义的需要的截获API函数。Detour函数的调用方式、参数个数和参数类型必须与目标函数一致(这样才不会破坏堆栈中的数据)。此函数在程序调用目标API函数的第一条指令的时候就会被调用。如果在Detour函数中想继续调用原来的目标函数,必须调用Trampoline函数(Trampoline函数在执行完目标函数的前5个字节的指令后会无条件跳转到Target函数的5个字节后继续执行)。不能再直接调用目标函数,否则将进入死循环(Target函数跳转到Detour函数,Detour函数又跳转到Target函数)。通过对Trampoline函数的调用后,可以获取目标函数的执行结果。

Detour

Detours开源工具库API Hook示例

    Detour提供了向运行中的应用程序注入Detour函数在二进制文件基础上注入Detour函数两种方式。
    出于使用基本的函数截获功能的目的, Detours同样提供了编辑任何DLL导入表的功能,达到向已存在的二进制代码中添加任意数据节表的目的,向一个新进程或者一个已经运行着的进程中注入一个DLL。一旦向一个进程注入了DLL,这个动态库就可以截获任何Win32函数,不论它是在应用程序中或者在系统库中。基于这个功能,可以通过“在二进制EXE文件中添加一个名称为 Detour的节表”的方式来实现在二进制文件基础上注入Detour函数。

Detours开源工具库注入的DLL示例代码

  • MessageBoxA、MessageBoxW原API函数入口地址(指针),实际上是Target函数
  • OldMessageBox:一个函数指针,在程序中被初始化为MessageBox API函数入口地址;但是,在随后的DetourAttach函数中,OldMessageBox会被修改为Trampoline函数的地址
  • NewMessageBoxA函数、NewMessageBoxW函数:API HOOK技术使用者自定义的Detour函数
//Dllmain.cpp:定义DLL应间程序的人口点。
#include "pch.h"
#include "detours.h"

#pragma comment(lib, "detours.lib")

#include <stdio.h>
#include <stdarg.h>
#include <iostream>

//函数指针
//定义和引入需要Hook的函数和替换的函数
//作用:detour更改了MessageBox函数入口地址对应的指令,由于前几个字节的拷贝和覆盖,再从MessageBox函数原来的入口地址调用会导致无限次的嵌套(可能)
//OldMessageBox函数记录了Trampoline函数的地址,这个地址才是真正的MessageBox函数的地址
static int(WINAPI* OldMessageBoxW)(_In_opt_ HWND hWnd, //指定该对话框的所有者窗口。如果该参数为0,则该对话框不属于任何窗口
    _In_opt_ LPCWSTR lpText,
    _In_opt_ LPCWSTR lpCaption, //在对话框标题栏中显示的字符串表达式。如果该参数为空,则使用默认的“错误”作为对话框的标题。
    _In_ UINT uType     //指定显示按钮的数目及形式,使用的图标样式
    ) = MessageBoxW;
static int(WINAPI* OldMessageBoxA)(_In_opt_ HWND hWnd, _In_opt_ LPCSTR lpText, _In_opt_ LPCSTR lpCaption, _In_ UINT uType) = MessageBoxA;

extern "C" __declspec(dllexport) int WINAPI NewMessageBoxA(_In_opt_ HWND hWnd, _In_opt_ LPCSTR lpText, _In_opt_ LPCSTR lpCaption, _In_ UINT uType)
{
    return OldMessageBoxA(hWnd, "MessageBoxA Hooked!", "Detours", uType);
}

extern "C" __declspec(dllexport) int WINAPI NewMessageBoxW(_In_opt_ HWND hWnd, _In_opt_ LPCWSTR lpText, _In_opt_ LPCWSTR lpCaption, _In_ UINT uType)
{
    return OldMessageBoxW(hWnd, L"MessageBoxW Hooked!", L"Detours", uType);
}


BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:

        //在线程被创建的时候,也会执行一次已经被加载的DLL的DLLMain,加载原因是DLL_THREAD_ATTACH,离开时也同理
        //DisableThreadLibraryCalls会避免线程启动时调用DLL_THREAD_ATTACH
        DisableThreadLibraryCalls(hModule);

        DetourTransactionBegin();   //开始截获
        DetourUpdateThread(GetCurrentThread()); //列入一个在DetourTransaction过程中要进行update的线程

        //添加要截获的目标函数

        //MessageBox API
        DetourAttach(&(PVOID&)OldMessageBoxW, NewMessageBoxW);
        DetourAttach(&(PVOID&)OldMessageBoxA, NewMessageBoxA);

        //真正执行当前的Transaction过程,进行截获操作
        DetourTransactionCommit();
        break;

    case DLL_THREAD_ATTACH:
        break;

    case DLL_THREAD_DETACH:
        break;

    case DLL_PROCESS_DETACH:
        DetourTransactionBegin();   //开始解除截获
        DetourUpdateThread(GetCurrentThread()); //列入一个在DetourTransaction过程中要进行update的线程

        //添加要解除截获的目标函数

        //MessageBox API
        DetourDetach(&(PVOID&)OldMessageBoxW, NewMessageBoxW);
        DetourDetach(&(PVOID&)OldMessageBoxA, NewMessageBoxA);


        //真正执行当前的Transaction过程,进行解除截获操作
        DetourTransactionCommit();
        break;
    }

    return TRUE;
}

Detours开源工具库注射DLL的程序(注射器)示例代码

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

#pragma comment(lib, "detours.lib")

int main()
{
    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    /*
    * #define RtlZeroMemory(Destination,Length) memset((Destination),0,(Length))
    * #define ZeroMemory RtlZeroMemory
    */

    ZeroMemory(&si, sizeof(STARTUPINFO));
    ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));

    si.cb = sizeof(STARTUPINFO);

    WCHAR DirPath[MAX_PATH + 1];
    wcscpy_s(DirPath, MAX_PATH, L"F:\\");       //Dll的文件夹

    char DLLPath[MAX_PATH + 1] = "F:\\SoftwareSafetyDesign.dll";    //Dll的地址

    WCHAR EXEPath[MAX_PATH + 1] = { 0 };
    wcscpy_s(EXEPath, MAX_PATH, L"F:\\test.exe");   //需要注入程序的地址

    if (DetourCreateProcessWithDllEx(EXEPath, NULL, NULL, NULL, TRUE, CREATE_DEFAULT_ERROR_MODE | CREATE_SUSPENDED, NULL, DirPath, &si, &pi, DLLPath, NULL))
    {
        ResumeThread(pi.hThread);   //启动线程
        WaitForSingleObject(pi.hProcess, INFINITE); //同步
    }
    else
    {
        char error[100] = { 0 };
        sprintf_s(error, "%d", GetLastError()); //输出错误信息
    }

    return 0;
}

示例测试结果

    建立注射器项目InjectorDLL项目SoftwareSafetyDesign,生成Injector.exe和SoftwareSafetyDesign.dll:
project
    准备WPF应用程序test.exe作为测试程序

///MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApp1
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("Hello~~~~");
        }
    }
}

before

    运行Injector.exe进行API Hook,观察到MessageBox函数执行结果发生变化
hook

文章推荐

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值