Win32API起始处的mov edi, edi与用户空间InlineHook
在x86平台上,无论是在调试器中跟到系统DLL中时,还是反汇编某个系统DLL时,经常会发现很多API的第一条汇编指令都是mov edi, edi。根据经验来讲,C函数的汇编形式,应该是首先push ebp保存原始栈顶,然后mov ebp, esp构造新的栈帧,接下来sub esp, 0nnh分配局部变量空间,之后就是函数体了,结束时先mov esp, ebp恢复栈指针,然后pop ebp恢复栈顶指针,最后retn。
push ebp ; 保存原始栈顶指针到栈
mov ebp, esp ; 获取新栈顶指针,用以访问参数和局部变量
sub esp, 0NNh ; 分配局部变量空间,大小0xNN字节
...
... mov esp, ebp ; 恢复初始栈指针
pop ebp ; 恢复原始栈顶指针,调用者用来访问参数和局部变量
retn ; 返回到调用者
现在,前面多了一条mov edi, edi,是用来干什么的呢?看了网上好多论坛博客,众说纷纭。有的说是一条延时指令为了实现一个短的等待,有的说是为了对齐以便于Cache,等等。看了半天,都不是很有说服力,最终在MSDN Blog上找到一篇相关说明,应该是最有道理的。上面说,这条指令的添加主要是为了支持hotpatch,翻译过来好像是叫热修补,类似于Inline Hook。说到这里就要先提一下MSDN帮助文档上与此相关的说明,是关于hotpatch的两个编译链接选项:
首先是编译器选项/hotpatch:
When /hotpatch is used in a compilation, the compiler ensures that first instruction of each function is at least two bytes, which is required for hot patching.
To complete the preparation for making an image hotpatchable, after you use /hotpatch to compile, you must use /FUNCTIONPADMIN to link. When you compile and link an image by using one invocation of cl.exe, /hotpatch implies /functionpadmin.
Because instructions are always two bytes or larger on the ARM architecture, and because x64 compilation is always treated as if /hotpatch has been specified, you don't have to specify /hotpatch when you compile for these targets; however, you must still link by using /functionpadmin to create hotpatchable images for them. The /hotpatch compiler option only affects x86 compilation.
大致含义:
当/hotpatch用于编译时,编译器会确保每个函数的第一条指令至少占两个字节,这是热修补的要求。
要使生成的映像文件可以热修补,除了使用/hotpatch来编译,你还必须使用/FUNCTIONPADMIN进行链接。当你使用cl.exe一次性完成编译和链接时,/hotpatch隐含了/FUNCTIONPADMIN。
因为ARM架构的指令至少占两个字节,而且x64编译总是视为/hotpatch已指定,所以当编译目标为这两种架构时不必指定/hotpatch参数;然而,你仍然必须使用/FUNCTIONPADMIN来为它们生成可以热修补的映像文件。/hotpatch编译器选项仅影响x86编译。
连接器选项/FUNCTIONPADMIN:
The amount of padding to add to the beginning of each function, 5, 6, or 16. x86 images require five bytes of padding, x64 images require 6 bytes, and images built for the Itanium Processor Family require 16 bytes of padding at the beginning of each function.
By default, the compiler will add the correct amount of space to the image, based on the machine type of the image.
In order for the linker to produce a hotpatchable image, the .obj files must have been compiled with /hotpatch (Create Hotpatchable Image).
When you compile and link an image with a single invocation of cl.exe, /hotpatch implies /functionpadmin.
机器译文:
要添加到每个函数开头的填充量,为 5、6 或 16字节。在每个函数的开头,x86 映像需要填充 5 个字节,x64 映像需要填充 6 个字节,为 Itanium 处理器系列生成的映像需要填充 16 个字节。
默认情况下,编译器根据映像的计算机类型将正确的空格数添加到映像中。
为了使链接器生成可热修补的映像,.obj 文件必须已使用 /hotpatch 进行编译。
使用 cl.exe 的单个调用来编译和链接映像时,/hotpatch 隐含表示 /functionpadmin。
看完上面两段文字,应该会有所思。回过头来继续刚才的mov edi, edi,先来看一下函数开始几条指令的大小:
00000000 8BFF mov edi, edi ; 2字节
00000002 55 push ebp ; 1字节
00000003 8BEC mov ebp, esp ; 2字节
我们可以看到正如上面帮助文档所讲,mov edi, edi占用了两个字节。那么为什么第一条指令不能少于两个字节呢?其实以前的MSDN上本来说的是确保第一条指令为两字节,后来更改为不少于两字节,估计早时就是针对x86上的mov edi, edi来说的。。根据hotpatch的设计思路,要在第一条指令处写入一个短跳转,x86平台为0xEB后跟一字节偏移立即数,通过这个短跳转跳转到函数间的填充区域,在填充区域写入一个长跳转,x86平台为0xE9后跟一个双字立即数偏移,通过这个长跳转就可以跳转到我们的Patch函数或者Hook函数。
E902000000 jmp 07h ; 函数间至少有5字节填充,将双字偏移2改为实际函数的相对地址 EBF9 jmp 0h ; 原来的mov edi, edi,负向跳转7字节到长跳转指令
函数头部的第一条指令如果是像mov edi, edi这样的一条无作用指令,那么跳回前也不需要再进行等价操作来实现这条指令的作用了,直接跳转回mov edi, edi的下一条指令即可。看一看现在好多Inline Hook在实现的时候,并没有利用这种机制,而是直接覆盖掉函数的前5字节,然后自己实现被覆盖的指令。其实如果目标函数前三条指令是mov edi, edi、push ebp和mov ebp, esp的话,这样也是一个普适的方法,就是跳转回来之前需要自己实现push ebp和mov ebp, esp这两条指令。对于其他情况,需要有针对的特殊化处理。
综上所述,在Hook以mov edi, edi开头的函数时,还是使用短跳转加长跳转的方式实现起来最方便,完全用C语言即可实现,不需要再写汇编代码,还可以做成一个通用Hook函数。只是这种方便只存在于32位的大多数API中,还是有很大局限性的。
OK. That's all. 学识有限,大家有好的思路欢迎交流。
Win32API起始处的mov edi, edi与用户空间InlineHook的更多相关文章
- 函数开始处的MOV EDI, EDI的作用
调试程序调试到系统库函数的代码时,总会发现系统函数都是从一条MOV EDI, EDI指令开始的,紧接着这条指令下面才是标准的建立函数局部栈的代码.对系统DLL比如ntdll.dll进行反汇编,可以发现 ...
- Linux用户空间与内核空间(理解高端内存)
Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递数据,因为Linux使用的虚拟内存机制,用户空间的数据可能被换出,当内核空间使用用户空间指针时,对应的数 ...
- Linux用户空间与内核空间
源:http://blog.csdn.net/f22jay/article/details/7925531 Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针 ...
- linux 用户空间与内核空间——高端内存详解
摘要:Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递数据,因为Linux使用的虚拟内存机制,用户空间的数据可能被换出,当内核空间使用用户空间指针时,对 ...
- linux用户空间和内核空间(内核高端内存)_转
转自:Linux用户空间与内核空间(理解高端内存) 参考: 1. 进程内核栈.用户栈 2. 解惑-Linux内核空间 3. linux kernel学习笔记-5 内存管理 Linux 操作系统和驱 ...
- Linux用户空间与内核空间(理解高端内存)【转】
转自:http://www.cnblogs.com/wuchanming/p/4360277.html Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递 ...
- Linux系统调用具体解释(怎样从用户空间进入内核空间)
系统调用概述 计算机系统的各种硬件资源是有限的,在现代多任务操作系统上同一时候执行的多个进程都须要訪问这些资源,为了更好的管理这些资源进程是不同意直接操作的,全部对这些资源的訪问都必须有操作系统控制. ...
- Linux用户空间与内核地址空间
Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递数据,因为Linux使用的虚拟内存机制,用户空间的数据可能被换出,当内核空间使用用户空间指针时,对应的数 ...
- linux 用户空间与内核空间——高端内存了解
Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递数据,因为Linux使用的虚拟内存机制,用户空间的数据可能被换出,当内核空间使用用户空间指针时,对应的数 ...
随机推荐
- 容器平台选型的十大模式:Docker、DC/OS、K8S 谁与当先?
作者:刘超 来自:网易云 基础服务 无论是在社区,还是在同客户交流的过程中,总会被问到到底什么时候该用 Docker?什么时候用虚拟机?如果使用容器,应该使用哪个容器平台? 显而易见,我不会直接给 ...
- 将JavaScript转化为C#
因为一个需求,不得不将JavaScript转化为C#. 其实有强大的 Node.js. 说说代码, 其实可以使用dynamic 来实现.但是dynamic有很多问题. 直接上代码吧,不是很难,就是很啰 ...
- 测试String.Format中的Format参数
DateTime datetime = DateTime.Now; Console.WriteLine(String.Format("{0:d}", datetime)); // ...
- 调试 ms 源代码
如果需要调试 WPF 源代码或框架源代码,那么需要使用 DotPeek. 首先需要下载 dotPeek ,可以到官网下载 dotPeek: Free .NET Decompiler & Ass ...
- oracle 表空间不足解决办法
问题:在对某一表空间进行新建表的时候,出现ora-01658的错误. create 语句: create table OA_ORGCONFIG( OAOC_UNID INTEGER not ...
- 【ASP.NET MVC 学习笔记】- 18 Bundle(捆绑)
本文参考:http://www.cnblogs.com/willick/p/3438272.html 1.捆绑(Bundle),一个在 View 和 Layout 中用于组织优化浏览器请求的 CSS ...
- js自调用函数的实现方式
我们知道,js中定义自调用函数通常使用下列方式: (function () { alert("函数2"); })(); 事实上,使用括号包裹定义函数体,解析器将会以函数表达式的方式 ...
- 树莓派.安装系统+Node.js+MongoDB系列环境
1.先去树莓派官网下载最新的ROM https://www.raspberrypi.org/downloads/raspbian/ 这里选的是: RASPBIAN JESSIE WITH DESKTO ...
- LeetCode 485. Max Consecutive Ones (最长连续1)
Given a binary array, find the maximum number of consecutive 1s in this array. Example 1: Input: [1, ...
- CentOS7.3安装NVIDIA-1080ti驱动、cuda、cudnn、TensorFlow
本文为作者原创,转载请注明出处(http://www.cnblogs.com/mar-q/)by 负赑屃 Ubuntu非要换centOS...好吧... 看了很多是通过ELRepo源安装驱动,不过我没 ...