这些天一直在逆向HS,不过鉴于自己蹩脚的汇编水平,以及等于零的逆向经验,以至于十多天时间过去了仍没有把握HS的系统流程
但是细节之处却发现许多值得我们学习借鉴的技术,今天抽出点时间来写一点,与大家共同分享
今天我们主要看一下HS对于内核钩子的处理方式
想要将几个流程函数剥离出来而不掺杂其他的不相干的杂质是困难的,因为其内部都有着千丝万缕的联系,
在这里我选择了HS对于shadow ssdt的处理为例,相对来说这里的封装更纯净一些
HS内部维护了很多全局结构数组以及全局链表,逆向过程中主要就是与它们在打交道,
所以掌握了这些结构的定义形式,很多问题都能迎刃而解
我们看一下 shadow ssdt 所对应的全局结构:
struct{
ULONG: FunctionNameAddress; //+0x00 函数名地址
ULONG: FunctionIndex; //+0x04 函数索引
ULONG: ProxyAddress; //+0x08 代理函数地址
ULONG: PreAddress; //+0x0C hook之前的地址
ULONG: HasBeenHooked; //+0x10 是否已经安装了hook
ULONG: Flag; //+0x14
}shadow_ssdt_hook_info
struct{
ULONG: FunctionName: //函数名称字串指针
ULONG: PreAddress; //hook之前的地址
PVOID: BufferForJmpCode; //跳转指令的地址
BYTE : HasBeenHooked; //+0xC是否已经Hook的标志
BYTE : IsHookCallInstruction; //+0xD是否在call指令处进行Hook
ULONG: unknow1; //+0x10
ULONG: unknow2; //+0x14
ULONG: FunctionFlag; //+0x18
ULONG: unknow3; //+0x1C
}shadow_ssdt_hook_info2
下面的结构对应于在 shadow ssdt 函数中做inline Hook
<对于ssdt的处理,内部也维护了类似的两张表>
看一下主要的流程函数:
主要的两个调用分别完成了对于指定shadow ssdt的两种Hook
另外这里我们也可以看到HS对于锁的应用,像这样的应用在HS中随处可见
有时间的话再将HS中使用的各种锁单独提取出来与大家共同学习,
因为我觉得这对于我们平日的编程特别是安全性与稳定性上有莫大的好处而网上的相关介绍却很少
直接的SSDT Hook很简单,就不说了,我们看一下inline Hook
想一想我们平时进行的inline Hook,一般步骤如下:
1.定位到要进行Hook的地址处
2.nop掉5个或者更多的字节,写入jmp指令调转到我们的代理函数
3.执行原指令,平衡堆栈等一些列操作..
4.跳转回nop之后的地址处继续执行
HS也是这样做的,不过它的跳转都是先跳转到一个全局缓冲,这个全局缓冲是一个中间跳,再由它跳转到代理函数
看一下HS是怎么做的
ShadowSSDT_Hook_Inline只是一个包装函数,过滤了一下全局变量,然后调用Hook_InlineDo进行inline Hook
Hook_InlineDo proc near
OpcodeLength = dword ptr -2Ch
JmpCode = dword ptr -28h
JmpCode_ = dword ptr -24h
temp = dword ptr -20h
WriteAble = dword ptr -1Ch
TotalDecodedLength = dword ptr -14h
PreAddressIp = dword ptr -10h
DecodedLength = dword ptr -0Ch
JmpCodeDump = dword ptr -8
JmpCodeDump_ = dword ptr -4
JmpBackBufferAddress = dword ptr 8 //回跳的缓冲地址
PreAddress = dword ptr 0Ch //进行Hook的起始地址
JmpToProxyBufferAddress = dword ptr 10h //跳至代理函数的缓冲地址
IsHookCallInstruction = byte ptr 14h //是否是对call指令进行Hook
ReturnValue = dword ptr 18h
push ebp
mov ebp, esp
sub esp, 2Ch
push esi
push edi
mov byte ptr [ebp+JmpCode], 90h
mov byte ptr [ebp+JmpCode+1], 90h
mov byte ptr [ebp+JmpCode+2], 90h
mov byte ptr [ebp+JmpCode+3], 90h
mov byte ptr [ebp+JmpCode_], 90h
mov byte ptr [ebp+JmpCode_+1], 90h
mov byte ptr [ebp+JmpCode_+2], 90h
mov byte ptr [ebp+JmpCode_+3], 90h
mov [ebp+OpcodeLength], 0
mov [ebp+TotalDecodedLength], 0
mov [ebp+DecodedLength], 0
push 0
mov eax, [ebp+PreAddress]
push eax
call CheckFunctionWhetherBeenHooked ; 这里验证一下首字节 看看有没有被hook掉
mov [ebp+PreAddress], eax ; 如果被hook了 而且是绝对跳转 这里返回绝对跳转的地址
cmp [ebp+JmpBackBufferAddress], 0
jz short loc_21ACD
cmp [ebp+JmpToProxyBufferAddress], 0
jz short loc_21ACD
cmp [ebp+PreAddress], 0
jnz short loc_21AD4
loc_21ACD: ; ...
xor al, al
jmp loc_21CCE
; ---------------------------------------------------------------------------
loc_21AD4: ; ...
movzx ecx, [ebp+IsHookCallInstruction]
cmp ecx, 1 ; 要进行hook的是call还是别的神马
jz loc_21BF7
mov edx, [ebp+PreAddress]
mov [ebp+PreAddressIp], edx
loc_21AE7: ; ...
lea eax, [ebp+OpcodeLength]
push eax
mov ecx, [ebp+PreAddressIp]
push ecx
call DisAsm
mov [ebp+DecodedLength], eax
mov edx, [ebp+TotalDecodedLength]
add edx, [ebp+DecodedLength]
mov [ebp+TotalDecodedLength], edx
mov eax, [ebp+PreAddressIp]
add eax, [ebp+DecodedLength]
mov [ebp+PreAddressIp], eax
cmp [ebp+TotalDecodedLength], 5
jb short loc_21AE7 ; 确保足够5字节长也就是一条jmp指令的长度
cmp [ebp+TotalDecodedLength], 1Bh
jbe short loc_21B1C
xor al, al
jmp loc_21CCE
; ---------------------------------------------------------------------------
loc_21B1C: ; ...
push 1 ; 对非call处进行hook
push 8
mov ecx, [ebp+JmpBackBufferAddress] ; 定位到 jmp xxxx的缓冲后面
push ecx
call MakeSpecialAddressWriteAble
mov [ebp+WriteAble], eax
mov edx, [ebp+PreAddress]
movzx eax, byte ptr [edx]
cmp eax, 0E9h
jnz short loc_21B6A ; 不是jmp指令的话跳转
mov byte ptr [ebp+JmpCode], 0E9h
mov ecx, [ebp+PreAddress]
mov edx, [ecx+1] ; 定位到机器码偏移
mov eax, [ebp+PreAddress]
lea ecx, [eax+edx+5] ; 计算绝对地址
mov [ebp+temp], ecx
mov edx, [ebp+JmpBackBufferAddress]
add edx, 5
mov eax, [ebp+temp]
sub eax, edx ; 计算偏移地址
mov [ebp+temp], eax
mov ecx, [ebp+temp]
mov [ebp+JmpCode+1], ecx ; 写入到缓冲
mov [ebp+TotalDecodedLength], 0
jmp short loc_21BA0
; ---------------------------------------------------------------------------
loc_21B6A: ; ...
mov byte ptr [ebp+JmpCode], 0E9h ; jmp 指令
mov edx, [ebp+PreAddress]
add edx, [ebp+TotalDecodedLength] ; 回跳的目的地址
mov eax, [ebp+TotalDecodedLength]
mov ecx, [ebp+JmpBackBufferAddress]
lea eax, [ecx+eax+5] ; 目的地址 = jmp偏移+5+当前指令地址
sub edx, eax ; jmp偏移 = 目的地址 -(当前指令地址+5)
mov [ebp+temp], edx
mov ecx, [ebp+temp]
mov [ebp+JmpCode+1], ecx ; 完成jmp code
mov ecx, [ebp+TotalDecodedLength]
mov esi, [ebp+PreAddress]
mov edi, [ebp+JmpBackBufferAddress]
mov edx, ecx
shr ecx, 2
rep movsd ; 复制要执行的指令段
mov ecx, edx
and ecx, 3
rep movsb
loc_21BA0: ; ...
mov eax, [ebp+JmpBackBufferAddress]
add eax, [ebp+TotalDecodedLength]
mov ecx, [ebp+JmpCode]
mov [eax], ecx
mov edx, [ebp+JmpCode_]
mov [eax+4], edx ; 写入回跳的缓冲之中
mov eax, [ebp+WriteAble]
push eax
push 8
mov ecx, [ebp+JmpBackBufferAddress]
push ecx
call MakeSpecialAddressWriteAble
mov edx, [ebp+PreAddress]
mov eax, [edx]
mov [ebp+JmpCode], eax
mov ecx, [edx+4]
mov [ebp+JmpCode_], ecx ; 前8个字节
mov byte ptr [ebp+JmpCode], 0E9h
mov edx, [ebp+PreAddress]
add edx, 5
mov eax, [ebp+JmpToProxyBufferAddress]
sub eax, edx
mov [ebp+temp], eax
mov ecx, [ebp+temp]
mov [ebp+JmpCode+1], ecx
mov edx, [ebp+JmpCode]
mov [ebp+JmpCodeDump], edx
mov eax, [ebp+JmpCode_]
mov [ebp+JmpCodeDump_], eax
jmp loc_21C94
; ---------------------------------------------------------------------------
loc_21BF7: ; ...
mov [ebp+TotalDecodedLength], 5
mov ecx, [ebp+TotalDecodedLength]
mov esi, [ebp+PreAddress]
lea edi, [ebp+JmpCode]
mov edx, ecx
shr ecx, 2
rep movsd ; 复制5个字节 call xxxx
mov ecx, edx
and ecx, 3
rep movsb
mov byte ptr [ebp+JmpCode], 0E9h ; jmp指令
mov eax, [ebp+PreAddress]
add eax, [ebp+TotalDecodedLength]
add eax, [ebp+JmpCode+1] ; 跳转的目标地址
mov [ebp+temp], eax ; 这里是绝对地址 也就是回跳的地址
mov ecx, [ebp+JmpBackBufferAddress]
add ecx, [ebp+TotalDecodedLength]
mov edx, [ebp+temp]
sub edx, ecx ; 回跳的目的地址 - (指令地址+指令长度) = 偏移
mov [ebp+temp], edx
mov eax, [ebp+temp]
mov [ebp+JmpCode+1], eax ; 写入缓冲
push 1
push 8
mov ecx, [ebp+JmpBackBufferAddress]
push ecx
call MakeSpecialAddressWriteAble
mov [ebp+WriteAble], eax
mov edx, [ebp+JmpBackBufferAddress]
mov eax, [ebp+JmpCode]
mov [edx], eax ; 写入回跳jmp命令
mov ecx, [ebp+JmpCode_]
mov [edx+4], ecx
mov edx, [ebp+WriteAble]
push edx
push 8
mov eax, [ebp+JmpBackBufferAddress]
push eax
call MakeSpecialAddressWriteAble
mov ecx, [ebp+PreAddress]
mov edx, [ecx]
mov [ebp+JmpCode], edx
mov eax, [ecx+4]
mov [ebp+JmpCode_], eax
mov ecx, [ebp+PreAddress]
add ecx, 5
mov edx, [ebp+JmpToProxyBufferAddress]
sub edx, ecx
mov [ebp+temp], edx
mov eax, [ebp+temp]
mov [ebp+JmpCode+1], eax
mov ecx, [ebp+JmpCode]
mov [ebp+JmpCodeDump], ecx
mov edx, [ebp+JmpCode_]
mov [ebp+JmpCodeDump_], edx
loc_21C94: ; ...
push 1
push 8
mov eax, [ebp+PreAddress]
push eax
call MakeSpecialAddressWriteAble
mov [ebp+WriteAble], eax
mov ecx, [ebp+JmpCodeDump_]
push ecx
mov edx, [ebp+JmpCodeDump]
push edx
mov eax, [ebp+PreAddress]
push eax
call ExInterlockedCompareExchange64Pack ; 这里正式进行Hook
mov ecx, [ebp+ReturnValue]
mov [ecx], eax
mov [ecx+4], edx
mov edx, [ebp+WriteAble]
push edx
push 8
mov eax, [ebp+PreAddress]
push eax
call MakeSpecialAddressWriteAble
mov al, 1
loc_21CCE: ; ...
pop edi
pop esi
mov esp, ebp
pop ebp
retn 14h
Hook_InlineDo endp
首先是必要的变量初始化,以 JmpCode 为起始的8个字节都填入了nop指令<机器码0x90>,
然后调用 CheckFunctionWhetherBeenHooked 验证了一下 PreAddress 参数地址起始的指令,
传参 PreAddress 指定了进行Hook的起始地址
这个函数检测了一下起始地址是否是 FF25 或者 E9 形式的 jmp,也就是检测目标函数有没有被Hook掉,
如果正常的话返回值与传参相同,否则做相应的处理
<这里因为传入的Flag是0,所以相当于只是过滤了 FF25 形式的jmp,如果是FF25形式的jmp将返回跳转的绝对地址>
接下来是针对call指令进行Hook与否的跳转分支,先看一下对于非call指令的函数头inline Hook:
HS内嵌了一个简易的反汇编引擎,由于我除了OD也没用过别的引擎,也没能看出来这个到底是神马
不过函数形式应该是这样的:
ULONG DisAsm(ULONG:Address, PULONG:OpcodeLength);
既然要对函数头进行inline Hook,就要定位出足够的空间进行指令写入而且定位点不能截断在一条指令的字节之间,
所以内嵌一个反汇编引擎是非常必要的
HS对于非call指令的inline Hook基本都是在函数头直接jmp,所以这里需要至少定位出5个字节的空间
成功定位之后跳转到了 loc_21B1C
这个代码片段针对首条指令是否为jmp指令进行了分别的处理,结合接下来的操作一起看一下:
两个分支的流程分别如下:
A:如果目标函数已经被Hook<首条指令为E9 jmp>,就将回跳的目的地址写成当前Hook的目标地址,转到C
B:目标函数没有被Hook,回跳的目的地址为目标函数地址+TotalDecodedLength,并将长度为TotalDecodedLength的原指令写入回跳缓冲之中,转到C
C:将回跳指令写入缓冲
最后写入跳转到HS代理函数的jmp指令<这里是跳转到一个中间跳的缓冲>
我尝试着写成了C的形式:<未经编译可能存在错误>
#define JmpCodeLength 5
#define CallCodeLength 5
BOOLEAN Hook_InlineDo(PVOID JmpBackBuffer, ULONG PreAddress, PVOID JmpToProxyBuffer,
BOOLEAN IsHookCallInstruction, PLARGE_INTEGER ReturnValue)
{
ULONG OpcodeLength = 0;
ULONG DecodedLength = 0;
ULONG TotalDecodedLength = 0;
ULONG AddressTemp;
ULONG WriteAble;
ULONG JmpCode[2];
ULONG Offset;
FillMemory(&JmpCode,sizeof(ULONG)*2,0x90);
PreAddress = CheckFunctionWhetherBeenHooked(PreAddress,0);
if ( (JmpBackBuffer == NULL)||(JmpToProxyBuffer == NULL)||(PreAddress == 0) ) return FALSE;
//这个if else主要是将回跳信息写入JmpBackBuffer
if ( IsHookCallInstruction == FALSE )
{
AddressTemp = PreAddress;
do
{
DecodedLength = HsDisasm(PreAddress,&OpcodeLength);
TotalDecodedLength = TotalDecodedLength + DecodedLength;
} while ( TotalDecodedLength < JmpCodeLength );
if ( TotalDecodedLength > 0x1B ) return FALSE;
WriteAble = MakeSpecialAddressWriteAble(JmpBackBuffer,8,1);
if ( *(PUCHAR)PreAddress == 0xE9 ) //首条指令是jmp
{
*(PUCHAR)&JmpCode = 0xE9;
Offset = *(PULONG)(PreAddress + 1); //得到机器码偏移
AddressTemp = Offset + PreAddress + JmpCodeLength; //计算jmp的绝对地址
Offset = AddressTemp - (ULONG)JmpBackBuffer - JmpCodeLength;
*(PULONG)((ULONG)&JmpCode + 1) = Offset; //写入缓冲
TotalDecodedLength = 0;
}
else //首条指令非jmp
{
*(PUCHAR)&JmpCode = 0xE9;
Offset = PreAddress + TotalDecodedLength - (JmpBackBuffer + TotalDecodedLength + JmpCodeLength); //跳转偏移
*(PULONG)((ULONG)&JmpCode + 1) = Offset; //写入缓冲
RtlMoveMemory(&JmpBackBuffer,(PVOID)PreAddress,TotalDecodedLength); //写入原指令
}
RtlMoveMemory(((PVOID)((ULONG)JmpBackBuffer + TotalDecodedLength)), &JmpCode, sizeof(ULONG)*2); //写入回跳jmp指令
MakeSpecialAddressWriteAble((ULONG)JmpBackBuffer,8,WriteAble); //恢复页面
*(PUCHAR)&JmpCode = 0xE9;
Offset = (ULONG)JmpToProxyBuffer - PreAddress - JmpCodeLength;
*(PULONG)((ULONG)&JmpCode + 1) = Offset; //准备跳转至代理函数的数据
}
else //对 call进行Hook
{
RtlMoveMemory(&JmpCode,(PVOID)PreAddress,CallCodeLength);
*(PUCHAR)&JmpCode = 0xE9;
AddressTemp = *(PULONG)((ULONG)&JmpCode + 1) + PreAddress + CallCodeLength; //取到call的绝对地址
Offset = AddressTemp - JmpBackBuffer - CallCodeLength; //计算偏移
*(PULONG)((ULONG)&JmpCode + 1) = Offset;
WriteAble = MakeSpecialAddressWriteAble(JmpBackBuffer,8,1);
RtlMoveMemory((PVOID)JmpBackBuffer,&JmpCode,sizeof(ULONG)*2);
MakeSpecialAddressWriteAble(JmpBackBuffer,8,WriteAble);
RtlMoveMemory(&JmpCode, (PVOID)PreAddress, sizeof(ULONG)*2);
Offset = (ULONG)JmpToProxyBuffer - PreAddress - CallCodeLength;
*(PULONG)((ULONG)&JmpCode + 1) = Offset; //准备跳转至代理函数的数据
}
WriteAble = MakeSpecialAddressWriteAble(PreAddress,8,1);
*ReturnValue = ExInterlockedCompareExchange64Pack(PreAddress,JmpCode);
MakeSpecialAddressWriteAble(PreAddress,8,WriteAble);
return TRUE;
}
这样HS就得到了主导权
如果函数头是FF25形式的jmp呢?那么此时操作的函数头地址就是jmp后面的绝对地址,HS此时的操作便是针对于这个地址的,
如果这里是你的代理函数,那么此时HS已经修改了你的代码
不同于我们平时使用的断中断+修改CR0这种写入Hook的方式,HS巧妙地使用了一个原子操作来完成Hook,非常值得借鉴:
另外关键函数 MakeSpecialAddressWriteAble 由于涉及到的页表页目录相关的知识我还没掌握,等以后再逆一下
写的有点乱七八糟,不知能不能有人看懂.
今天就写这点把,关于其他方面,改天再来讨论
- 标 题:简单分析一下HS驱动保护 - Hook篇
- 作 者:thisIs
- 时 间:2011-10-09 11:35:29
- 链 接:http://bbs.pediy.com/showthread.php?t=141127
代码:
代码:
代码:
代码: