Windows:32位Inline Hook

什么是Inline Hook

Inline Hook,即内联钩子。
系统提供的API都保存在DLL文件中,当程序运行需要调用某个API时,它会隐式地将此API所在的DLL文件加载到其进程中。那么,如果我们使用一种方法,直接修改DLL中的API函数在内存中的映像,使API函数的首条代码为jmp MyFuncAddress这样就可以执行自己的函数了。
由于这种方法是直接在程序中嵌入jmp指令来实现Hook的,所以把它称为内联钩子。

使用DLL实现对其他进程的Inline Hook

思路:写一个DLL,DLL内写好钩子函数,然后进行DLL注入。其在首次被Load入进程时,在进程中查找特定函数的地址,然后将其入口处前五字节替换为一条跳转指令,跳转到钩子函数的地址,这个过程叫挂钩。钩子函数中,执行完自定义操作后,如果想继续执行原程序的操作,则可以在钩子函数中调用原来的函数,但是要注意的是:调用被钩住的原函数,又会再次调用钩子函数,这样会死循环。所以如果想在钩子函数中调用原函数,必须先解钩,再调用,调用完了再挂钩上去。

32位和64位下的内联钩子

实现32位和64位系统的Inline Hook
要注意:一定保证位数的一致,不然执行jmp xxxxxxxx时,由于偏移量不合法,很可能出现
在这里插入图片描述

源码

说明:全部文件均采用32位方式编译
在这里插入图片描述
文件结构如下:
钩子类与DLL源文件在同一项目下
在这里插入图片描述
被挂钩进程是独立的一个项目。
DLL注入器是一个独立的项目。

钩子类实现

inline_hook.h

//
// Created by 23028 on 2021/3/13.
//

#ifndef INLINE_HOOK_INLINE_HOOK_H
#define INLINE_HOOK_INLINE_HOOK_H

#include <windows.h>


class InlineHook{
private:
    // 被钩函数地址
    FARPROC hookedFuncAddress;
    // 原函数入口处5字节的指令
    BYTE origFuncInstruction[5];
    // 跳转至钩子函数入口的指令
    BYTE jmpToHookInstruction[5];

public:
    InlineHook();
    ~InlineHook();

    // 钩
    // 参数为:被钩函数所在DLL名、被钩函数名、钩子函数地址
    BOOL Hook(LPCTSTR pszModuleName, LPCTSTR pszHookedFuncName, FARPROC pfnHookFuncAddress);

    // 解钩
    VOID UnHook();

    // 重钩
    BOOL ReHook();
};


#endif //INLINE_HOOK_INLINE_HOOK_H

inline_hook.cpp
关于ReadProcessMemory的第二个参数用了一次(void*)强转的解释见:
invalid conversion from FARPROC to LPCVOID

//
// Created by 23028 on 2021/3/13.
//
#include "inline_hook.h"


InlineHook::InlineHook() {
    this->hookedFuncAddress = NULL;
    ZeroMemory(this->origFuncInstruction, 5);
    ZeroMemory(this->jmpToHookInstruction, 5);
}


InlineHook::~InlineHook() {
    // 解钩
    InlineHook::UnHook();

    this->hookedFuncAddress = NULL;
    ZeroMemory(this->origFuncInstruction, 5);
    ZeroMemory(this->jmpToHookInstruction, 5);
}


BOOL InlineHook::Hook(LPCTSTR pszModuleName, LPCTSTR pszHookedFuncName, FARPROC pfnHookFuncAddress) {
    // 钩子状态
    BOOL hookStatus = FALSE;
    // 拿到被钩函数的地址
    this->hookedFuncAddress = (FARPROC)GetProcAddress(GetModuleHandle(pszModuleName), pszHookedFuncName);
    if (this->hookedFuncAddress != NULL){
        // 保存被钩函数入口前五字节的指令到origFuncInstruction
        ReadProcessMemory(GetCurrentProcess(), (void*)this->hookedFuncAddress, this->origFuncInstruction, 5, NULL);

        // 构造跳转指令jmpToHookInstruction为jmp xxxxxxxx,后4B为偏移量
        // E9即:jmp操作
        this->jmpToHookInstruction[0] = '\xE9';
        // 计算被钩函数入口地址到要跳转地址的偏移量:目标地址 - 入口地址 - 本指令长度(jmp xxxxxxxx共5B)
        // 指针后移一字节,强转为DWORD型指针,其内容赋为DWORD型的偏移量
        *(DWORD*)(this->jmpToHookInstruction + 1) = (DWORD)pfnHookFuncAddress - (DWORD)this->hookedFuncAddress - 5;

        // 写入构造好的跳转指令
        if(WriteProcessMemory(GetCurrentProcess(), (void*)this->hookedFuncAddress, this->jmpToHookInstruction, 5, NULL)){
            hookStatus = TRUE;
        }

    }

    return hookStatus;
}


VOID InlineHook::UnHook() {
    if (this->hookedFuncAddress != NULL && this->origFuncInstruction[0] != NULL) {
        // 修改为原来的指令
        WriteProcessMemory(GetCurrentProcess(), (void*)this->hookedFuncAddress, this->origFuncInstruction, 5, NULL);
    }
}


BOOL InlineHook::ReHook() {
    BOOL reHookStatus = FALSE;
    if (this->hookedFuncAddress != 0) {
        // 修改为跳转指令
        WriteProcessMemory(GetCurrentProcess(), (void*)this->hookedFuncAddress, this->jmpToHookInstruction, 5, NULL);
        reHookStatus = TRUE;
    }

    return reHookStatus;
}

DLL编写

参考:Windows开发:CLion下编写及动/ 静态调用DLL
library.h

#ifndef INLINE_HOOK_LIBRARY_H
#define INLINE_HOOK_LIBRARY_H

#include "inline_hook.h"


// 钩子函数,传入原函数所有参数
BOOL WINAPI MyCreateProcessA(LPCTSTR lpApplicationName,
                             LPTSTR lpCommandLine,
                             LPSECURITY_ATTRIBUTES lpProcessAttributes,
                             LPSECURITY_ATTRIBUTES lpThreadAttributes,
                             BOOL bInheritHandles,
                             DWORD dwCreationFlags,
                             LPVOID lpEnvironment,
                             LPCTSTR lpCurrentDirectory,
                             LPSTARTUPINFO lpStartupInfo,
                             LPPROCESS_INFORMATION lpProcessInformation);


extern "C" BOOL APIENTRY DllMain(HANDLE hModule, DWORD reason_for_call, LPVOID lpReserved);

#endif //INLINE_HOOK_LIBRARY_H

library.cpp

#include "library.h"

// 声明钩子对象
InlineHook myHook;

BOOL WINAPI MyCreateProcessA(LPCTSTR lpApplicationName,
                             LPTSTR lpCommandLine,
                             LPSECURITY_ATTRIBUTES lpProcessAttributes,
                             LPSECURITY_ATTRIBUTES lpThreadAttributes,
                             BOOL bInheritHandles,
                             DWORD dwCreationFlags,
                             LPVOID lpEnvironment,
                             LPCTSTR lpCurrentDirectory,
                             LPSTARTUPINFO lpStartupInfo,
                             LPPROCESS_INFORMATION lpProcessInformation){
    BOOL createStatus = FALSE;
    // 若钩住,则跳转到此钩子函数,此时先解钩,不然下面调用被钩函数,又跳转回来,死循环
    myHook.UnHook();
    // 弹窗
    MessageBoxA(NULL, "in fact, call MyCreateProcessA  ", "call CreateProcessA", MB_YESNO);
    // 创建进程
    createStatus = CreateProcessA(lpApplicationName,
            lpCommandLine,
            lpProcessAttributes,
            lpThreadAttributes,
            bInheritHandles,
            dwCreationFlags,
            lpEnvironment,
            lpCurrentDirectory,
            lpStartupInfo,
            lpProcessInformation
            );
    // 重钩
    myHook.ReHook();

    return createStatus;

}


extern "C" BOOL APIENTRY DllMain(HANDLE hModule, DWORD reason_for_call, LPVOID lpReserved){
    switch (reason_for_call){
        // dll首次被Load时,钩住Kernel32.dll中的CreateProcessA,钩子函数为MyCreateProcessA
        case DLL_PROCESS_ATTACH: {
            if(myHook.Hook("Kernel32.dll", "CreateProcessA", (FARPROC)MyCreateProcessA)){
                // 成功挂钩
                MessageBoxA(NULL, "hook success", "success", MB_OK);
            }
            else{
                MessageBoxA(NULL, "hook failed", "failed", MB_OK);
            }
            break;
        }
        // dll被卸载时解钩
        case DLL_PROCESS_DETACH: {
            myHook.UnHook();
            break;
        }
    }
}



被挂钩进程

编译出delete.exe
主要做的工作就是调用CreateProcessA创建进程。

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

int main(void){
    Sleep(20 * 1000);
    PROCESS_INFORMATION pi = {0};
    STARTUPINFO si = {0};

    LPCTSTR lpApplicationName = "D:\\Notepad++\\notepad++.exe";    // 可执行文件名
    LPTSTR lpCommandLine = "./readme.txt";    // 命令行参数
    LPSECURITY_ATTRIBUTES  lpProcessAttributes = NULL;    // 进程安全属性
    LPSECURITY_ATTRIBUTES  lpThreadAttributes = NULL;    // 线程安全属性
    BOOL bInheritHandles = FALSE;    // 可继承句柄是否可被新进程继承
    DWORD dwCreationFlags = 0;    // 进程优先级、创建标志
    LPVOID lpEnvironment = NULL;    // 新进程的环境变量
    LPCTSTR lpCurrentDirectory = "D:\\Notepad++\\";    // 新进程的当前目录
    LPSTARTUPINFO lpStartupInfo = &si;    // 新进程的启动信息
    LPPROCESS_INFORMATION lpProcessInformation = &pi;   // 新进程和主线程的相关信息(两个句柄两个ID)
    CreateProcessA(lpApplicationName, lpCommandLine, lpProcessAttributes,
                   lpThreadAttributes, bInheritHandles, dwCreationFlags,
                   lpEnvironment, lpCurrentDirectory, lpStartupInfo,
                   lpProcessInformation);

    Sleep(10 * 1000);

    return 0;
}

DLL注入器

参考:Windows开发:DLL远程线程注入

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


// 参数:dll路径,被注入进程的PID
bool ModuleInject(LPCTSTR szModule, DWORD dwPid){
    // 参数:权限、是否有子进程继承权限、PID
    HANDLE hProcess = OpenProcess(
            PROCESS_QUERY_INFORMATION | PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE,
            FALSE,
            dwPid
            );
    if (hProcess){
        // 成功获取到进程句柄
        // dll路径字符串的大小
        int cByte = (_tcslen(szModule) + 1) * sizeof(TCHAR);
        // 在被注入进程中申请分配内存,返回首地址
        // 参数:进程句柄、要开始分配内存的位置、分配大小、分配内存类型、内存管理方式
        LPVOID pAddr = VirtualAllocEx(hProcess, NULL, cByte, MEM_COMMIT, PAGE_READWRITE);
        if (pAddr){
            // 分配成功则尝试在被注入进程中写入dll路径
            // 参数:进程句柄、分配好的内存首址、数据内容指针、数据长度、传出(写入了多少字节)
            if (WriteProcessMemory(hProcess, pAddr, szModule, cByte, NULL)){
// 如果定义了UNICODE
#ifdef _UNICODE
    // 使用GMH获得Kernel32.dll的句柄,用GPA在其中找到宽字节的LoadLibraryW函数的地址,此地址在所有进程中相同
    // 强转为PSR类型指针,作为CreateRemoteThread的参数
    PTHREAD_START_ROUTINE pfnStartAddr = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(_T("Kernel32")), "LoadLibraryW");
#else
    PTHREAD_START_ROUTINE pfnStartAddr = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(_T("Kernel32")), "LoadLibraryA");
#endif
                if (pfnStartAddr){
                    // 远程线程ID
                    DWORD dwThreadID = 0;
                    // 远程线程句柄
                    // 参数:进程句柄、安全属性指针、堆栈大小(0默认)、线程函数(LoadLibrary)地址、线程函数参数(dll路径)、创建标志、传出远程线程ID
                    HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, pfnStartAddr, pAddr, 0, &dwThreadID);
                    if (hRemoteThread){
                        // 若成功注入,则dll被load到进程中,其DllMain被执行
                        std::cout << "inject success." << std::endl;
                        // 等待远程线程退出再退出
                        WaitForSingleObject(hRemoteThread, INFINITE);

                        CloseHandle(hRemoteThread);
                        CloseHandle(hProcess);
                    }
                    else{
                        std::cout << "create remote thread failed." << std::endl;
                    }
                }
                else{
                    std::cout << "cannot find func LoadLibrary" << std::endl;
                }
            }
            else{
                std::cout << "write mem failed." << std::endl;
            }
        }
        else{
            std::cout << "alloc mem failed." << std::endl;

            return FALSE;
        }

    }
    else{
        std::cout << "failed to open process." << std::endl;

        return FALSE;
    }
}

VOID EnumProcessModule(int myPid) {
    MODULEENTRY32 me32 = {0};
    me32.dwSize = sizeof(MODULEENTRY32);

    // 创建DLL型快照,参数为进程ID
    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, myPid);
    if (hSnap == INVALID_HANDLE_VALUE){
        std::cout << "failed to create module snapshot." << std::endl;
    }
    else{
        BOOL enumStatus = Module32First(hSnap, &me32);
        int i = 0;
        while (enumStatus){
            std::cout << me32.szModule << ", " << me32.szExePath << std::endl;
            i++;
            enumStatus = Module32Next(hSnap, &me32);
        }
    }

    CloseHandle(hSnap);
}


int main() {
    
    int myPid = 0;
    std::cin >> myPid;
    ModuleInject(_T("D:\\CLion_workspace\\win32_programing\\dll_programing\\dll_inject\\lib\\libdll_generate.dll"), myPid);
    Sleep(5000);
    EnumProcessModule(myPid);

    return 0;
}

演示

首先编译DLL,然后编译DLL注入器,路径写生成的DLL路径,编译被注入的delete.exe。

启动DLL注入器
然后启动delete.exe
在delete.exe的源码中,我预留了20秒的时间
在这里插入图片描述这20秒的时间是用来打开任务管理器,查看delete.exe进程的PID的

在这里插入图片描述
dll_inject.exe中输入此PID,回车,然后杀软放行
在这里插入图片描述
提示注入成功
在这里插入图片描述首次load此DLL时,会调用myHook.Hook进行挂钩,被钩函数为Kernel32.CreateProcessA,钩子函数为自定义的MyCreateProcessA
在这里插入图片描述
先来看一下挂钩是怎么挂的
在这里插入图片描述
首先拿到Kernel32.dll的句柄,在其中查找CreateProcessA的地址,然后保存此地址的前5字节的指令,计算此地址到要跳转的MyCreateProcessA的地址的偏移量,构造跳转指令jmp xxxxxxxx,然后覆盖到此地址的前5字节。挂钩完成,此时如果程序调用CreateProcessA,会执行跳转指令,直接跳到钩子函数处。

来看一下钩子函数在这里插入图片描述
因为下面还要调用CreateProcessA,所以需要先解钩,不然又会jmp到钩子函数,死循环。钩子函数比较简单,我只是写了个弹窗验证一下钩子函数被调用了
在这里插入图片描述

然后如果点确定,则继续执行CreateProcessA创建进程,创建完毕之后,再重新钩住。

回到DllMain继续看
在这里插入图片描述
如果挂钩成功,则出现一个弹窗
在这里插入图片描述
点确定,让delete.exe继续执行,当它调用CreateProcessA时,实际已经跳转到了钩子函数MyCreateProcessA
在这里插入图片描述
点’是’让其继续,成功打开notepad++
在这里插入图片描述
关于为什么还能成功执行CreateProcessA我不太清楚,有个猜想应该是钩子函数里做的事情都没有改变堆栈。本来它是将参数都压栈了准备call CreateProcessA,然后就jmp到钩子函数了,但是jmp之后做的事情都没改变堆栈,然后钩子函数又调用了CreateProcessA所以又jmp回到原位置,此时已经解钩了,且堆栈没问题,所以正常执行CreateProcessA,执行完之后,又回到钩子函数继续执行。
先留坑吧,我对动态调试还不熟,以后有能力了可以验证一下。

之后就乏善可陈了,只有一点需要注意。
delete.exe结束,卸载DLL,此时要解钩
在这里插入图片描述
如果不解钩,下次此DLL没有被注入到进程中的话,调用CreateProcessA就不知道jmp到哪里去了,可能会导致程序崩溃。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
//官方网站:www.feiyuol.com //郁金香灬老师 //QQ 150330575 //个人网站:www.yjxsoft.com 跨进程调用CALL 跨进程调用带多个的参数CALL // myInject_dll.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include #include"RWA.h" //PVOID 跨进程分配内存(WORD nSize ); //1、获取进程句柄 //2、读写 分配内存 创建线程 //3、跨进程分配内存 void mycall() { PVOID p1=跨进程分配内存(1000); printf("分配的内存地址=%p\n",p1); printf("按回车键 释放内存\n"); getchar(); 跨进程释放内存(p1,1000); printf("已经 释放内存\n"); } LPTHREAD_START_ROUTINE a; BOOL 跨进程调用CALL(PVOID pcall地址,PVOID plst参数 ); //LoadLibraryA(dll名字指针) //MessageBeep(1) void Test远程调用MessageBeep() { 跨进程调用CALL(MessageBeep,(PVOID)0x12768); } //注入my022MFC.dll到目标进程 void Test3() { char szDllName[]="my022MFC.dll"; //全路径 // char szDllName[]="C:\\Users\\yjxsoft\\Documents\\visual studio 2010\\Projects\\my022\\Debug\\my022MFC.dll"; PVOID p1=跨进程分配内存(1000); printf("分配的内存地址=%p\n",p1); WN((DWORD)p1,szDllName,sizeof(szDllName));//WriteProcessMemory /*跨进程调用CALL(LoadLibraryA,(PVOID)szDllName);*/ 跨进程调用CALL(LoadLibraryA,(PVOID)p1); } void Test4() { // char szDllName[]="my022MFC.dll"; //全路径 char szDllName[]="E:\\1905\\代码\\my022-24\\Debug\\my022MFC.dll"; PVOID p1=跨进程分配内存(1000); printf("分配的内存地址=%p\n",p1); WN((DWORD)p1,szDllName,sizeof(szDllName));//WriteProcessMemory /*跨进程调用CALL(LoadLibraryA,(PVOID)szDllName);*/ 跨进程调用CALL(LoadLibraryA,(PVOID)p1); } int _tmain(int argc, _TCHAR* argv[]) { //mycall(); Test3(); Test4(); return 0; } //作业 //1、练习跨进程注入DLL //2、跨进程分配的内存内存 使用完后 用VirtualFreeEx释放掉 //3、进程句柄使用完后用CloseHandle释放句柄资源

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值