首发于debugwar.com,授权转载。
本文是这篇求助帖子的后续,可能有人会问, 都2202年了, x64占主流的情况下为什么还在研究SSDT相关的东西。原因其实很简单,别人有我也要有 /doge。
不废话,请看正文。
--------------下面是正文,而我是分割线--------------
两张表使用了同一数据结构,只是名字不同而已,具体结构C语言表示如下:
其中_KSERVICE_TABLE_DESCRIPTOR对应nt!KeServiceDescriptorTable和nt!KeServiceDescriptorTableShadow。而nt!KiServiceTable一般指向_KSYSTEM_SERVICE_TABLE的首地址,即ServiceTableBase。
这么说还是太抽象了,来看几个实际的例子。
由于我们要查看ShadowSSDT,因此上面先用.process切换到了fffffa80060a7b30这个进程上下文(有SessionID的程序),不然看到的nt!KeServiceDescriptorTableShadow可能不是有效数据。
上文中,我们根据表结构,已经可以得到ServiceTableBase地址了,那么如何获取这个Base下各个函数的实际地址呢?
如果此时你是x64的环境,而且你迫不及待的去看ServiceTableBase处内容的话,你会发现和真实的函数地址对不上,产生这个现象的原因本文后面会提到。现在让我们先从最简单的情况开始讨论。
x86环境下情况最为简单,你会发现ServiceTableBase处的地址就是对应函数的地址,无论是静态看还是动态看:
调试器中观察相同位置的数值及符号如下:
由于基址不同,以NtAccessCheck函数为例。静态环境的值为0x00471b6c, 加载在基址0x00400000处。动态环境的值为0x820d5b6c,加载在基址0x82064000处。
两个环境的值分别换算成RVA,可知:
虽然静态与动态环境下KiServiceTable具体的值不一样(这是由于重定位造成的),但是通过上述RVA计算可知,其内存处的值是可以对上的。最终我们可以得出结论,x86下 KiServiceTable直接存放的就是目标函数地址。
x64下微软做了调整,静态环境下,在低版本内核中(Windows7、8、8.1)和x86一样直接存放函数地址。而高版本内核(Windows 10以上),KiServiceTable中将不再直接存放函数地址,而且数据长度也由原来的4字节变成了8字节。
调整较大的是动态环境,当用调试器进行内核调试时,会发现无论高版本还是低版本,KiServiceTable处的值无论如何也和静态文件中的不一样,这主要是因为内核会对KiServiceTable处的值做“压缩”,压缩的动作由nt!KeCompactServiceTable函数完成的,后文中我们会有讨论。
下面我们分开看一下高低版本各环境的情况。
下图是Windows 7 x64下KiServiceTable的截图,可见依然是存储的函数地址,此时的加载基址为0x00000001`40000000。
然后通过调试器观察系统跑起来后KiServiceTable处的动态值:
如果此时我们尝试用上一节中的方法将动静态地址分别转换成RVA,会发现无论如何也得不出相等的数值,这也就是本节开头所说:由于动态地址处的值经过nt!KeCompactServiceTable函数的处理,因此已经不能计算出相同的RVA。
如果此时你打开高版本内核(比如Windows 10 x64),定位到KiServiceTable处,你就会发现事情并不简单。此时的IDA甚至不能“正确”的识别SSDT表中的函数,而且数据上看上去也是每4 bytes为一组,和低版本中每8 bytes为一组的情况大相径庭:
此处我就不截调试状态下KiServiceTable地址处的值了,你只需要知道和低版本的情况一致:也无法对的上,即可。
那么,我们如何得出正确函数的地址呢?
上文中说到x64下静态与动态KiServiceTable处数值无法对应的问题,导致这一问题的最终原因是系统在启动阶段使用nt!KeCompactServiceTable函数对该地址处的值进行了“压缩”处理,接下来我们看一下Windows 7 x64该函数的反编译代码:
将上述逻辑更精炼一点描述如下:
需要注意的一点是:在Windows 10 x64及以上的系统版本中, 有2处重要差别:
具体的KeCompactServiceTable反汇编代码我就不贴了,有兴趣的可以逆一下。总之逆向完成之后,上面代码的第13行在Windows 10 x64下应该为(可能有轻微不一样,但是计算结果应该是等价的):
首先是总体的公式,x64系统下,无论高低,所有版本SSDT每个表项对应函数的计算公式都为:
FunctionAddress = HIWORD(KiServiceTableBase)<<32 + (UHALF_PTR)((UHALF_PTR)KiServiceTableBase + (UHALF_PTR)FunctionCookie)
注意UHALF_PTR在x64下是4字节长度,计算的时候别把高位也带上了。下面我们以Windows 7 x64和Windows 10 x64为例来手动算一下地址。
首先是Windows 7 x64, 回收一张上文中的截图:
按照上面的计算公式(注意数据类型是64位下的UHALF_PTR,也就是4个字节):
结果即为NtMapUserPhysicalPagesScatter函数的地址。
再来看一下Windows 10 x64下的计算,继续回收上文截图:
这里需要注意,Windows 10 x64上的每个表项变为了4个字节而不是Windows 7 x64上的8个字节:
我们看一下Windows 10 x64下0x1400e54b4地址处是什么:
上图中左右两个函数的高位地址不同,是因为真正在内存中加载的内核经过了地址重定位的原因。
其实知道了真正地址的计算方法,只需要按照如下思路计算出原始地址处的值,然后再和现内核同一偏移处的值比较,值不一样即被Hook过:
这里就不上具体代码了, 给一份验证过后的截图:
- KiServiceTableBase = 0x40071B00
- FunctionCookie = 0x40482190 - 0x40071B00 = 0x410690
- FunctionAddress = 0x1`00000000 + 0x40071B00 + 0x410690 = 0x140482190
- KiServiceTableBase = 0x402EE800
- FunctionCookie = 0x000E54B4 - 0x402EE800 + 0x40000000 = 0xFFDF6CB4
- FunctionAddress = 0x1`00000000 + 0x402EE800 + 0xFFDF6CB4 = 0x1400e54b4
- typedef struct _KSYSTEM_SERVICE_TABLE {
- PUCHAR ServiceTableBase;
- PULONG Count;
- ULONG_PTR TableSize;
- PUCHAR ArgumentTable;
- } KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE;
-
- typedef struct _KSERVICE_TABLE_DESCRIPTOR
- {
- KSYSTEM_SERVICE_TABLE ntoskrnl;
- KSYSTEM_SERVICE_TABLE win32k;
- KSYSTEM_SERVICE_TABLE notUsed1;
- KSYSTEM_SERVICE_TABLE notUsed2;
- } KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;
- kd> .process /i fffffa80060a7b30
- You need to continue execution (press 'g' <enter>) for the context
- to be switched. When the debugger breaks in again, you will be in
- the new process context.
- kd> g
- Break instruction exception - code 80000003 (first chance)
- nt!RtlpBreakWithStatusInstruction:
- fffff800`03cd1490 cc int 3
- kd> !process fffffa80060a7b30 0
- PROCESS fffffa80060a7b30
- SessionId: 0 Cid: 0138 Peb: 7fffffde000 ParentCid: 0130
- DirBase: 9e3fa000 ObjectTable: fffff8a006353010 HandleCount: 408.
- Image: csrss.exe
-
- kd> dps nt!KeServiceDescriptorTable L10
- // typedef struct _KSERVICE_TABLE_DESCRIPTOR {
- fffff800`03f0a840 fffff800`03cda300 nt!KiServiceTable // KSYSTEM_SERVICE_TABLE ntoskrnl.ServiceTableBase;
- fffff800`03f0a848 00000000`00000000 // KSYSTEM_SERVICE_TABLE ntoskrnl.Count;
- fffff800`03f0a850 00000000`00000191 // KSYSTEM_SERVICE_TABLE ntoskrnl.TableSize;
- fffff800`03f0a858 fffff800`03cdaf8c nt!KiArgumentTable // KSYSTEM_SERVICE_TABLE ntoskrnl.ArgumentTable;
- fffff800`03f0a860 00000000`00000000 // KSYSTEM_SERVICE_TABLE win32k.ServiceTableBase;
- fffff800`03f0a868 00000000`00000000 // KSYSTEM_SERVICE_TABLE win32k.Count;
- fffff800`03f0a870 00000000`00000000 // KSYSTEM_SERVICE_TABLE win32k.TableSize;
- fffff800`03f0a878 00000000`00000000 // KSYSTEM_SERVICE_TABLE win32k.ArgumentTable;
- fffff800`03f0a880 fffff800`03cda300 nt!KiServiceTable // KSYSTEM_SERVICE_TABLE notUsed1.ServiceTableBase;
- fffff800`03f0a888 00000000`00000000 // KSYSTEM_SERVICE_TABLE notUsed1.Count;
- fffff800`03f0a890 00000000`00000191 // KSYSTEM_SERVICE_TABLE notUsed1.TableSize;
- fffff800`03f0a898 fffff800`03cdaf8c nt!KiArgumentTable // KSYSTEM_SERVICE_TABLE notUsed1.ArgumentTable;
- fffff800`03f0a8a0 fffff960`00161f00 // KSYSTEM_SERVICE_TABLE notUsed2.ServiceTableBase;
- fffff800`03f0a8a8 00000000`00000000 // KSYSTEM_SERVICE_TABLE notUsed2.Count;
- fffff800`03f0a8b0 00000000`0000033b // KSYSTEM_SERVICE_TABLE notUsed2.TableSize;
- fffff800`03f0a8b8 fffff960`00163c1c // KSYSTEM_SERVICE_TABLE notUsed2.ArgumentTable;
- // } KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;
-
- kd> dps nt!KeServiceDescriptorTableShadow L10
- // typedef struct _KSERVICE_TABLE_DESCRIPTOR {
- fffff800`03f0a880 fffff800`03cda300 nt!KiServiceTable // KSYSTEM_SERVICE_TABLE ntoskrnl.ServiceTableBase;
- fffff800`03f0a888 00000000`00000000 // KSYSTEM_SERVICE_TABLE ntoskrnl.Count;
- fffff800`03f0a890 00000000`00000191 // KSYSTEM_SERVICE_TABLE ntoskrnl.TableSize;
- fffff800`03f0a898 fffff800`03cdaf8c nt!KiArgumentTable // KSYSTEM_SERVICE_TABLE ntoskrnl.ArgumentTable;
- fffff800`03f0a8a0 fffff960`00161f00 // KSYSTEM_SERVICE_TABLE win32k.ServiceTableBase;
- fffff800`03f0a8a8 00000000`00000000 // KSYSTEM_SERVICE_TABLE win32k.Count;
- fffff800`03f0a8b0 00000000`0000033b // KSYSTEM_SERVICE_TABLE win32k.TableSize;
- fffff800`03f0a8b8 fffff960`00163c1c // KSYSTEM_SERVICE_TABLE win32k.ArgumentTable;
- fffff800`03f0a8c0 00000000`77c51206 // KSYSTEM_SERVICE_TABLE notUsed1.ServiceTableBase;
- fffff800`03f0a8c8 00000000`00000000 // KSYSTEM_SERVICE_TABLE notUsed1.Count;
- fffff800`03f0a8d0 fffff800`00a06428 // KSYSTEM_SERVICE_TABLE notUsed1.TableSize;
- fffff800`03f0a8d8 fffff800`00a063d8 // KSYSTEM_SERVICE_TABLE notUsed1.ArgumentTable;
- fffff800`03f0a8e0 00000000`00000002 // KSYSTEM_SERVICE_TABLE notUsed2.ServiceTableBase;
- fffff800`03f0a8e8 00000000`00008600 // KSYSTEM_SERVICE_TABLE notUsed2.Count;
- fffff800`03f0a8f0 00000000`000d79f0 // KSYSTEM_SERVICE_TABLE notUsed2.TableSize;
- fffff800`03f0a8f8 00000000`00000000 // KSYSTEM_SERVICE_TABLE notUsed2.ArgumentTable;
- //
- 0x00471b6c - 0x00400000 = 0x820d5b6c - 0x08206400 = 0x71b6c
- unsigned int __fastcall KeCompactServiceTable(
- char *KiServiceTablePtr,
- char *ArgumentTable,
- unsigned int limit,
- boolean a4)
- {
- size_t v5; // r10
- DWORD KiServiceTableBase; // ebx
- char *PKiServiceTablePtr; // rdx
- __int64 v8; // r11
- DWORD ValueOfServiceTable; // er8
- unsigned int result; // eax
-
- v5 = limit;
- KiServiceTableBase = (unsigned int)KiServiceTablePtr;
- PKiServiceTablePtr = KiServiceTablePtr;
- if ( limit )
- {
- v8 = limit;
- do
- {
- ValueOfServiceTable = *(_DWORD *)PKiServiceTablePtr;
- PKiServiceTablePtr += 8;
- result = (unsigned __int8)*ArgumentTable++ >> 2;
- *(_DWORD *)KiServiceTablePtr = result | (16 * (ValueOfServiceTable - KiServiceTableBase));
- KiServiceTablePtr += 4;
- --v8;
- }
- while ( v8 );
- }
- if ( a4 == 1 )
- return (unsigned int)memmove(KiServiceTablePtr, PKiServiceTablePtr, v5);
- return result;
- }
- ULONG_PTR Index = 0, TableSize = 0, ServiceTableBase = 0, ArgumentTable = 0;
-
- TableSize = ServiceDescriptorTable->ntoskrnl.TableSize;
- ServiceTableBase = ServiceDescriptorTable->ntoskrnl.ServiceTableBase;
- ArgumentTable = ServiceDescriptorTable->ntoskrnl.ArgumentTable;
-
- for (Index = 0; Index < TableSize; ++Index)
- {
- UHALF_PTR FunctionCookie = 0;
- PUHALF_PTR Pointer = ServiceTableBase + Index * 4;
- UINT8 ArgumentCookie = *(PUCHAR)(ArgumentTable + Index);
-
- FunctionCookie = (UHALF_PTR)*(PUHALF_PTR)(ServiceTableBase + Index * 8) - (UHALF_PTR)ServiceTableBase;
- *Pointer = (16 * FunctionCookie) | (ArgumentCookie >> 2);
- }
- 原KiServiceTable处的数据由8字节长度变成了4字节
- 引入了内核加载基址参与计算
-
FunctionCookie = (UHALF_PTR)*(PUHALF_PTR)(ServiceTableBase + Index * 4) \
- - (UHALF_PTR)ServiceTableBase + (UHALF_PTR)NewImageBase;
- 重载一份内核
- 按照nt!KeCompactServiceTable函数的逻辑,修正第1步中KiServiceTable各表项的值
- 将重载内核的KiServiceTable各表项与现内核同偏移处的值比较
- 不一样的即为被Hook过的函数
- Windows 11 SSDT & ShadowSSDT地址获取问题
typedef struct _KSYSTEM_SERVICE_TABLE {
PUCHAR ServiceTableBase;
PULONG Count;
ULONG_PTR TableSize;
PUCHAR ArgumentTable;
} KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE;
typedef struct _KSERVICE_TABLE_DESCRIPTOR
{
KSYSTEM_SERVICE_TABLE ntoskrnl;
KSYSTEM_SERVICE_TABLE win32k;
KSYSTEM_SERVICE_TABLE notUsed1;
KSYSTEM_SERVICE_TABLE notUsed2;
} KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;
kd> .process /i fffffa80060a7b30
You need to continue execution (press 'g' <enter>) for the context
to be switched. When the debugger breaks in again, you will be in
the new process context.
kd> g
Break instruction exception - code 80000003 (first chance)
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2022-11-23 13:49
被Hacksign编辑
,原因: