这些天一直在逆向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 表,
下面的结构对应于在 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
这个函数封装了两种操作,一种是在函数开头的inline Hook 一种是对于函数中call指令的inline Hook,我们分段看一下

首先是必要的变量初始化,以 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;
}
目的很明显的,如果目标函数之前已经被Hook了,那么HS将覆盖这个Hook,当HS过滤完之后再跳转到之前的HOOK,
这样HS就得到了主导权

如果函数头是FF25形式的jmp呢?那么此时操作的函数头地址就是jmp后面的绝对地址,HS此时的操作便是针对于这个地址的,
如果这里是你的代理函数,那么此时HS已经修改了你的代码

不同于我们平时使用的断中断+修改CR0这种写入Hook的方式,HS巧妙地使用了一个原子操作来完成Hook,非常值得借鉴:



另外关键函数 MakeSpecialAddressWriteAble 由于涉及到的页表页目录相关的知识我还没掌握,等以后再逆一下


写的有点乱七八糟,不知能不能有人看懂.
今天就写这点把,关于其他方面,改天再来讨论