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 ...
随机推荐
- mybatis动态sql详情
mybatis动态拼装sql详情 MyBatis的动态SQL是基于OGNL表达式的,它可以帮助我们方便的在SQL语句中实现某些逻辑. MyBatis中用于实现动态SQL的元素主要有: if choos ...
- python实现加密的方式总结
python实现加密的方式总结 原文地址 目录 基础知识扫盲 Base64 MD5 DES 3DES AES RSA 基础知识扫盲 对称加密 对称密钥加密 , 又叫私钥加密.即信息发送的方和接受方用一 ...
- 企业级开发账号In House ipa发布流程
这两天需要发布一个ipa放到网上供其他人安装,需要用到企业级开发者账号.在网上查了一下资料,感觉没有一个比较完善的流程,于是决定把整个流程写下来,供大家参考. 首先详细说明一下我们的目标,我们需要发布 ...
- 一道有关#define的题
题目是:查看以下代码,问结果是什么? 结果是打印出“array:16345678910”吗? #include "stdafx.h" #include <iostream&g ...
- 3.SpringBoot整合Mybatis(一对多)
前言: Mybatis一对多的处理关系: 一个人有好多本书,每本书的主人只有一个人.当我们查询某个人拥有的所有书籍时,就涉及到了一对多的映射关系. 一.添加数据表: CREATE TABLE `boo ...
- 001-SaltStack入门篇(一)之SaltStack部署
早期运维工作中用过稍微复杂的Puppet,下面介绍下更为简单实用的Saltstack自动化运维的使用. Saltstack知多少Saltstack是一种全新的基础设施管理方式,是一个服务器基础架构集中 ...
- Connection refused 排查过程
Connection refused 排查过程 connection refused 排查 起因 今天在连接 rabbitmq 时,报 Connection refused (如下图),借此机会记 ...
- ZROI 19.08.01 树上数据结构
1.总览 LCT 链分治(树剖) 点/边分治 2.点分治 一棵树,点有\(0/1\),多次修改,询问最远的两个\(1\)距离. 建出点分树,每个子树用堆维护:①最远的\(1\)距离:②它的每个儿子的① ...
- 【leetcode】1219. Path with Maximum Gold
题目如下: In a gold mine grid of size m * n, each cell in this mine has an integer representing the amou ...
- linux-PXE-12
以DHCP+DNS模式管理服务器IP地址和主机名.服务器上架前,以其MAC地址为依据,在DHCP中配置主机保留并分配主机名.DHCP结合TFTP提供的PXE服务,提供PXE引导Linux内核和启动镜像 ...