首页
社区
课程
招聘
[旧帖] [原创]x64内核编程小窥-SSDT HOOK笔记 0.00雪花
2014-11-15 00:46 14404

[旧帖] [原创]x64内核编程小窥-SSDT HOOK笔记 0.00雪花

2014-11-15 00:46
14404
x64内核编程小窥-SSDT HOOK笔记

    声明:本文不包含Anti-PatchGuard的技术,只是单纯的记录一下本人在x64下做SSDT HOOK和x86下的一些区别,以及自己记录的x86下和x64下的内核编程的一些区别,所以想看过PatchGuard的看官们要失望了。本文脱胎于Tesla.Angla的教程,纯属笔记和扫盲贴,为的是加深自己对x64内核编程的印象,也为同样像我这样的菜鸟提供一点点资料,算不上原创。为了论坛文章的质量,对于那些被讨论过无数次的、在网上一搜一堆的基础知识,如什么是SSDT表,什么是KeServiceDescriptorTable本文不做介绍,只在最后做点推荐阅读。本文也只是学习的产物,难免有理解错误的地方,如有错误还请大家谅解。
    此外,由于是笔记,所以本文的写作方法是对32bit和64bit进行对比,阅读本文最好具备一些win32 hook的基本知识,做过win32 SSDT Hook最好不过。
    本人测试环境 win7sp1 x64(虚拟机+主机),VS2013+WDK8.1


一.获取64位下的SSDT表
    在x64下做SSDT HOOK和x86下做SSDT HOOK虽然在原理上都是一样的,但是在实现上还是有很大不同的,最起码的,32位下,KeServiceDescriptorTable表是导出的,而在64位下则没有导出。也就是说,在32位下,我们只需要对KeServiceDescriptorTable进行一下声明就可以使用了,而在64位下,则需要另想办法——比如,通过特征码对内存区域进行搜索,这听上去和32位下的获取Shadow SSDT方法貌似一样,实际上,确实是一样的。所以,在64位下做SSDT HOOK明显比在32位下要复杂,但好处在于,可以用同一段代码实现对Shadow SSDT表的查找。直接上代码:
UINT64 getKeServiceDescirptorTable()
{
  UINT64 KeServiceDescirptorTable = 0;// 接收KeServiceDescirptorTable地址
  PUCHAR addrStartSearch = (PUCHAR)__readmsr(ULONG(0xC0000082));  // 读取KiSystemCall64地址

  PUCHAR addrEndSearch = addrStartSearch + 0x500;  // 搜索的结束地址
  ULONG tmpAddress = 0;// 用于保存临时地址
  int j = 0;// 用于进行索引

  //    开始搜索,从KiSystemCall64开始搜索其函数体内关于KeServiceDescriptorTable结构的信息
  for (PUCHAR i = addrStartSearch; i < addrEndSearch; i++, j++)
  {
    if (MmIsAddressValid(i) && MmIsAddressValid(i + 1) && MmIsAddressValid(i + 2))
    {
      //特征码 0x4c 0x8d 0x15
      if (addrStartSearch[j] == 0x4c &&
        addrStartSearch[j + 1] == 0x8d &&
        addrStartSearch[j + 2] == 0x15)
      {
        RtlCopyMemory(&tmpAddress, i + 3, 4);  // 保存后4个机器码
        // 得到KeServiceDescirptorTable表真实地址
        KeServiceDescirptorTable = tmpAddress + (INT64)i + 7;
      }
    }
  }
  return KeServiceDescirptorTable;
}


    解释一下:在64位下,要得到KeServiceDescriptorTable表,需要从KiSystemCall64中得其偏移(tmpAddress),而要得到KiSystemCall64需要从C0000082寄存器(msr,特别模块寄存器)中读取其地址。从KiSystemCall64往后搜索0x500字节左右,就可以搜到关于KeServiceDescriptorTable信息.....简单来说就是这样:
    __readmsr(0xC00000082)->KiSystemCall64->KeServiceDescriptorTable
    MmIsAddressValid是为了检验内存地址是否可读,也可以不加,这样就可以坐等BSOD了
    得到KeServiceDescriptorTable后将其封装成函数,这样,要得到SSDT表就简单了,SSDT表基址是KeServiceDescriptorTable的第一个结构体成员。所以只需写上如下代码就是:

PSERVICES_DESCRIPTOR_TABLE pServiceDescriptorTable = (PSERVICES_DESCRIPTOR_TABLE)getKeServiceDescirptorTable();
PULONG ssdt = (PULONG)pServiceDescriptorTable->ServiceTableBase;

    这样,就可以通过ssdt指针变量来操作SSDT表了。

二.HOOK前的准备
2.1 64位下SSDT表与32位下的区别
    需要了解的是,在32位下和64位下变量类型的区别!int、long、char、wchar_t等在64位下长度依旧没变,而指针变量统一是8字节,比如前面定义的PULONG,在64位下用sizeof(PULONG)发现,是8字节的,只不过它指向地址是ULONG类型长度的。64位下要使用8字节长度的变量应使用__int64、INT64、UINT64、ULONGLONG、PULONGLONG等。(均指VS编译器)
    前面已经得到SSDT表的起始地址了。要是在32位下,只需这样一行代码,就可以进行HOOK了:
    ssdt[nIndex] = hookXXX; //  hookXXX,自己的代理函数地址。
    在64位下又是怎么样一番景象呢?直接上代码:
    ssdt[nIndex] = hookXXX; //  hookXXX,自己的代理函数地址。
    我擦,看上去不TM一样的么....
    注意,这里有个重要的知识点:在64位下,ssdt[nIndex]保存的是一个4字节长度的地址而不是8字节地址(nIndex为ssdt函数索引号)。实际上,这个地址不是ssdt的实际地址,而只是一个ssdt函数相对于ssdt的一个偏移。真实地址计算公式如下:
    真实SSDT地址 = ssdt(基址) + ssdt[nIndex]>>4
    至于为什么是ssdt[nIndex]>>4而不是ssdt[nIndex],只能说通过逆向发现微软的做法就是这样的.....ssdt[nIndex]>>4才是实际偏移地址......(经查证,这个四节的偏移最后四位是例程的参数个数,所以需要右移四位后取得真正的偏移)
下面贴上具体的计算实际SSDT地址的方法:

UINT64 getSsdtFunctionAddress(UINT index)
{
  INT64 address = 0;
  PSERVICES_DESCRIPTOR_TABLE pServiceDescriptorTable = (PSERVICES_DESCRIPTOR_TABLE)getKeServiceDescirptorTable();
  PULONG ssdt = (PULONG)pServiceDescriptorTable->ServiceTableBase;
  ULONG  dwOffset = ssdt[index];
  dwOffset >>= 4;            // get real offset
  address = (UINT64)ssdt + dwOffset;  // get real address of function in ssdt
  KdPrint(("0x%llX\n", address));
  return address;
}


2.2 HOOK的手法
    知道这些以后,还不能好好的进行SSDT HOOK,具体原因,直接引用Tesla.Angla的话:
    “要知道,WIN64内核里每个驱动都不在同一个4GB里,而4字节的整数只能表示 4GB 的范围!所以无论你怎么修改这个值,都跳不出 ntoskrnl 的手掌心。如果你想通过修改这个值来跳转到你的代理函数,那是绝对不可能。 因为你的驱动地址不可能跟 ntoskrnl在同一个4GB里。虽然不能直接用4字节来表示自己的代理函数所在的地址, 但是还可以修改这个值。要知道在ntoskrnl有很多地方的代码通常是不会被执行的,比如 KeBugCheckEx 。所以我的办法是: 修改这个偏移地址的值,使之跳转到KeBugCheckEx ,然后在 KeBugCheckEx的头部写一个12字节的mov - jmp ,这是一个可以跨越 4GB的跳转,跳到我们函数里!”
贴上代码:

VOID initKeBugCheckEx()
{
  /*
    向KeBugCheckEx头部中写入的数据
    48 B8 xxxx    mov rax,XXXh;
    FF E0      jmp rax
  */
  UCHAR jmpCode[13] = "\x48\xB8\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xE0"; //12 data
  UINT64 proxyFunction;
  proxyFunction = (UINT64)proxyNtOpenProcess;  //自己的NtOpenProess
  RtlCopyMemory(jmpCode + 2, &proxyFunction, 8);
  wpOff();  // 关保护
  memset(KeBugCheckEx, 0x90, 15);  // 初始化15个字节为 nop
  RtlCopyMemory(KeBugCheckEx, jmpCode, 12); // mov rax,XXXh;jmp rax; nop; nop;
  wpOn();    // 写保护
  return;
}


三.SSDT HOOK
    现在,终于可以开始SSDT HOOK了,直接上代码再说明:
VOID ssdtHook(UINT index)
{
  PSERVICES_DESCRIPTOR_TABLE pKeServiceDescriptorTable = (PSERVICES_DESCRIPTOR_TABLE)getKeServiceDescirptorTable();
  PULONG pSsdt = (PULONG)pKeServiceDescriptorTable->ServiceTableBase;
  //保存原始SSDT中NtOpenProcess地址
  NtOpenProcessX = (NTOPENPROCESS)getSsdtFunctionAddress(35);
  initKeBugCheckEx();
  wpOff();
  pSsdt[index] = getOffset((UINT64)KeBugCheckEx);  // SSDT HOOK
  wpOn();
  return;
}

    其中,getOffset是对KeBugCheckEx进行取偏移,因为SSDT表保存的是4字节的偏移,而KeBugCheckEx这个函数的地址却是8字节的,所以需要算出其偏移地址。getOffset的算法是之前的getSsdtFunctionAddress函数的逆运算,具体实现看附件中代码,这里就不贴了。

四.SSDT UNHOOK
    SSDT UNHOOK是对SSDT HOOK进行卸载,其做法是SSDT HOOK的反向操作,我在这里偷了个懒,直接对ssdt表中的偏移做了保存,恢复的时候把保存的起源偏移地址全部还原回去...具体代码如下:

//DriverEntry中的代码
  // save origin ssdt offset address
  nCount = getFunctionCount();
  for (int i = 0; i < nCount; i++)
  {
    PSERVICES_DESCRIPTOR_TABLE p = (PSERVICES_DESCRIPTOR_TABLE)getKeServiceDescirptorTable();
    PULONG ssdt = (PULONG)p->ServiceTableBase;
    originAddress[i] = ssdt[i];
  }


// 其中的参数 index只是个摆设...
VOID ssdtUnhook(UINT index)
{
  int nCount = getFunctionCount();
  wpOff();
  for (int i = 0; i < nCount; i++)
  {
    PSERVICES_DESCRIPTOR_TABLE p = (PSERVICES_DESCRIPTOR_TABLE)getKeServiceDescirptorTable();
    PULONG ssdt = (PULONG)p->ServiceTableBase;
    ssdt[i] = originAddress[i];
  }
  wpOn();
}


    然后在DriverUnload中调用ssdtUnhook就好了。关于KeBugCheckEx,我也没做恢复处理...

五.总结
最后总结一下本文中所涉及到的64位下和32位下编程的区别:
    1.在64位下,SSDT表和Shadow SSDT表均未导出,而32位下,SSDT表导出,ShadowSSDT未导出。
    2.int、long、char、wchar_t等在64位下长度不变,但是指针变量统一变为8字节。
    3.SSDT中保存的不再是绝对地址,而只是相对于SSDT基址的偏移地址。
    4.最后一个没有提及的是,x64下不支持直接内联汇编,在必要时可以做成shellcode


关于32位下的SSDT及SSDT HOOK,推荐李马的《城里城外看SSDT》,感觉比较经典,然后可以看看论坛speeday的《新手学ssdt_hook》,介绍的也比较详细

x64SSDTHOOK.7z

[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

上传的附件:
收藏
点赞1
打赏
分享
最新回复 (64)
雪    币: 32
活跃值: (34)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
sarsky 2014-11-15 00:47
2
0
沙发留名
感谢大家啊,我转正了,非常感谢我们任老师A1Pass和b23526给的邀请码
也衷心希望大家也来15PB,我现在这里,这里真的很好。
雪    币: 29
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
一头毛虫 2014-11-15 01:16
3
0
不留名板櫈
雪    币: 55
活跃值: (519)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
hrpirip 1 2014-11-15 07:57
4
0
顶一个。
雪    币: 122
活跃值: (16)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
butian 2014-11-15 08:00
5
0
顶一个……
雪    币: 32
活跃值: (34)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
sarsky 2014-11-15 08:08
6
0
感觉没人理啊...唉,郁闷的啦,当时写得好辛苦的....
雪    币: 32
活跃值: (34)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
sarsky 2014-11-15 08:22
7
0
大牛,你kx好多啊,送我个号呗...i really want to become regular members
雪    币: 32
活跃值: (34)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
sarsky 2014-11-15 08:23
8
0
我擦,刚才竟突然打不出中文...........
雪    币: 52
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
轩辕追命 2014-11-15 17:18
9
0
感觉不错啊....为毛没加精啊......
雪    币: 478
活跃值: (50)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
cnxxm 2014-11-15 22:50
10
0
学习了啊。。
雪    币: 0
活跃值: (17)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
傲世孤尘 2014-11-15 23:46
11
0
好厉害,膜拜
雪    币: 219
活跃值: (738)
能力值: (RANK:290 )
在线值:
发帖
回帖
粉丝
viphack 4 2014-11-16 04:01
12
0
鼓励一下
雪    币: 32
活跃值: (34)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
sarsky 2014-11-16 08:07
13
0
求转正啊.....
雪    币: 310
活跃值: (1912)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
niuzuoquan 2014-11-16 08:14
14
0
mark
雪    币: 349
活跃值: (125)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
linso 1 2014-11-16 08:37
15
0
路过留名
雪    币: 114
活跃值: (135)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
qqlinhai 2014-11-16 09:28
16
0
MARK,谢谢分享。
雪    币: 4548
活跃值: (912)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
b23526 2014-11-16 09:32
17
0
不错的帖子,送你个邀请码
雪    币: 8
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
guyue三十五 2014-11-16 11:05
18
0
虽然不是很懂   但是也支持下啊
雪    币: 71
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mumaren 2014-11-16 11:11
19
0
不错

名师出高徒阿
雪    币: 1412
活跃值: (4133)
能力值: ( LV13,RANK:240 )
在线值:
发帖
回帖
粉丝
IamHuskar 4 2014-11-16 11:41
20
0
如果我是SSDT hook200个函数怎么办。
应当是从代码段的末尾寻找这样的跳转空间比较好
雪    币: 32
活跃值: (34)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
sarsky 2014-11-16 11:50
21
0
说得很好诶,我就是对ntoskrnl.exe文件不了解,没找到更好的hook点
雪    币: 78
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
一盏清茗 2014-11-16 14:18
22
0
虽然是完全参考了[www.vbasm.com]WIN64驱动编程基础教程,但还是鼓励下吧。
雪    币: 78
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
一盏清茗 2014-11-16 14:22
23
0
至于为什么是ssdt[nIndex]>>4而不是ssdt[nIndex],只能说通过逆向发现微软的做法就是这样的.....ssdt[nIndex]>>4才是实际偏移地址......(也是不知道该怎么解释了,希望遇到更明白的大牛告知)

IDA自己找KiSystemServiceStart。慢慢分析原理吧~
fffff800`03e73024 49c1fb04        sar     r11,4                注意这个逗比操作,右移4位,获取真正偏移
fffff800`03e73028 4d03d3          add     r10,r11  把偏移加到ssdt/shadow表的地址上,得到函数在内存中真正地址,u r10试试
雪    币: 294
活跃值: (119)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
zfdyq 1 2014-11-16 14:24
24
0
LZ是五期大牛啊~
雪    币: 32
活跃值: (34)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
sarsky 2014-11-16 15:30
25
0
[QUOTE=一盏清茗;1331228]至于为什么是ssdt[nIndex]>>4而不是ssdt[nIndex],只能说通过逆向发现微软的做法就是这样的.....ssdt[nIndex]>>4才是实际偏移地址......(也是不知道该怎么解释了,希望遇到更明白的大牛告知)

IDA自己找KiSystemServiceStart。慢慢分...[/QUOTE]

对诶,我就是说的这个操作,微软就是这么做的,只是有些不理解.....非常感谢你
游客
登录 | 注册 方可回帖
返回