easyhook源码分析三——申请钩子
EasyHook 中申请钩子的原理介绍
函数原型
内部使用的函数,为给定的入口函数申请一个hook结构。
准备将目标函数的所有调用重定向到目标函数,但是尚未实施hook。
EASYHOOK_NT_INTERNAL LhAllocateHook(
void* InEntryPoint,
void* InHookProc,
void* InCallback,
LOCAL_HOOK_INFO** OutHook,
ULONG* RelocSize)
参数说明
InEntryPoint—如果入口点不能hook,将返回STATUS_NOT_SUPPORTED
InHookProc—- 与入口点完全匹配的替代函数。
InCallback—-hook后可以从LhBarrierGetCallback 得到的回调函数
OutHook—-返回一个Hook 结构,包含:已经申请好的跳板函数,重定位的入口指针。
RelocSize—- 入口点重定向指令的大小返回值
STATUS_NO_MEMORY
无法在目标入口点周围申请内存
STATUS_NOT_SUPPORTED
目标入口点包含不支持的指令。
STATUS_INSUFFICIENT_RESOURCES
同时叠加在这个函数上的hook 数目太多。敲黑板
这个函数是EasyHook 挂钩操作的一个最最基本也是最最重要的函数,它决定了这个开源工具的实用性以及稳定性。我尽量按照我的理解来讲述这个函数的实现过程。
我们都知道,EasyHook 实现的HOOK算是一种InlineHook,那么InlineHook 的通用的做法是什么呢?EasyHook 的特殊性在哪里?
InlineHook 的通常做法:http://blog.csdn.net/qq_18218335/article/details/76262918最好首先阅读我之前写的关于hook 的方法的介绍的文章,对hook 的方法有基本的理解。EasyHook 使用的是相对来说最复杂的一种hook 的方法,我们需要研究的就是它针对目标函数的前几条指令的各种情况,如何处理。以及如何在不同的地址上模拟执行目标函数的前几条指令的。总的来说,如果被覆盖的指令的执行效果与执行指令的地址(RIP/EIP)有关的话,就需要特殊处理,如果与RIP/EIP 无关,直接拷贝即可
申请hook 时对于目标函数前几条指令的不同处理
函数声明
EASYHOOK_NT_INTERNAL LhRelocateEntryPoint(
UCHAR* InEntryPoint,
ULONG InEPSize,
UCHAR* Buffer,//用于存储转化后的模拟执行目标函数前几个指令的指令
ULONG* OutRelocSize);// 转化后的替代代码的长度
代码分析
while(pOld < InEntryPoint + InEPSize)
{
b1 = *(pOld);
b2 = *(pOld + );
OpcodeLen = ;
AbsAddr = ;
IsRIPRelative = FALSE;
...
}
循环处理目标函数开始所有收到影响的指令,下面的代码都是在这个循环里面的
// 检查指令前缀
switch(b1)
{
case 0x67: // 地址大小重写前缀,后面的代码为16 位代码,我们不考虑
// 关于指令前缀:http://wiki.osdev.org/X86-64_Instruction_Encoding#Operand-size_and_address-size_override_prefix
bCurrent16 = TRUE;
// 标记当前指令包含0x67 前缀,处理下一个指令
pOld++;
continue;
/*
不用管 0x66 前缀[操作数/数据-大小前缀],因为我们仅仅需要直到当前是否为 [地址大小重写] 前缀
0x 66 指令通常毫不改变地复制(除了 64-bit rip 相对地址),此时我们仅仅调整地址
*/
}
如果当前指令的第一个字节为0x67 的话,其代表的是地址大小重写前缀,当我们在Win32 程序进行16位地址操作时就会出现这样的前缀。关于这个前缀没有仔细研究,暂时忽略吧。
// 得到相对地址的值
switch(b1)
{
case 0xE9: // jmp 16位 立即数/32位 立即数
{
/*
当且仅当这个指令是入口的第一个指令时满足条件
*/
if(pOld != InEntryPoint)
THROW(STATUS_NOT_SUPPORTED, L"Hooking far jumps is only supported if they are the first instruction.");
}
case 0xE8: // call 16-bit 立即数 / 32-bit 立即数
{
if(bCurrent16)// 16 位跳转,做过一个实验,测试当有0x67 前缀与没有0x67 前缀,指令的执行逻辑是相同的,可能是我对于这个前缀的理解不对,暂时不研究这个问题
{
AbsAddr = *((__int16*)(pOld + ));
OpcodeLen = ;
}
else
{
AbsAddr = *((__int32*)(pOld + ));
OpcodeLen = ;// 通常的call 指令,指令长度为5
}
}break; case 0xEB: // jmp 8 位立即数
{
AbsAddr = *((__int8*)(pOld + ));
OpcodeLen = ;
}break;
/*
条件跳转指令是不支持的
*/
case 0xE3: // jcxz imm8
{
THROW(STATUS_NOT_SUPPORTED, L"Hooking near (conditional) jumps is not supported.");
}break;
case 0x0F:
{
if((b2 & 0xF0) == 0x80) // jcc imm16/imm32
THROW(STATUS_NOT_SUPPORTED, L"Hooking far conditional jumps is not supported.");
}break;
}// switch(b1)
我们看到上面的这个处理整体上是查找跳转以及call 指令的,如果开头是0xE9 即表示跳转指令的时候,该指令必须是函数的第一个指令,如果是call 指令的话,分为两种情况,一种是有0x67 指令前缀,一种是没有,对于0x67 前缀理解的不够现在暂且不谈,如果是普通的call 指令,这里得到了一个相对的偏移值放在了AbsAddr 中。如果代码中包含了条件跳转指令,将报错,因为此时代码的运行状态是不确定的,因此不支持。
// 转换得到 mov eax,绝对地址
if(OpcodeLen > )
{
AbsAddr += (POINTER_TYPE)(pOld + OpcodeLen); #ifdef _M_X64
*(pRes++) = 0x48; // 一种指令前缀,扩展使用64位操作数
#endif
*(pRes++) = 0xB8; // mov eax,
*((LONGLONG*)pRes) = AbsAddr; // address pRes += sizeof(void*); // points into entry point?
if((AbsAddr >= (LONGLONG)InEntryPoint) && (AbsAddr < (LONGLONG)InEntryPoint + InEPSize))
/* 不支持跳转到我们自己写的跳转指令内部的操作 */
THROW(STATUS_NOT_SUPPORTED, L"Hooking jumps into the hooked entry point is not supported."); // 插入 替代代码
switch(b1)
{
case 0xE8: // call eax
{
*(pRes++) = 0xFF;
*(pRes++) = 0xD0;
}break;
case 0xE9: // jmp eax
case 0xEB: // jmp imm8
{
*(pRes++) = 0xFF;
*(pRes++) = 0xE0;
}break;
}
*OutRelocSize = (ULONG)(pRes - Buffer);
}
// 没有跳转指令,修正 RIP 相关的指令
else
{
// 查看是否有RIP 相对寻址的指令,如果有的话,修正这些指令。
FORCE(LhRelocateRIPRelativeInstruction((ULONGLONG)pOld, (ULONGLONG)pRes, &IsRIPRelative));
}
我们看到,当OpcodeLen > 0 也就代表当前指令为跳转或者call这类改变函数流程的指令的时候,我们会构建一个等价的跳转或者是call 指令,其中使用的是eax存储这个目标地址,当当前平台为64位,这里使用了一个指令前缀0x48,用于扩展访问64-bit的值;如果没有跳转指令,调用LhRelocateRIPRelativeInstruction函数。
EASYHOOK_NT_INTERNAL LhRelocateRIPRelativeInstruction(
ULONGLONG InOffset,
ULONGLONG InTargetOffset,
BOOL* OutWasRelocated)
{
/*
Description: [若给定的指令是RIP 相关?重置它:什么都不做]
[只支持 64-bit]
Parameters: - InOffset The instruction pointer to check for RIP addressing and relocate. - InTargetOffset The instruction pointer where the RIP relocation should go to.
Please note that RIP relocation are relocated relative to the
offset you specify here and therefore are still not absolute! - OutWasRelocated TRUE if the instruction was RIP relative and has been relocated,
FALSE otherwise.
*/ #ifndef _M_X64
return FALSE; // 只有X64 存在RIP 相对寻址
#else
#ifndef MAX_INSTR
#define MAX_INSTR 100
#endif
NTSTATUS NtStatus;
CHAR Buf[MAX_INSTR];
ULONG AsmSize;
ULONG64 NextInstr;
CHAR Line[MAX_INSTR];
LONG Pos;
LONGLONG RelAddr;
LONGLONG MemDelta = InTargetOffset - InOffset;//增量 ULONGLONG RelAddrOffset = ;
LONGLONG RelAddrSign = ; ASSERT(MemDelta == (LONG)MemDelta,L"reloc.c - MemDelta == (LONG)MemDelta"); *OutWasRelocated = FALSE; /*
BYTE t[10] = {0x8b, 0x05, 0x12, 0x34, 0x56, 0x78};
udis86 outputs: 0000000000000000 8b0512345678 mov eax, [rip+0x78563412] // 一个示例代码
*/
// 反汇编当前指令
if(!RTL_SUCCESS(LhDisassembleInstruction((void*)InOffset, &AsmSize, Buf, sizeof(Buf), &NextInstr)))
THROW(STATUS_INVALID_PARAMETER_1, L"Unable to disassemble entry point. "); // 查看当前指令中是否有rip 相对寻址的指令]
Pos = RtlAnsiIndexOf(Buf, '[');
if(Pos < )
RETURN; if (Buf[Pos + ] == 'r' && Buf[Pos + ] == 'i' && Buf[Pos + ] == 'p' && (Buf[Pos + ] == '+' || Buf[Pos + ] == '-'))
{
// 找到了rip 相对指令
/*
Support negative relative addresses
支持负的相对地址
https://easyhook.codeplex.com/workitem/25592
e.g. Win8.1 64-bit OLEAUT32.dll!VarBoolFromR8
Entry Point:
66 0F 2E 05 DC 25 FC FF ucomisd xmm0, [rip-0x3da24] IP:ffc46d4
Relocated:
66 0F 2E 05 10 69 F6 FF ucomisd xmm0, [rip-0x996f0] IP:100203a0
*/
if (Buf[Pos + ] == '-')
RelAddrSign = -; Pos += ;
// parse content
if (RtlAnsiSubString(Buf, Pos + , RtlAnsiIndexOf(Buf, ']') - Pos - , Line, MAX_INSTR) <= )
RETURN; // Convert HEX string to LONGLONG
RelAddr = RtlAnsiHexToLongLong(Line, MAX_INSTR);
if (!RelAddr)
RETURN; // Apply correct sign
RelAddr *= RelAddrSign; if(RelAddr != (LONG)RelAddr)
RETURN;
// 现在我们得到了rip + RelAddr 中的 RelAddr【正/负】 的值
/*
Ensure the RelAddr is equal to the RIP address in code
确保RelAddr 等于 RIP 地址
https://easyhook.codeplex.com/workitem/25487
Thanks to Michal for pointing out that the operand will not always
be at *(NextInstr - 4)
e.g. Win8.1 64-bit OLEAUT32.dll!GetVarConversionLocaleSetting
Entry Point:
83 3D 【71 08 06 00 00】 cmp dword [rip+0x60871], 0x0 IP:ffa1937
Relocated:
83 3D 【09 1E 0B 00 00】 cmp dword [rip+0xb1e09], 0x0 IP:ff5039f
*/
// 找到存储相对地址的地方
for (Pos = ; Pos <= NextInstr - InOffset - ; Pos++) {
if (*((LONG*)(InOffset + Pos)) == RelAddr) {
if (RelAddrOffset != ) {
// More than one offset matches the address, therefore we can't determine correct offset for operand
// 不仅有一个匹配的地址,因此我们不能决定正确的偏移for[操作数]
// 这个可能性基本没有???一个指令长度最大是有限度的,然后在里面有同样的两个地址???
RelAddrOffset = ;
break;
} RelAddrOffset = Pos;
}
} if (RelAddrOffset == ) {
THROW(STATUS_INTERNAL_ERROR, L"The given entry point contains a RIP-relative instruction for which we can't determine the correct address offset!");
} /*
重置这个指令
*/
// Adjust the relative address
RelAddr = RelAddr - MemDelta;// InTargetOffset - InOffset;
// Ensure the RIP address can still be relocated
if(RelAddr != (LONG)RelAddr)
THROW(STATUS_NOT_SUPPORTED, L"The given entry point contains at least one RIP-Relative instruction that could not be relocated!"); // 拷贝指令到 目标地址
RtlCopyMemory((void*)InTargetOffset, (void*)InOffset, (ULONG)(NextInstr - InOffset));
// 利用上面找到的偏移修正 rip 相对地址
*((LONG*)(InTargetOffset + RelAddrOffset)) = (LONG)RelAddr; *OutWasRelocated = TRUE;
} RETURN; THROW_OUTRO:
FINALLY_OUTRO:
return NtStatus;
#endif
}
修正RIP 相对寻址总的来说就是根据指令是’+’或者’-‘,以及老RIP 相对寻址指令与 新指令位置的差值生成新的等价指令的过程。注释写的比较明白,这里就不再过多的解释了。
// 与前面 对应,pOld 需要-- 操作
if (bCurrent16) pOld--; // 找到下一个指令
FORCE(InstrLen = LhGetInstructionLength(pOld)); // 没有找到跳转指令,直接
if(OpcodeLen == )
{
// 不是RIP 相关的指令,直接拷贝这个指令 if(!IsRIPRelative) // RIP 指令相关的指令已经在上面的处理中拷贝到了pRes;
RtlCopyMemory(pRes, pOld, InstrLen); pRes += InstrLen;
} pOld += InstrLen;// 转移到了下一个指令
IsRIPRelative = FALSE;
bCurrent16 = FALSE;
到这里我们就可以清晰的认识这个函数对于目标函数前几个指令的处理过程了。特殊的指令需要处理,主要包括:“绝对jmp 指令、call 指令,RIP 相对寻址的指令”,需要注意的是条件跳转指令是不支持的,跳转到我们所覆盖的指令的地址范围内的指令不支持。其它的指令直接拷贝即可,因为其执行效果与指令运行的位置无关。
上面这段代码将‘跳转到原函数被覆盖指令的后一条指令的代码’放到了重新生成的被覆盖的指令的后面。在执行完新生成的替代代码之后,代码将跳转到原来的位置继续执行。
引用
easyhook源码分析三——申请钩子的更多相关文章
- tomcat源码分析(三)一次http请求的旅行-从Socket说起
p { margin-bottom: 0.25cm; line-height: 120% } tomcat源码分析(三)一次http请求的旅行 在http请求旅行之前,我们先来准备下我们所需要的工具. ...
- 使用react全家桶制作博客后台管理系统 网站PWA升级 移动端常见问题处理 循序渐进学.Net Core Web Api开发系列【4】:前端访问WebApi [Abp 源码分析]四、模块配置 [Abp 源码分析]三、依赖注入
使用react全家桶制作博客后台管理系统 前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基 ...
- Kafka源码分析(三) - Server端 - 消息存储
系列文章目录 https://zhuanlan.zhihu.com/p/367683572 目录 系列文章目录 一. 业务模型 1.1 概念梳理 1.2 文件分析 1.2.1 数据目录 1.2.2 . ...
- ABP源码分析三:ABP Module
Abp是一种基于模块化设计的思想构建的.开发人员可以将自定义的功能以模块(module)的形式集成到ABP中.具体的功能都可以设计成一个单独的Module.Abp底层框架提供便捷的方法集成每个Modu ...
- ABP源码分析三十一:ABP.AutoMapper
这个模块封装了Automapper,使其更易于使用. 下图描述了改模块涉及的所有类之间的关系. AutoMapAttribute,AutoMapFromAttribute和AutoMapToAttri ...
- ABP源码分析三十三:ABP.Web
ABP.Web模块并不复杂,主要完成ABP系统的初始化和一些基础功能的实现. AbpWebApplication : 继承自ASP.Net的HttpApplication类,主要完成下面三件事一,在A ...
- ABP源码分析三十四:ABP.Web.Mvc
ABP.Web.Mvc模块主要完成两个任务: 第一,通过自定义的AbpController抽象基类封装ABP核心模块中的功能,以便利的方式提供给我们创建controller使用. 第二,一些常见的基础 ...
- ABP源码分析三十五:ABP中动态WebAPI原理解析
动态WebAPI应该算是ABP中最Magic的功能之一了吧.开发人员无须定义继承自ApiController的类,只须重用Application Service中的类就可以对外提供WebAPI的功能, ...
- Duilib源码分析(三)XML解析器—CMarkup
上一节介绍了控件构造器CDialogBuilder,接下来将分析其XML解析器CMarkup: CMarkup:xml解析器,目前内置支持三种编码格式:UTF8.UNICODE.ASNI,默认为UTF ...
随机推荐
- python:count 函数
API 一.string 中 某字符 的次数 str.count(sub, start= 0,end=len(string)) Args Annotations sub 搜索的子字符串 start 字 ...
- 十三、LaTex中的参考文献BibTex
将默认文献工具设置为bibtex
- shell编程注意点
1.标准输入输出转存不能随便用,例如: echo "export PATH=/home/huangmr/jdk/bin:\$PATH" >> /home/huangmr ...
- Matlab 快速多通道积分图计算函数
所谓快速多通道积分图计算,其实就是 cumsum2D. 我写了一个比较快的版本(比 VLFeat 的快),用 mex 编译一下就能用了. 代码 #include <string.h> #i ...
- Laravel5如何向闭合函数内传递参数 where function 传参
如上,怎么将$title 像$query一样,在函数内部使用? $result = UserMenus::with(['menu'=>function($query){ $query->w ...
- ARM仿真器之驱动黄色惊叹号
JLink CDC UART PORT 黄色惊叹号 Windows 无法验证此设备所需的驱动程序的数字签名.某软件或硬件最近有所更改,可能安装了签名错误或损毁的文件,或者安装的文件可能是来路不明的恶意 ...
- solairs11与solairs10 ftp服务的区别
Migration from Solaris WU-FTPD to ProFTPD Introduction ------------ This document provides an overvi ...
- JAVA语言程序设计-笔记摘录
JAVA 程序语言设计(基础篇) 笔记摘录 为避免输入错误, 不要在nextByte().nextShort().nextInt()等等后面使用nextLine() nextXXXXX()都称为令牌读 ...
- 在vue-cli中使用px2rem,配合lib-flexible使用
原文地址 附上github源码 看这里 1.下载lib-flexible npm安装 npm i lib-flexible --save 2.引入lib-flexible 在main.js中引入li ...
- thinkphp之cookie操作
cookie设置 命名空间 代码