使用汇编和反汇编引擎写一个x86任意地址hook
最简单的Hook
刚开始学的时候,用的hook都是最基础的5字节hook,也不会使用hook框架,hook流程如下:
- 构建一个jmp指令跳转到你的函数(函数需定义为裸函数)
- 保存被hook地址的至少5字节机器码,然后写入构建的jmp指令
- 接着在你的代码里做你想要的操作
- 以内联汇编的形式执行被hook地址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的更多相关文章
- 汇编入门——使用DOSBox写一个HelloWorld以及相关软件安装
0.0.0) 在D盘建立一个ASM文件夹 0.0.1) 放入所需要的文件 1所标示的红色框为必须要存在的文件,要处理汇编文件.百度网盘中下载. 2自己编写的汇编(asm)文件. 3编译汇编自己生成的文 ...
- 写一个迷你版Smarty模板引擎,对认识模板引擎原理非常好(附代码)
前些时间在看创智博客韩顺平的Smarty模板引擎教程,再结合自己跟李炎恢第二季开发中CMS系统写的tpl模板引擎.今天就写一个迷你版的Smarty引擎,虽然说我并没有深入分析过Smarty的源码,但是 ...
- 只有20行Javascript代码!手把手教你写一个页面模板引擎
http://www.toobug.net/article/how_to_design_front_end_template_engine.html http://barretlee.com/webs ...
- 用 Smali 手写一个可运行的 HelloWorld!!!
一.前言 Android 的 App 实际上并不是运行在 Java 虚拟机中,而是运行在 Dalvik 虚拟机中.Dalvik 虚拟机对 Java 虚拟机做了一些额外的优化,让它更适用于移动设备.而 ...
- 反汇编EXE添加一个启动时的消息框
反汇编EXE添加一个启动时的消息框 最近有一个要修改PE文件的需求,就先从EXE文件下手吧,我也是初学一个小时而已,不过之前接触过一点汇编罢了,这篇文章算是个DEMO,主要的思路是将其反汇编得到汇编代 ...
- IDA反汇编EXE添加一个启动时的消息框
IDA反汇编EXE添加一个启动时的消息框 上一篇文章介绍了用OD反汇编EXE添加一个启动时的消息框,这篇文章也是实现同样的效果,这边主要的思路还是将其反汇编得到汇编代码后,然后手动修改他的逻辑首先跳转 ...
- 【转】用C写一个简单病毒
[摘要]在分析病毒机理的基础上,用C语言写了一个小病毒作为实例,用TURBOC2.0实现. [Abstract] This paper introduce the charateristic of t ...
- JavaScript写一个连连看的游戏
天天看到别人玩连连看, 表示没有认真玩过, 不就把两个一样的图片连接在一起么, 我自己写一个都可以呢. 使用Javascript写了一个, 托管到github, 在线DEMO地址查看:打开 最终的效果 ...
- 写一个Windows上的守护进程(2)单例
写一个Windows上的守护进程(2)单例 上一篇的日志类的实现里有个这: class Singleton<CLoggerImpl> 看名字便知其意--单例.这是一个单例模板类. 一个进程 ...
- AT&T汇编——在你开始写
不知不觉,少年将超过,计算机相关知识,学到基本上可以说是教过.毕业.所以,我们打算更深入了解自己的兴趣背着背笼.也因为它是检讨大学. 计划写的内容在: 1.汇编语言 2.C/C++语言 3.Linux ...
随机推荐
- API接口获取快手商品详情(封装代码)
快手是中国最大的短视频平台之一,也是许多电商企业进行推广的重要渠道.为了更好地了解快手的商品信息,我们可以通过API接口来获取商品详情. 首先,我们需要了解快手API接口和相应的文档 接下来,我们需要 ...
- iframe子窗口调用父窗口方法
//一个iframe页面调用另一个iframe页面的方法self.parent.frames["sort_bottom"].mapp($("#id").val( ...
- 探索抽象同步队列 AQS
by emanjusaka from https://www.emanjusaka.top/archives/8 彼岸花开可奈何 本文欢迎分享与聚合,全文转载请留下原文地址. 前言 AbstractQ ...
- Linux系列教程——Shell、Linux文件管理
文章目录 Shell 1.什么是Bash shell(壳) 2.Bash Shell能干什么? 3.平时我们如何使用Shell呢? 4.Shell提示符 5.Shell基础语法 2.Bash Shel ...
- Linux 回收站
聊一聊执行 rm -rf 数据恢复以及建立 Linux 回收站 误删除 rm -rf 如果在Linux 平台下,执行 rm -rf 误删除文件,我们可以做哪些数据恢复的工作以及我们该如何应对不小心删除 ...
- java——1.变量和数据类型
变量和数据类型 字符.字节.位之间的关系 1.字符:人类可以阅读的文本内容最小单位 字符编码:utf-8,gbk 2.字节:1字符=2字节:1字符=4字节 3.位:1字节=8位 位指的是二进制位, ...
- html5学习内容-6(flex)
弹性布局–flex (一)视口单位主要包括以下4个: vw:1vw等于视口宽度的1% vh:1vh等于视口高度的1% vmin:选取vm和vh中最小的那个 vmax:选取vm和vh中最大的那个 常用于 ...
- 基于AStyle的代码格式化脚本 [已开源]
这是一个简单的windows端脚本 主要用于C/C++代码的格式化 可以添加到鼠标右键,直接在.C/.H文件上右键格式化代码 具体开源地址 https://gitee.com/svchao/code_ ...
- srm-50
刚开始拿到题目没有思路,看了字符串发现也没什么特别的,也没有提示输入什么什么,然后找到main函数,f5进去以后也没什么特别的 然后就看了wp发现得从程序本身出发去解决问题 点开程序,随便输入点什么 ...
- HDL刷题:Edgedetect
原题链接 一道想了好久的题目,在这种并行执行的程序里怎么才能保存前一个状态,看了题解后才发觉,非阻塞赋值啊,代码如下: module top_module ( input clk, input [7: ...