首页
论坛
课程
招聘
[原创]SSDT InlineHook学习笔记
2015-5-9 00:59 8344

[原创]SSDT InlineHook学习笔记

2015-5-9 00:59
8344
最近学习SSDT InlineHook,看了很多论坛前辈的文章,收获很多,代码很长,也很完善,但觉得有点难理解,太完善的代码会禁锢人的思维,发现问题通过努力自己解决,岂不理解得更加深刻?纸上得来终觉浅,绝知此事要躬行。下面说说我对InlineHook的理解,以NtOpenProcess为例。先看下正常的NtOpenProcess的执行流程,如图1:



我们应该怎么Hook这个函数呢,最简单的方法只需要把函数头的push 0C4h改为jmp xxxxxxx就可以了,当调用NtOpenProcess的时候,就会跳到我们的Hook函数上,就可以为所欲为了。这就是InlineHook,改变函数内部执行流程的Hook就是InlineHook。Hook后的函数,如图2:



我们看到原函数的push 04Ch已经变为jmp f7c15600,这个跳转的地址就是HookedNtOpenProcess的地址。那么问题来了,我们如何把原函数头部的push指令改为jmp指令,使其跳转到我们的Hooked函数上呢。我们必须要取得两个函数的地址,由于SSDT表是导出的,所以取得NtOpenProcess非常简单.

typedef struct _SERVICE_DESCRIPTOR_TABLE
{
  PVOID    ServiceTableBase;
  PULONG   ServiceCounterTableBase;
  ULONG    NumberOfService;
  ULONG    ParamTableBase;
}SERVICE_DESCRIPTOR_TABLE,*PSERVICE_DESCRIPTOR_TABLE; 

Extern PSERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable;       //KeServiceDescriptorTable为导出函数

Address=(ULONG)KeServiceDescriptorTable->ServiceTableBase+0x7A*4;
//存放NtOpenProcess的数组下标为7A,

OldServiceAddress=*(ULONG*)Address;  
//取得Address中的内容,为NtOpenProcess地址。


HookedNtOpenProcess函数是我们定义的,直接取就可以,这样jmp的地址就可以计算了。
jmpaddr=(ULONG)HookedNtOpenProcess-(ULONG)RealNtOpenProcess-5;为什么要这样计算跳转地址呢,直接jmp MyNtOpenProcess不可以吗,当然不行。在16位汇编下,jmp指令是分段内转移和段间转移的。32位下内存为flat模式,不再区分段内段外,可以理解为都是段内转移,所以jmp指令执行的时候不需要转移的目的地址,需要的是偏移。我们需要以原函数的第二条指令为基址计算偏移(push 04Ch占5字节),也就是图1中push offset nt!ObWatchHandles+0x25c (804daaa8)指令,写成如下格式可能更好理解

jmpaddr=(ULONG)HookedNtOpenProcess-((ULONG)RealNtOpenProcess+5);

Jmpaddr就是跳转位移,跟jmp的机器码e9组合起来,就是最关键的跳转指令了。如下图所示,这我Hook完之后的函数头,大家注意,e965336577这个字节码,e9为jmp指令的字节码,65336577倒过来看77653365为相对于原函数的偏移量。



我们可以反推一下Hooked函数=原函数+偏移量+5,我们用WinDbg看下是否正确,如下图:



果然,已经来到了Hooked函数头部,我们的计算是正确的。具体函数如下。

ULONG OldServiceAddress;                                         //原来NtOpenProcess的服务地址

VOID Hook(int *PID)
{  //HANDLE pid;
     ULONG temp;
  ULONG Address;
  ULONG jmpaddr;
  Address=(ULONG)KeServiceDescriptorTable->ServiceTableBase+0x7A*4;    //0x7A为NtOpenProcess服务ID
  DbgPrint("Address:0x%08X\n",Address);
  MyPID=(HANDLE)*PID;
  OldServiceAddress=*(ULONG*)Address;                                  //保存原来NtOpenProcess的地址
  RealNtOpenProcess=(NTOPENPROCESS)OldServiceAddress;
  DbgPrint("RealNtOpenProcess:0x%08X\n",RealNtOpenProcess);

  DbgPrint("MyNtOpenProcess:0x%08X\n",MyNtOpenProcess);

  __asm                                                                //去掉关内存保护
  {
        cli
      mov    eax,cr0
      and    eax,not 10000h
      mov    cr0,eax
  }


     temp=*((ULONG*)Address);
     DbgPrint("temp:%08X\n",temp);
     jmpaddr=(ULONG)MyNtOpenProcess-(ULONG)RealNtOpenProcess-5;
         __asm
         {
           mov ebx,temp
           mov eax,jmpaddr
           mov byte ptr ds:[ebx],0xE9           //在原来函数头加jmp 
           mov DWORD ptr ds:[ebx+1],eax
         }

  __asm                                                               //恢复开内存保护 
  {
        mov    eax,cr0
      or     eax,10000h
      mov    cr0,eax
      sti
  }
  DbgPrint("hook\n");
}


Hook函数完成了,接下来看下执行主体的实现方法。NtOpenProcess在内核中的调用非常频繁,所以我们需要判断哪些程序不能发现我们保护的进程。这个函数有多种写法, 我更喜欢用naked函数,这里又要普及一下naked关键字了。裸函数就是不为你添加任何汇编指令的函数,真正的全都要自己写的函数,使用这个关键字有几点需要特别注意:

1.使用 naked 关键字必须自己构建 EBP 指针
2.必须显示返回,用联汇编的ret不是c的return
3.不能直接引用参数,需要用寄存器如ESP+8
4.栈平衡要尤其注意


最简单的我们可以把它写成这样子,什么都不做,直接返回四个参数,这样也Hook NtOpenProcess。

NTSTATUS __declspec(naked)(__stdcall MyNtOpenProcess)(
                                              PHANDLE ProcessHandle,
                        ACCESS_MASK DesiredAccess,
                        POBJECT_ATTRIBUTES ObjectAttributes,
                        PCLIENT_ID ClientId) 
{
  __asm
  {
  
          retn 0x10   //执行保护后的返回
  }


我们现在把它扩展一下,根据进程ID来保护进程不被OD发现并且不能被任务管理器终结。
我们关注下第四个参数:

typedef struct _CLIENT_ID {
    HANDLE UniqueProcess;
    HANDLE UniqueThread;
} CLIENT_ID;
typedef CLIENT_ID *PCLIENT_ID;


UniqueProcess这个成员变量是调用者传入的进程PID,只需要根据这个PID来判断是否使我们要保护的进程就可以了,因为不能直接访问参数所以用寄存器访问。

__asm
  {
    push eax
    mov eax,[esp+0x14]  //获取ClientId,因为是第四个参数所以【esp+10】,但上面有push eax 所以再加4
    mov eax,[eax]       //取得UniqueProcess
    mov PID,eax        //不能在naked函数里定义局部变量
    pop eax
  }


PID和Access我定义的是全局变量,保存esp 用ebp来存放局部变量,应该也可以但我并没有试验。取到了PID之后呢与需要保护的PID对比

if(PID==MyPID)
    {
      __asm
        {
          mov [esp+4],0 //返回空句柄
          mov eax,0xC0000022L  //返回值STATUS_ACCESS_DENIED 无法发现进程
          retn 0x10   //执行保护后的返回
        }
    }


如果是的话ProcessHandle置零,如果函数成功会返回进程对象,我们不让他成功,返回值设置为0xC0000022L,含义如下

#define STATUS_ACCESS_DENIED             ((NTSTATUS)0xC0000022L)


这个函数相当于我们只把进程对象置零,并且返回无权限,这个Hookd函数就结束了。但这还没有完,当我们需要放行的时候,还需要调用原来的NtOpenProcess。

__asm
  {
    push  0x0C4
    mov edx,RealNtOpenProcess
    add edx,5
    jmp edx
  }


因为我们之前Hook的时候已经把push 04Ch改为了jmp xxxxxxx,所以调用原来函数的时候需要实现它的前五个字节也就是push 04Ch,然后跳到它的下一条指令继续执行。这样这个函数就写完了,可能有点不直观,再来个流程图,如图5



这样清晰多了。我们看下效果吧,driverMoniter载入。



我们已经无法通过任务管理器关闭计算器了。再看下OD能不能发现我们的进程。



OD看不到计算器了,我们的hook成功了。

文笔不好加之水平有限,错漏再算难免,只为做个记录,请大家批评指正。

代码如下:

inlineHook.rar

[注意] 欢迎加入看雪团队!base上海,招聘安全工程师、逆向工程师多个坑位等你投递!

上传的附件:
收藏
点赞0
打赏
分享
最新回复 (15)
雪    币: 762
活跃值: 活跃值 (146)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
dagangwood 活跃值 2015-5-9 01:24
2
0
不错,赞一个!
雪    币: 571
活跃值: 活跃值 (133)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lylxd 活跃值 2015-5-9 04:07
3
0
挺好~~~~~~
雪    币: 2919
活跃值: 活跃值 (807)
能力值: ( LV13,RANK:240 )
在线值:
发帖
回帖
粉丝
IamHuskar 活跃值 4 2015-5-9 09:12
4
0
inline hook还是要考虑多核蓝屏问题。最好HOOK四个字节或者8字节 用原子锁这样的
雪    币: 98
活跃值: 活跃值 (164)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
褐眼男子 活跃值 1 2015-5-9 09:42
5
0
谢大神指点。我还有许多细节需要学习~
雪    币: 2822
活跃值: 活跃值 (147)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
cattyabcd 活跃值 2015-5-9 11:17
6
0
学习一下,请教下r0那个要怎么编译出来
雪    币: 98
活跃值: 活跃值 (164)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
褐眼男子 活跃值 1 2015-5-9 11:27
7
0
你不是没装WDK把,在wdk命令行下,指定目录,build就可以了~
雪    币: 2822
活跃值: 活跃值 (147)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
cattyabcd 活跃值 2015-5-14 01:11
8
0
谢谢,楼主能否分享下学习的资料,对驱动有点无从下手
雪    币: 98
活跃值: 活跃值 (164)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
褐眼男子 活跃值 1 2015-5-14 08:39
9
0
驱动的书比较少,一般都是看张帆的windows驱动开发技术详解。
雪    币: 16
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
leafsv 活跃值 2015-5-14 08:39
10
0
很详细的讲解
雪    币: 16
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
leafsv 活跃值 2015-5-14 14:39
11
0
122是在xp下的地址,在win7下不同,但是我在win7下测试,hook成功,但是会立马蓝屏呢?
雪    币: 98
活跃值: 活跃值 (164)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
褐眼男子 活跃值 1 2015-5-14 16:09
12
0
这个函数在WIN7的索引已经变了,而且头部也不是push 04c,肯定会蓝屏的
雪    币: 16
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
leafsv 活跃值 2015-5-14 17:06
13
0
哦,索引我改了,那就是头部的问题了
雪    币: 11
活跃值: 活跃值 (66)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
kldxxlxc 活跃值 2017-7-15 10:47
14
0
感谢分享,在win7中的函数头部就是mov  edi,edi  push  ebp  mov  ebp,esp,和普通的hook头五个字节一样
雪    币: 11
活跃值: 活跃值 (66)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
kldxxlxc 活跃值 2017-7-15 11:53
15
0
建议再hook前保存好原函数的前五个字节,方便对hook的恢复
雪    币: 1100
活跃值: 活跃值 (877)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
inquisiter 活跃值 2 2017-7-15 12:07
16
0
仁兄,我也做过一个类似的,只不过hook了ntterminateprocess,效果就是任务管理器无法关闭程序了,你这个思路好像更好点
游客
登录 | 注册 方可回帖
返回