首页 > 编程语言 >使用汇编和反汇编引擎写一个x86任意地址hook

使用汇编和反汇编引擎写一个x86任意地址hook

时间:2023-12-21 09:44:06浏览次数:39  
标签:汇编 x86 hook 指令 hookAddress 反汇编 DWORD

最简单的Hook

刚开始学的时候,用的hook都是最基础的5字节hook,也不会使用hook框架,hook流程如下:

  1. 构建一个jmp指令跳转到你的函数(函数需定义为裸函数)
  2. 保存被hook地址的至少5字节机器码,然后写入构建的jmp指令
  3. 接着在你的代码里做你想要的操作
  4. 以内联汇编的形式执行被hook地址5字节机器码对应的汇编指令
  5. 跳转回被hook的地址下一条指令

这样操作比较繁琐,每次hook都要定义一堆东西,还得自己补充hook地址被修改的汇编指令,最重要的是这种hook无法扩展到Python里使用。

加入反汇编和汇编引擎

csdn有一篇文章说了可以通过引入汇编和反汇编引擎来去掉第二步和第四步,也就是不需要关心hook地址的汇编是什么。

文章中用的汇编引擎是XEDParse,我试了下用vs2017编译不通过,看了文档和issue,必须得使用vs2013及以下的版本才能编译成功,所以就放弃了,改成使用keystone。想编译keystone和Beaengine可以看另一篇文章keystone和beaengine的编译

我也对文章中的代码进行了一些小优化,这也是为了方便引入到Python中使用。

开始写代码

下面的说明可能会啰嗦一些,对每行代码都做了解释。你也可以去看c++ 源码,也对每行代码做了注释。

定义一个hook函数, 参数有四个,返回值是被修改的字节数:

  • hookAddress: 要hook的地址
  • hookFunc: hook的回调函数
  • hookOldCode:保存被修改的字节
  • hookOldSize:hookOldCode的缓冲区大小

size_t HookAnyAddress(__in DWORD hookAddress, __in AnyHookFunc hookFunc, __out BYTE* hookOldCode, __in size_t hookOldSize)

AnyHookFunc的函数指针定义:

typedef void(_stdcall * AnyHookFunc)(RegisterContext*);

RegisterContext结构体的定义

struct RegisterContext
{
	DWORD EFLAGS;
	DWORD EDI;
	DWORD ESI;
	DWORD EBP;
	DWORD ESP;
	DWORD EBX;
	DWORD EDX;
	DWORD ECX;
	DWORD EAX;
};

首先定义一个内存的shellcode,用来存放裸函数里的指令

BYTE ShellCode[0x40] = {
	0x60,	//pushad
	0x9C,	//pushfd
	0x54,	//push esp
	0xB8, 0x90, 0x90, 0x90, 0x90,  //mov eax,hookFunc
	0xFF, 0xD0,	 //call eax
	0x9D, //popfd
	0x61, //popad
};

这里的4个0x90是存放hook回调函数的地址,接着写入回调函数地址

memcpy(&ShellCode[0x4], &hookFunc, 4);

分配一块可执行的内存, 用于存放这段shellcode

DWORD shellcodeMemAddr = (DWORD)VirtualAlloc(NULL, 0x100, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (shellcodeMemAddr == 0) {
	return 0;
}

因为shellcode已经写了0xC个字节,所以后面的指令从+0xC开始写

DWORD shellcodeMemAddrStart = shellcodeMemAddr + 0xC;

定义反汇编引擎和汇编引擎,keystone也是老朋友了,之前x86发消息的时候就已经用过了:

// 定义反汇编引擎
DISASM MyDisasm;
memset(&MyDisasm, 0, sizeof(DISASM));
MyDisasm.EIP = (UIntPtr)hookAddress;
// 设置为32位x86平台
MyDisasm.Archi = 32;
MyDisasm.Options = PrefixedNumeral + ShowSegmentRegs;
// PrefixedNumeral: 数值前加0x,ShowSegmentRegs: 显示段寄存器的值

// 定义汇编引擎
ks_engine *ks;
ks_err err = ks_open(KS_ARCH_X86, KS_MODE_32, &ks);
if (err != KS_ERR_OK) {
	return 0;
}

开始计算hook地址的指令,并将指令写到shellcodeMemAddr里

// 保存返回hook地址下一条指令的地址
DWORD hookRetAddr = 0;
// 记录被修改的指令长度
size_t hookSize = 0;
// 开始循环反汇编,直到满足5个字节
while (true) {
	// 开始反汇编,每次反汇编一条指令,返回这条指令的长度
	int DisasmCodeSize = Disasm(&MyDisasm);
	if (DisasmCodeSize < 1) {
		return 0;
	}
	// hook的地址不能包含ret指令
	if (MyDisasm.Instruction.BranchType == RetType)
	{
		return 0;
	}
	hookSize += DisasmCodeSize;
	// 保存汇编指令条数
	size_t encodingCount;
	// 保存汇编后的指令
	unsigned char *encodingCode;
	// 保存汇编后的指令长度
	size_t encodingSize;
	// 利用keystone将反汇编后的指令再转为机器码,这么操作可以自动处理相对地址
	// 前三个参数是输入参数,第二个参数是反汇编会的指令,第三个参数是指令所在的内存地址(用于计算相对偏移)
	// 后三个参数为输出参数,见定义处
	if (ks_asm(ks, MyDisasm.CompleteInstr, shellcodeMemAddrStart, &encodingCode, &encodingSize, &encodingCount) != KS_ERR_OK) {
		return 0;
	}
	// 将汇编后的机器码写到shellcode
	memcpy(&ShellCode[shellcodeMemAddrStart - shellcodeMemAddr], encodingCode, encodingSize);
	ks_free(encodingCode);
	// 注意: 反汇编和汇编的机器码和长度可能是不一样的
	shellcodeMemAddrStart += encodingSize;
	// 开始下一条指令的反汇编和汇编
	MyDisasm.EIP += DisasmCodeSize;
	// 如果指令达到5个字节就结束
	if (hookSize >= 5)
	{
		hookRetAddr = MyDisasm.EIP;
		break;
	}
}
ks_close(ks);

开始构建跳转指令,跳转回hook地址的下一条指令的位置

// 保存原始内存属性值
DWORD dwOldProtect = 0;
// 给hook的地址赋予可写权限
BOOL bRet = VirtualProtect((LPVOID)hookAddress, 0x20, PAGE_EXECUTE_READWRITE, &dwOldProtect);
if (!bRet) {
	return 0;
}
// 保存被覆盖的机器码
memcpy(hookOldCode, (LPVOID)hookAddress, hookSize);
// 构建跳转指令
BYTE pushRetCode[6] = {
	0x68, 0x90, 0x90, 0x90, 0x90, // push hookRetAddr
	0xC3  // ret
};
memcpy(&pushRetCode[1], &hookRetAddr, 4);

将构架的跳转指令写入到shellcode里,并将shellcode写到申请的内存shellcodeMemAddr里

memcpy(&ShellCode[shellcodeMemAddrStart - shellcodeMemAddr], pushRetCode, sizeof(pushRetCode));
// 将shellcode写入申请的内存地址
memcpy((LPVOID)shellcodeMemAddr, ShellCode, sizeof(ShellCode));

开始修改hook地址的机器码,跳转到申请的内存地址shellcodeMemAddr

BYTE jmpCode[5] = { 0xE9, 0xFF, 0xFF, 0xFF, 0xFF };
*(DWORD*)(jmpCode + 1) = shellcodeMemAddr - (DWORD)hookAddress - 5;
memcpy((LPVOID)hookAddress, jmpCode, 5);
BYTE nopCode[2] = { 0x90,0x90};

如果被修改的指令超过了五个字节,其他字节用nop填充

if (hookSize > 5) {
	memset((LPVOID)(hookAddress + 5), 0x90, hookSize - 5);
}

最后还原内存属性,返回被修改的指令长度

VirtualProtect((LPVOID)hookAddress, 0x20, dwOldProtect, &dwOldProtect);
return hookSize;

取消hook,只需要将保存的机器码还原:

DWORD UnHookAnyAddress(__in DWORD hookAddress, __in BYTE* hookOldCode, __in size_t hookOldSize) {
	DWORD dwOldProtect = 0;
	VirtualProtect((LPVOID)hookAddress, 0x20, PAGE_EXECUTE_READWRITE, &dwOldProtect);
	memcpy((LPVOID)hookAddress, hookOldCode, hookOldSize);
	VirtualProtect((LPVOID)hookAddress, 0x20, dwOldProtect, &dwOldProtect);
	return 0;
}

Python中使用

将这个编译成dll就能在Python里加载了,不过dll只能用于hook当前进程,这是因为函数不能跨进程调用,你创建的回调函数,其他进程无法调用。

解决这个问题也很简单,可以在目标进程申请一块可执行的内存,用汇编引擎和反汇编引擎将回调函数写到这块内存里。

不过我的使用场景是将Python注入到了进程,Python作为线程在目标进程里运行,不用这么繁琐。使用案例看另一篇文章封装32位和64位hook框架实战hook日志

参考

标签:汇编,x86,hook,指令,hookAddress,反汇编,DWORD
From: https://www.cnblogs.com/kanadeblisst/p/17918309.html

相关文章

  • React Hooks的使用规范和最佳实践
    ReactHooks自从推出以来,彻底改变了React组件的编写方式。它们提供了一种在函数组件中使用state和其他React特性的能力,从而使得函数组件更加强大和灵活。本文将深入探讨useEffect、useMemo、useCallback和useState这四种常用Hooks的特点、优缺点,以及它们对组件性能的影响。我们还......
  • 【反汇编3】基本数据类型的表现形式
    参考书籍,《C++反汇编与逆向分析技术揭秘》。 这次主要研究各种数据在计算机里怎么存的,又要涉及补码、科学计数法等基础内容。这些课程计算机专业的都会学,但作为程序员未必有直观的体验,比如java或python程序员,他们不用自己管理内存,也就根本不会接触到这类内容,例如inti=-1;对......
  • 通过反汇编理解GCC优化以及inline函数的功能
    在linux环境写下以下C代码:首先不加优化选项去编译:gcc-ginline_func_test.c-oinline_func_test之后用objdump-S反汇编可见:可见,即使f1是inline函数,还是和f2一样被调用了六次。之后加入优化选项去编译gcc-O1-ginline_func_test.c-oinline_func_test这一次,f2依然被......
  • git hook 和 Husky工具
    githook背景git:除了作为版本控制之外,还能执行自定义操作----githook,它存在于.git文件夹下的hook文件夹,里面有很多以.sample结尾的demo文件,要执行它,只需要把文件名后面的的sample删了。应用场景:(1)实现自动编译(2)自动删除仓库中的编译代码Husky工具介绍是一个专门......
  • react常用hooks
    useMountconstuseMount=(callback)=>{React.useEffect(callback,[])} useUnmounted1constuseUnmount=(callback)=>{2constcallbackRef=React.useRef(callback)34callbackRef.current=callback56React.useEffect(......
  • gitlab如何配置webhook post请求
    需求:1.如果提交了仓库代码,想立即自动构建一个job,拉去仓库代码并且更新代码,2.如果提交了仓库代码,想自动触发一个事务 对于问题1有2种解决方法:1)在gitlab中构建一个webhook。这里需要你提前写好一个post接口,接口做的事情是:构建某一个jenkinsjob,仅此而已。配置webhook步骤:进......
  • 兼容性复制功能/自定义mock数据/通用hook
    *****自定义mockconstresourceList=computed(()=>Array.from({length:20},(_,index)=>index).map((v,i)=>{return{id:i,joinList:Array.from({length:i},(_,index1)=>index1).map((v,......
  • react_hooks系列 useCallback,高阶函数memo
    react_hooks的useCallback,高阶函数memo一、概念和作用1、memo高阶函数:memo解决的是函数式组件的无效渲染问题,当函数式组件重新渲染时,会先判断数据是否发生了变化。相当于类组件的PureComponent(默认提供ShouldComponentUpdate)2、useCallback:1)、useCallback会返回一个函数的memoiz......
  • React Hooks 钩子特性
    人在身处逆境时,适应环境的能力实在惊人。人可以忍受不幸,也可以战胜不幸,因为人有着惊人的潜力,只要立志发挥它,就一定能渡过难关。Hooks是React16.8的新增特性。它可以让你在不编写class组件的情况下使用state以及其他的React特性。ReactHooks表现形式是以use开头......
  • react_hooks系列 useState
    一、作用:useState让函数式组件也可以处理状态。二、格式:1、定义状态:const[状态名,更新状态的函数]=React.useState(初始值|函数);​如:1)、基本类型的状态声明一个新的叫做“count”的state变量,初始值为0。​const[count,setCount]=React.useState(0);//useS......