InlineHook介绍
InlineHook是一种指令级的函数hook方式,通过直接修改运行时内存的方式替换指令,完全手工的完成hook及跳回操作,理论上可以实现任意位置的hook。平时使用Frida进行Native hook实际上也是用的InlineHook的原理。
InlineHook的大概实现流程是:
- 获取target函数地址
- 读取target函数开头的指令,并保存为opcode_src
- 修改内存,将target函数开头的指令改为跳转指令opcode_jmp,使其跳转到自己编写的my_target函数的开头地址,实现函数替换
- 如果在my_target函数需要调用target函数,先把target函数的指令修改回去opcode_src,然后调用,调用完成后重新修改为opcode_jmp
理论部分大概就这些,接下来讲下实践。这篇文章讲会介绍在Windows平台下,使用DLL注入的方式来实现C 语言的inlinehook的实现。
Target程序编写
随便写一个简单的程序用来hook测试
代码语言:javascript复制#include <stdio.h>
#include <Windows.h>
int targetFunction(){
return 666;
}
int main(){
printf("Calling taget function....n");
int result = targetFunction();
printf("result: %dn",result);
system("pausen");
printf("Calling taget function second time....n");
result = targetFunction();
printf("result: %dn",result);
return 0;
}
这段程序运行后会调用targetFunction
,然后暂停,方便我们进行hook,最后再调用一次targetFunction
DLL编写
我们要编写的DLL它的功能是能够在自己被加载时自动地对targetFunction进行Hook,需要用到DllMain
函数,这个函数在Dll中相当于正常程序中的main
函数,在自己被加载或者被解除加载时会被调用。声明方式为bool DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
,fdwReason
为调用的理由,当其为DLL_PROCESS_ATTACH
时代表被加载,当前为DLL_PROCESS_DETACH
时代表被释放。这边有个坑是这个函数名称为DllMain
而不是DLLMain
,要看清楚否则加载时不会调用。
然后在这边稍微提一下,虽然Hook时用不到但是还是讲下,在写DLL的导出函数时,根据编译器的不同导出函数的声明方式也不一样,网上的教程都是让你写__declspec(dllexport)
然而编译报错,因为我用的是g ,所以其实要写成__attribute__((visibility("default")))
。编译时输入g -shared ./mydll.cpp -o mydll.dll
即可。
最后按照开头介绍的流程编写hook的具体实现即可,目标函数的具体地址可以把编译好的EXE拖到IDA中看一下,跳转指令的汇编网上查一下就差不多了,具体代码如下:
代码语言:javascript复制#if defined(_MSC_VER)
// Microsoft
#define EXPORT __declspec(dllexport)
#define IMPORT __declspec(dllimport)
#elif defined(__GNUC__)
// GCC
#define EXPORT __attribute__((visibility("default")))
#define IMPORT
#else
// do nothing and hope for the best?
#define EXPORT
#define IMPORT
#pragma warning Unknown dynamic link import/export semantics.
#endif
#include <Windows.h>
#include <stdio.h>
extern "C" EXPORT int add(int a, int b);
typedef int(*targetFunc)();
targetFunc fun = (targetFunc)0x401550;
BYTE sourceOpCode[5] = {0x00};
BYTE jmpOpCode[5] = {0xE9};
int add(int a, int b)
{
return a b;
}
void EnableHook(BOOL Enable = TRUE)
{
DWORD OldProtect = 0;
VirtualProtect((LPVOID)fun, 5, PAGE_EXECUTE_READWRITE, &OldProtect);
memcpy((LPVOID)fun, Enable ? jmpOpCode : sourceOpCode, 5);
VirtualProtect((LPVOID)fun, 5, OldProtect, &OldProtect);
}
int myTargetFunc(){
printf("Before calling targetFuncn");
EnableHook(FALSE);
int result = fun();
EnableHook(TRUE);
printf("After calling targetFuncn");
printf("Replace result to 999n");
return 999;
}
bool init()
{
memcpy(sourceOpCode, (LPVOID)fun, 5);
DWORD64 jmpOffset = (DWORD64)myTargetFunc-(DWORD64)fun-5;
*(DWORD64*)&jmpOpCode[1] = jmpOffset;
return true;
}
bool DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch (fdwReason){
case DLL_PROCESS_ATTACH:
printf("DLL loadedn");
if(init()){
EnableHook(TRUE);
}
break;
case DLL_PROCESS_DETACH:
printf("DLL unloadedn");
break;
}
return true;
}
DLL注入
Windows平台的DLL注入比安卓平台的SO注入更简单,只需要知道目标进程的PID即可注入,无需管理员权限。主要使用CreateRemoteThread
来创建远程线程从而加载DLL,代码如下:
#include <stdio.h>
#include <Windows.h>
typedef int(*addFunc)(int,int);
int main()
{
DWORD Pid = 7788;
SIZE_T Size = 0;
const char* DllName = "D:\Coding\cplus\learn\mydll.dll";
HANDLE Handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);
LPVOID Addr = VirtualAllocEx(Handle, NULL, 0x1000, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(Handle, Addr, DllName, strlen(DllName) 1, &Size);
CreateRemoteThread(Handle, NULL, NULL, (LPTHREAD_START_ROUTINE)LoadLibraryA, Addr, NULL, NULL);
return 0;
}
通过这段代码我们可以指定PID为7788的进程去加载mydll.dll,从而触发mydll中的DllMain函数来完成一些函数调用,因此我们可以在mydll中编写hook函数。
效果
就不放截图了,自己这边测试通过了,先打开target.exe会输出666,在打开main.exe实现注入后会打印999。
Reference
PE基础7-HOOK练习