当前位置: 首页 > news >正文

mockcpp的ApiHook实现原理

相关汇编知识
call 指令
        它会自动进行ret addr的压栈。
 
ret n 指令
        它会自动退栈,用于stdcall的时候。(与默认的cdecl相比,不用每个调用处都产生一条调整栈的指令 add esp,n
        ret之后的n只能是立即数,不能是寄存器。
 
ret 指令(包括ret 和 ret n)
       它会把栈顶的ret addr作为下一步要执行的位置,从而跳转到返回地址处。
 
参数传递
      这里考虑的是intel架构CPU,所有参数都通过栈传递,并且进入函数func(para1, para2, ...)时,栈顶是ret addr,继续向栈底方向,依次是para1、para2、...
 
mockcpp的ApiHook背景
1、ApiHook的主要优势在于不侵入源代码,即可对已有的函数打桩。
 
2、ApiHook通过把被mock函数打桩为hook函数的方式,实现模拟函数的功能。
 
3、ApiHook必须把被mock的函数地址传递给hook方法,以便查找mock规范。
 
stdcall的api hook实现基本原理分析
编译器可以根据函数的参数自动推导出ret n的n的值,利用这个特点,我们把hook声明为stdcall类型,编译器就会自动帮我们推导出n了。
这样一来,我们的thunk代码就变得非常简单了,把old addr放到栈里面,作为参数传递给hook(以便根据old addr查找mocker),
不要用call hook,而是直接jmp hook,这样在hook返回时执行ret n就把栈恢复到了跳进thunk之前的状态了,一切mock替换完成了。
实现中,需要注意jmp hook时,需要保证栈顶位置是ret addr,所以得到的thunk代码如下:
const unsigned char thunkCodeTemplateStdcall[] = 

0xB8, 0x00, 0x00, 0x00, 0x00, // mov eax, [new_addr]
0xB9, 0x00, 0x00, 0x00, 0x00, // mov ecx, [old_addr]
0x5A, // pop edx
0x51, // push ecx
0x52, // push edx
0xFF, 0xE0 // jmp eax
};
 
栈的变化图
上面部分是进入thunk前的栈结构,下面部分是进入thunk后的栈结构(包含了上面部分)
| frame pointer |<-- ebp
| local vars |
| parameters | para1, para2, ...
| ret addr | call func_to_be_mocked(para1,para2, ...)

| func_to_be_mocked: jmp to thunk 
V
|->| frame pointer | 
| | local vars |
| | parameters | 
| | old addr | thunk begin, pop ret addr, push old addr, push ret addr =&gt; make ret addr at stack top 
|--| ret addr | and then jmp hook(old_addr, para1, para2, ...)
| new frame pointer | hook begin
|-------------------| <- ebp
| local vars |
| | 
| | 


mov eax, [new_addr]
mov ecx, [old_addr] 
pop edx ; pop ret addr
push ecx
push edx
jmp eax

hook function is:
hook(unsigned int old_addr, ...)
 
默认调用约定的apihook实现基本原理分析
默认调用约定(或者说是VC的cdecl调用约定),是需要调用者(caller)在被调函数(callee)返回后,再执行一条指令add esp,n来退栈的(清除栈中保存的被调函数需要的参数)。
这种情况下,调用者只能根据自己为了调用被调函数callee而push到栈中的参数(也就是被调函数声明的参数)来退栈。
所以,我们不能类似stdcall那样直接jmp hook的方式来实现,只能用thunk来模拟一个函数,在thunk中调用hook,注意thunk代码要像真正的函数一样来维护栈,包括hook返回后的栈顶调整(可能是mov esp,ebp ,或者leave,或者别的add esp, n)。
thunk需要完成的功能就是把old addr放入栈中,然后调用hook,并且保证hook能得到old addr。
最终得到的thunk代码如下:
const unsigned char thunkCodeTemplate[] = 

0x55, // push ebp
0x8B, 0xEC, // mov ebp, esp
0xB8, 0x00, 0x00, 0x00, 0x00, // mov eax, [new_addr]
0xB9, 0x00, 0x00, 0x00, 0x00, // mov ecx, [old_addr]
0x51, // push ecx
0xFF, 0xD0, // call eax
0xC9, // leave
0xC3 // ret
};
 
栈的变化图
上面部分是进入thunk前的栈结构,下面部分是进入thunk后的栈结构(包含了上面部分)
| frame pointer |&lt;-- ebp
| local vars |
| parameters | para1, para2, ...
| ret addr | call func_to_be_mocked(para1,para2, ...)

| func_to_be_mocked: jmp to thunk 
V
|->| frame pointer | 
| | local vars |
| | parameters | 
| | ret addr | 
|--| frame pointer | thunk begin
| old addr | 
| ret addr | call hook(old_addr, unused, unused, para1, para2, ...)
| new frame pointer | hook begin
|-------------------| &lt;- ebp
| local vars |
| | 
| | 


push ebp
mov ebp, esp
mov eax, [new_addr]
mov ecx, [old_addr] 
push ecx
call eax
leave 
ret 

note: leave equals to 
mov esp,ebp 
pop ebp

hook function is:
hook(unsigned int old_addr, void* unused1, void* unused2, ...)
 
根据汇编指令查找机器指令的方法
 
1、写一段嵌入汇编指令的C/C++程序。(把自己需要查找机器码的汇编指令写进去,至于程序是否能运行不用考虑)
int main()
{
_asm 

pop edx; 
push edx; 
jmp eax; 
push ecx;
call eax;
}
}
 
注:
最好包含几条熟悉机器指令的汇编语句,便于在hiew中定位到关注的代码的位置。
我上面最后两条指令是知道机器码的:
0x51, // push ecx
0xFF, 0xD0, // call eax
 
2、用Hiew打开1的代码编译出的可执行文件,按F4切换到Decode模式,然后按F7搜索你熟悉的机器码,比如51,很可能就一次定位到你期望的位置。
25778931.png
 
 
 
 





本文转sinojelly51CTO博客,原文链接:http://blog.51cto.com/sinojelly/431896 ,如需转载请自行联系原作者

相关文章:

  • MySQL数据库字符集由utf8修改为utf8mb4一例
  • IDEA 9.0.2整合Tomcat开发
  • Tomcat多域名访问
  • bootstrap模态框垂直居中
  • 如何让你的python爬虫“拟人化”, 突破60秒不被ban,绝地求生!
  • python(58):python下划线
  • HIVE,PV,UV分析
  • unity如何实现一个固定宽度的orthagraphic相机
  • 世界上最简单的无等待算法(getAndIncrement)
  • 项目Alpha冲刺Day1
  • RHEL6基础三十二之系统默认语言修改
  • [转]建行B2B支付回调参数乱码现象解析
  • 制作简易无限魔方
  • 【技巧篇】解决悬浮的header、footer遮挡内容的处理技巧
  • 聚集索引:三级阶梯SQL Server索引
  • JS中 map, filter, some, every, forEach, for in, for of 用法总结
  • $translatePartialLoader加载失败及解决方式
  • crontab执行失败的多种原因
  • JavaScript 无符号位移运算符 三个大于号 的使用方法
  • Java精华积累:初学者都应该搞懂的问题
  • JS题目及答案整理
  • miniui datagrid 的客户端分页解决方案 - CS结合
  • mysql常用命令汇总
  • Promise面试题2实现异步串行执行
  • SpringCloud集成分布式事务LCN (一)
  • Sublime text 3 3103 注册码
  • vagrant 添加本地 box 安装 laravel homestead
  • vue2.0一起在懵逼的海洋里越陷越深(四)
  • vue脚手架vue-cli
  • 回顾 Swift 多平台移植进度 #2
  • 离散点最小(凸)包围边界查找
  • 前端之React实战:创建跨平台的项目架构
  • 深入体验bash on windows,在windows上搭建原生的linux开发环境,酷!
  • 我的业余项目总结
  • 一个SAP顾问在美国的这些年
  • 没有任何编程基础可以直接学习python语言吗?学会后能够做什么? ...
  • 通过调用文摘列表API获取文摘
  • ​比特币大跌的 2 个原因
  • #我与Java虚拟机的故事#连载09:面试大厂逃不过的JVM
  • (七)理解angular中的module和injector,即依赖注入
  • (转)Linux整合apache和tomcat构建Web服务器
  • (转)人的集合论——移山之道
  • .equals()到底是什么意思?
  • .NET Core SkiaSharp 替代 System.Drawing.Common 的一些用法
  • .NET Core工程编译事件$(TargetDir)变量为空引发的思考
  • .Net 应用中使用dot trace进行性能诊断
  • .Net程序猿乐Android发展---(10)框架布局FrameLayout
  • .NET高级面试指南专题十一【 设计模式介绍,为什么要用设计模式】
  • .pyc文件是什么?
  • @Bean有哪些属性
  • [ vulhub漏洞复现篇 ] JBOSS AS 4.x以下反序列化远程代码执行漏洞CVE-2017-7504
  • [ 蓝桥杯Web真题 ]-Markdown 文档解析
  • []常用AT命令解释()
  • [AIGC codze] Kafka 的 rebalance 机制
  • [autojs]逍遥模拟器和vscode对接