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

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

2014-11-15 00:46
15306

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;
}



[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

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

名师出高徒阿
2014-11-16 11:11
0
雪    币: 1372
活跃值: (5667)
能力值: ( LV13,RANK:240 )
在线值:
发帖
回帖
粉丝
20
如果我是SSDT hook200个函数怎么办。
应当是从代码段的末尾寻找这样的跳转空间比较好
2014-11-16 11:41
0
雪    币: 32
活跃值: (34)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
21
说得很好诶,我就是对ntoskrnl.exe文件不了解,没找到更好的hook点
2014-11-16 11:50
0
雪    币: 78
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
虽然是完全参考了[cb3K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6i4k6T1j5i4y4E0i4K6u0W2j5$3!0E0i4K6g2p5g2@1W2z5y4U0c8Q4c8e0W2Q4b7e0W2Q4b7U0q4Q4c8e0g2Q4z5p5q4Q4b7e0S2Q4c8e0N6Q4b7V1y4Q4z5e0k6Q4c8e0N6Q4b7e0S2Q4z5p5u0Q4c8e0g2Q4z5f1k6Q4b7V1q4Q4c8e0N6Q4b7e0q4Q4z5o6m8Q4c8e0k6Q4z5e0g2Q4z5e0W2Q4c8e0N6Q4b7e0S2Q4z5p5u0Q4c8f1k6Q4b7V1y4Q4z5p5y4Q4c8e0c8Q4b7V1c8Q4z5o6k6Q4c8e0S2Q4b7V1k6Q4z5e0S2Q4c8e0k6Q4z5e0S2Q4b7f1k6Q4c8e0W2Q4b7V1y4Q4z5e0y4Q4c8e0g2Q4z5p5q4Q4b7U0q4Q4c8e0c8Q4b7U0S2Q4z5p5u0Q4c8e0g2Q4z5e0m8Q4b7e0N6Q4c8e0y4Q4z5o6m8Q4z5o6t1`.
2014-11-16 14:18
0
雪    币: 78
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
至于为什么是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试试
2014-11-16 14:22
0
雪    币: 294
活跃值: (119)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
24
LZ是五期大牛啊~
2014-11-16 14:24
0
雪    币: 32
活跃值: (34)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
25
[QUOTE=一盏清茗;1331228]至于为什么是ssdt[nIndex]>>4而不是ssdt[nIndex],只能说通过逆向发现微软的做法就是这样的.....ssdt[nIndex]>>4才是实际偏移地址......(也是不知道该怎么解释了,希望遇到更明白的大牛告知)

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

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