最简单的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的更多相关文章

  1. 汇编入门——使用DOSBox写一个HelloWorld以及相关软件安装

    0.0.0) 在D盘建立一个ASM文件夹 0.0.1) 放入所需要的文件 1所标示的红色框为必须要存在的文件,要处理汇编文件.百度网盘中下载. 2自己编写的汇编(asm)文件. 3编译汇编自己生成的文 ...

  2. 写一个迷你版Smarty模板引擎,对认识模板引擎原理非常好(附代码)

    前些时间在看创智博客韩顺平的Smarty模板引擎教程,再结合自己跟李炎恢第二季开发中CMS系统写的tpl模板引擎.今天就写一个迷你版的Smarty引擎,虽然说我并没有深入分析过Smarty的源码,但是 ...

  3. 只有20行Javascript代码!手把手教你写一个页面模板引擎

    http://www.toobug.net/article/how_to_design_front_end_template_engine.html http://barretlee.com/webs ...

  4. 用 Smali 手写一个可运行的 HelloWorld!!!

    一.前言 Android 的 App 实际上并不是运行在 Java 虚拟机中,而是运行在 Dalvik 虚拟机中.Dalvik 虚拟机对 Java 虚拟机做了一些额外的优化,让它更适用于移动设备.而 ...

  5. 反汇编EXE添加一个启动时的消息框

    反汇编EXE添加一个启动时的消息框 最近有一个要修改PE文件的需求,就先从EXE文件下手吧,我也是初学一个小时而已,不过之前接触过一点汇编罢了,这篇文章算是个DEMO,主要的思路是将其反汇编得到汇编代 ...

  6. IDA反汇编EXE添加一个启动时的消息框

    IDA反汇编EXE添加一个启动时的消息框 上一篇文章介绍了用OD反汇编EXE添加一个启动时的消息框,这篇文章也是实现同样的效果,这边主要的思路还是将其反汇编得到汇编代码后,然后手动修改他的逻辑首先跳转 ...

  7. 【转】用C写一个简单病毒

    [摘要]在分析病毒机理的基础上,用C语言写了一个小病毒作为实例,用TURBOC2.0实现. [Abstract] This paper introduce the charateristic of t ...

  8. JavaScript写一个连连看的游戏

    天天看到别人玩连连看, 表示没有认真玩过, 不就把两个一样的图片连接在一起么, 我自己写一个都可以呢. 使用Javascript写了一个, 托管到github, 在线DEMO地址查看:打开 最终的效果 ...

  9. 写一个Windows上的守护进程(2)单例

    写一个Windows上的守护进程(2)单例 上一篇的日志类的实现里有个这: class Singleton<CLoggerImpl> 看名字便知其意--单例.这是一个单例模板类. 一个进程 ...

  10. AT&amp;T汇编——在你开始写

    不知不觉,少年将超过,计算机相关知识,学到基本上可以说是教过.毕业.所以,我们打算更深入了解自己的兴趣背着背笼.也因为它是检讨大学. 计划写的内容在: 1.汇编语言 2.C/C++语言 3.Linux ...

随机推荐

  1. 【题解】AtCoder Beginner Contest 318(D - Ex)

    赛时过了 A-G,Ex 仿佛猜到了结论但是完全不懂多项式科技,就炸了. 大家好像都秒了 A,B,C 就不写了. D.General Weighted Max Matching 题目描述: 给你一个加权 ...

  2. 命令行安装ipa包

    我们可以通过ssh连接我们的iphone,来使用命令行安装ipa包 itunnel_mux.exe --lport 9993 --iport 22 itunnel_mux.exe --lport 99 ...

  3. 如何创建集成 LSP 支持多语言的 Web 代码编辑器

    对于一个云开发平台来说,一个好的 Web IDE 能很大程度地提高用户的编码体验,而一个 Web IDE 的一个重要组成部分就是代码编辑器. 目前有着多款 web 上的代码编辑器可供选择,比如 Ace ...

  4. LeetCode 周赛上分之旅 #45 精妙的 O(lgn) 扫描算法与树上 DP 问题

    ️ 本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 和 BaguTree Pro 知识星球提问. 学习数据结构与算法的关键在于掌握问题背后的算法思维框架,你的思考越 ...

  5. Quantitative Relationship Induction

    数量关系是指事物之间的数值或数量之间的相互关系(+.-.*./). 数量关系描述各种量的变化和相互关系.数量关系可以包括数值的比较.增减.比例.百分比.平均值等方面. 在数学中,数量关系可以通过代数方 ...

  6. Django框架——模型层单表操作、模型层多表操作、模型层常用和非常用字段和参数、模型层进阶

    文章目录 1 模型层-单表操作 一 ORM简介 二 单表操作 2.1 创建表 1 创建模型 2 更多字段 3 更多参数 4 settings配置 5 增加,删除字段 2.2 添加表纪录 2.3 查询表 ...

  7. WEB项目开发流程介绍

    web开发流程 web开发流程图 一.需求分析阶段 在需求分析阶段,即上图所述 "需求明确"之前的阶段 产品经理PM召集需要项目相关人员,开需求讨论会.讲解原型 相关人员需要以此了 ...

  8. MySQL5.7版本单节点大数据量迁移到PXC8.0版本集群全记录-2

    本文主要记录57版本升级80版本的过程,供参考. ■ 57版本升级80版本注意事项 默认字符集由latin1变为utf8mb4 MyISAM系统表全部换成InnoDB表 sql_mode参数默认值变化 ...

  9. Jmeter将响应数据的结果保存到本地的一个文件(xls和csv)

    打印excel和csv文件的区别?? 第一种:打印excel 第二种:打印csv文件 创建beanshell后置处理器  import org.json.*;import java.io.*; Str ...

  10. 多源异构数据信息的融合方式0 - Dempster/Shafer 证据理论(D-S证据理论)

    Dempster/Shafer 证据理论(D-S证据理论)的大体内容如下: 一.简介: 在理论中,由互不相容的基本命题组成的完备集合Θ称为识别框架,表示对于某一问题的所有可能答案,但是只有一个答案是正 ...