使用汇编和反汇编引擎写一个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 ...
随机推荐
- 数据可视化【原创】vue+arcgis+threejs 实现流光立体墙效果
本文适合对vue,arcgis4.x,threejs,ES6较熟悉的人群食用. 效果图: 素材: 主要思路: 先用arcgis externalRenderers封装了一个ExternalRender ...
- MindSponge分子动力学模拟——使用迭代器进行系统演化(2023.09)
技术背景 在前面几篇博客中,我们已经介绍过使用MindSponge去定义一个系统以及使用MindSponge计算一个分子系统的单点能.这篇文章我们将介绍一下在MindSponge中定义迭代器Updat ...
- Python colorama 设置控制台、命令行输出彩色文字
为了方便调试代码,经常会向stdout中输出一些日志,但是大量日志,有时不好定位问题. 使用终端打印特定颜色字符串,可以突出显示关键性的信息,帮助用户更好地识别和理解输出内容. https://pyp ...
- Vue源码学习(六):(支线)渲染函数中with(),call()的使用以及一些思考
好家伙, 昨天,在学习vue源码的过程中,看到了这个玩意 嘶,看不太懂,研究一下 1.上下文 这段出现vue模板编译的虚拟node部分 export function renderMixin( ...
- tcpdump后台不间断抓包
版本1的抓包命令 这两天排查一个小问题,需要在服务器上使用tcpdump24小时不间断抓包,这里简单记录下. 先看下tcpdump的语法: tcpdump [ -AbdDefhHIJKlLnNOpqS ...
- daffodil
import java.util.ArrayList; public class Daffodil { /** * 打印出100-999之间所有的"水仙花数",所谓"水仙 ...
- 记一次基于 PowerShell 的 Git 自动化部署 Java 多服务实践
前言 有这么一个自动化部署的需求,凑巧 git 还直接建立在测试服务器上,部署后可以直接在测试服务器上演示 步骤 建立 Git 仓库 与一般的 Git 部署一样,区别是需要添加 --bare 开关,这 ...
- 一个简单的C4.5算法,采用Python语言
Test1.py 主要是用来运行的 代码如下: # -*- coding: utf-8 -*- from math import log import operator import treePlot ...
- 【前端小技巧】如何使用 Eolink Apilkit 调用 Mock ?
在开发过程中,进度比较赶的情况下,前端人员当页面写完时,后台的接口还没写完,等要交付的时候后端才把接口给你,这个时候就很尴尬. 这个时候 Mock 就可以很好的解决这个问题,前端团队可以在 API 还 ...
- Little Victor and Set 题解
Little Victor and Set 题目大意 在 \([l,r]\) 中选不超过 \(k\) 个相异的数使得异或和最小,输出方案. 思路分析 分类讨论: 当 \(k=1\) 时: 显然选 \( ...