这个表就是一个把 Ring3 的 Win32 API 和 Ring0 的内核 API 联系起来。Ring3下调用的所有函数最终都会先进入到ntdll里面的
typedef struct _KSERVICE_TABLE_DESCRIPTOR
{
PULONG_PTR ServiceTableBase;<span style="white-space: pre;"> //SSTD基地址
PULONG Count; //包含着 SSDT 中每个服务被调用次数的计数器。这个计数器一般由sysenter 更新
ULONG TableSize; //由 ServiceTableBase 描述的服务的数目
PUCHAR ArgumentTable; //包含每个系统服务参数字节数表的基地址-系统服务参数表
} KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;</span>
SSDT:KeServiceDescriptorTable
ShadowSSDT:KeServiceDescriptorTableShadow
extern KSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable[NUMBER_SERVICE_TABLES];
extern KSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTableShadow[NUMBER_SERVICE_TABLES];
KeServiceDescriptorTable SSDT 主要是处理来自 Ring3 层的Kernel32.dll 中的系统调用,比如函数 OpenProcess ReadFile 等函数。从kernel32.dll—>ntdll.dll—>系统中断进入内核 ntoskrml.exe
KeServiceDescriptorTableShadow 则主要处理来自 User32.dll 和 GDI32.dll 中的系统调用,比如常见的PostMessage,SendMessage,FindWindow。Win32k.sys
如何准确的判断系统用的是ntoskrnl.exe还是ntkrnlpa.exe文件呢:NtQuerySystemInformation,class 11 第一个模块就是,看名字就可以了
更简便的方式,直接:
<pre style="widows: 1;">kd> lm vm nt
start end module name
804d8000 806e5000 nt (pdb symbols) c:\mysymbol\winxp\ntkrpamp.pdb\7D6290E03E32455BB0E035E38816124F1\ntkrpamp.pdb
Loaded symbol image file: ntkrpamp.exe
Image path: ntkrpamp.exe
Image name: ntkrpamp.exe
ntkrpamp.exe只是内部名称,可以通过ntoskrnl.exe、ntkrnlpa.exe的属性–版本–内部名称看到!
直接用IDA查看ntkrpamp.exe
在Export中搜索keservic即可跳到:
双击:
发现这货就是一个全局变量!(更确切的说是变量数组,这个从WRK也可以看出)既然KeServiceDescriptorTable是一个导出的全局变量(数组),那么我们来看wrk,大家都知道在编写代码的时候,要导出一个函数,通常使用def文件。所以ntoskrnl在编写的时候,同样也用到了def来导出,我们翻看wrk
extern KSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable[NUMBER_SERVICE_TABLES];
extern KSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTableShadow[NUMBER_SERVICE_TABLES];
#define NUMBER_SERVICE_TABLES 2
SSTD只用了一张表(KeServiceDescriptorTable[0]),SSTDSHADOW有两张表,第一张和KeServiceDescriptorTable[0]相同,即SSDT,第二张才是SSTDSHADOW
在应用层 ntdll.dll 中的 API 在这个系统服务描述表(SSDT)中都存在一个与之相对应的服务号,要说明这个,可以再用IDA加载ring3的NTDLL.dll,随便选个函数,如NTCreateFile:
ntkrpamp.exe+xuetr:
0xB7=183为服务号
ntdll.dll:
0xB7=183为服务号,其余的一样,比如zwcreatefile为0×25
当我们的应用程序调用 ntdll.dll 中的 API 时,最终会调用内核中与之相对应的系统服务,由于有了 SSDT,所以我们只需要告诉内核需要调用的服务所在 SSDT 中的索引(即服务员)就 OK 了,然后内核根据这个索引值就可以在 SSDT 中找到相对应的服务了,然后再由内核调用服务完成应用程序 API 的调用请求即可
ring3下zw和nt是同一套函数的两个别名
ring0下zw只是做一个过渡分发,而nt才是真正的函数主体,可以看下zwcreatefile的实现:
kd> u nt!zwcreatefile
nt!ZwCreateFile:
80501010 b825000000 mov eax,25h
80501015 8d542404 lea edx,[esp+4]
80501019 9c pushfd
8050101a 6a08 push 8
8050101c e830140400 call nt!KiSystemService (80542451)
80501021 c22c00 ret 2Ch
它只是把系统服务员0×25放到eax,然后开始调用系统分发函数走到ntcreatfile中.调用堆栈如下:
kd> kb
ChildEBP RetAddr Args to Child
bacfbbe0 8054261c bacfbcf0 00100180 bacfbcd4 nt!NtCreateFile
bacfbbe0 80501021 bacfbcf0 00100180 bacfbcd4 nt!KiFastCallEntry+0xfc
bacfbc84 8061f69d bacfbcf0 00100180 bacfbcd4 nt!ZwCreateFile+0x11
最后用windbg来查看下SSDT:kd> x nt!*servicedes*
80553f60 nt!KeServiceDescriptorTableShadow = <no type information>
80553fa0 nt!KeServiceDescriptorTable = <no type information>
kd> dd 80553f60 L2
80553f60 80502b8c 00000000
kd> dds 80502b8c L2
80502b8c 8059a948 nt!NtAcceptConnectPort
80502b90 805e7db6 nt!NtAccessCheck
要查看SSTDSHADOW需要先切换到GUI进程空间内 :
kd> dd nt!KeServiceDescriptorTableShadow L8
80553f60 80502b8c 00000000 0000011c 80503000
80553f70 bf999b80 00000000 0000029b bf99a890
kd> dds bf999b80 L1// 未切换时
bf999b80 ????????
kd> !process 0 0 calc.exe
PROCESS 861a9020 SessionId: 0 Cid: 068c Peb: 7ffdb000 ParentCid: 05c8
DirBase: 0c9801a0 ObjectTable: e21cdd28 HandleCount: 46.
Image: calc.exe
kd> .process 861a9020
Implicit process is now 861a9020
WARNING: .cache forcedecodeuser is not enabled
kd> dds bf999b80 L3//win32k
bf999b80 bf935f7e win32k!NtGdiAbortDoc
bf999b84 bf947b29 win32k!NtGdiAbortPath
bf999b88 bf88ca52 win32k!NtGdiAddFontResourceW
最后遍历SSDT测试代码:
#include <ntddk.h>
typedef struct _KSERVICE_TABLE_DESCRIPTOR
{
PULONG_PTR ServiceTableBase;//SSTD基地址
PULONG Count; //包含着 SSDT 中每个服务被调用次数的计数器。这个计数器一般由sysenter 更新
ULONG TableSize; //由 ServiceTableBase 描述的服务的数目
PUCHAR ArgumentTable; //包含每个系统服务参数字节数表的基地址-系统服务参数表
} KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;
//ssdt表已经导出了,这里例行公事下
extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable;
//卸载函数
VOID DriverUnload(IN PDRIVER_OBJECT DriverObject)
{
DbgPrint("卸载完成!\n");
}
//入口函数
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegistryPath)
{
int i = 0;
DriverObject->DriverUnload = DriverUnload;
for (i=0;i<KeServiceDescriptorTable->TableSize;i++)
{
DbgPrint("Number:%d Address:0x%08X\r\n\r\n",i,KeServiceDescriptorTable->ServiceTableBase[i]);
}
return STATUS_SUCCESS;
}
对于shadowsstd:
1:shadowSSDT是在KeServiceDescriptorTableShadow的第二个表,也就是查看win32K系统服务,第一个表就是ssdt
2:如果我们要查看win32K系统服务,必须切换到GUI线程的上下文,不然win32k无法被加载
遍历SSDTSHADOW:
1.查找到KeServiceDescriptorShadowTable的地址,因为不是导出的,所以只能特征码查找(wrk可以确认哪个函数有它)
2.KeServiceDescriptorShadowTable[0]内容和KeServiceDescriptorTable相同,这可以做为比对标志
3.查找一个GUI进程,并附加上去:如explorer.exe
示例代码:
#include <ntifs.h>
typedef struct _KSERVICE_TABLE_DESCRIPTOR
{
PULONG_PTR ServiceTableBase;//SSTD基地址
PULONG Count; //包含着 SSDT 中每个服务被调用次数的计数器。这个计数器一般由sysenter 更新
ULONG TableSize; //由 ServiceTableBase 描述的服务的数目
PUCHAR ArgumentTable; //包含每个系统服务参数字节数表的基地址-系统服务参数表
} KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;
//ssdt表已经导出了,这里例行公事下
extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable;
PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorShadowTable;
PVOID GetShadowTableAddress()
{
UNICODE_STRING functionName;
ULONG i = 0;
ULONG DwordAtByte = 0;
PUCHAR p = NULL;
RtlInitUnicodeString(&functionName, L"KeAddSystemServiceTable");
p = MmGetSystemRoutineAddress(&functionName);
if (NULL == p)
{
return NULL;
}
// 开始循环找,找一页,指针递增1
for (i=0;i<0x1024;i++,p++)
{
try
{
DwordAtByte = *(PULONG)p;
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
return NULL;
}
if (MmIsAddressValid((PVOID)DwordAtByte))
{
if (0 == memcmp((PVOID)DwordAtByte, KeServiceDescriptorTable, 16))//对比前16字节 相同则找到
{
if ((PVOID)DwordAtByte == KeServiceDescriptorTable)//排除sstd
{
continue;
}
return DwordAtByte;
}
}
}
return NULL;
}
UCHAR *PsGetProcessImageFileName(__in PEPROCESS eprocess);//导出下使用.
NTSTATUS LookupProcessByName(IN PCHAR pcProcessName,
OUT PEPROCESS *pEprocess)
{
PEPROCESS pCurEprocess = NULL;
PEPROCESS pNextEprocess = NULL;//做为一个标记,表示循环了一圈
PLIST_ENTRY pListActiveProcess = NULL;
ULONG offset = 0;//ActiveProcessLinks的偏移值
ULONG uLoopNum = 0;//查找的循环次数
RTL_OSVERSIONINFOEXW osver = {sizeof(RTL_OSVERSIONINFOEXW)};
char *lpszAttackProName = NULL;
if (!ARGUMENT_PRESENT(pcProcessName)
||!ARGUMENT_PRESENT(pEprocess))
{
KdPrint(("[LookupProcessByName]--invalid para\n"));
return STATUS_INVALID_PARAMETER;
}
if (KeGetCurrentIrql()>PASSIVE_LEVEL)
{
KdPrint(("[LookupProcessByName]--invalid irql\n"));
return STATUS_UNSUCCESSFUL;
}
if (STATUS_SUCCESS != RtlGetVersion((PRTL_OSVERSIONINFOW)&osver))
{
KdPrint(("[LookupProcessByName]--RtlGetVersion fail\n"));
return STATUS_UNSUCCESSFUL;
}
// 仅对xp测试,自己扩展
if (5 == osver.dwMajorVersion
&&1 == osver.dwMinorVersion)
{
offset = 0x88;//可通过windbg查看eprocess中的偏移
}
if (0 == offset)
{
KdPrint(("[LookupProcessByName]--unknow os\n"));
return STATUS_UNSUCCESSFUL;
}
// 遍历链表查询
pCurEprocess = PsGetCurrentProcess();
pNextEprocess = pCurEprocess;
__try
{
while (TRUE)
{
// TODO.做想做的事吧...
lpszAttackProName = (char *)PsGetProcessImageFileName(pCurEprocess);
if (lpszAttackProName
&& strlen(lpszAttackProName) == strlen(pcProcessName))
{
if (0 == _stricmp(lpszAttackProName, pcProcessName))
{
KdPrint(("[LookupProcessByName]--find\n"));
*pEprocess = pCurEprocess;
return STATUS_SUCCESS;
}
}
//出口
if (uLoopNum>=1
&&pNextEprocess == pCurEprocess)
{
KdPrint(("[LookupProcessByName]--loop end\n"));
*pEprocess = 0x00000000;
return STATUS_NOT_FOUND;
}
pListActiveProcess = (PLIST_ENTRY)((ULONG)pCurEprocess+offset);//注意大括号,不用大括号会出错的
(ULONG)pCurEprocess = (ULONG)pListActiveProcess->Flink;//pCurEprocess临时表示了前一个Active process
(ULONG)pCurEprocess = (ULONG)pCurEprocess - offset;//对应的Eprocess基址
KdPrint(("[LookupProcessByName]--pCurEprocess:%08x\n", pCurEprocess));
uLoopNum ++;//循环次数+1
}
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
KdPrint(("[LookupProcessByName]--execption:%08x--end\n", GetExceptionCode()));
*pEprocess = 0x00000000;
return STATUS_NOT_FOUND;
}
}
//卸载函数
VOID DriverUnload(IN PDRIVER_OBJECT DriverObject)
{
DbgPrint("卸载完成!\n");
}
//入口函数
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegistryPath)
{
int i = 0;
PEPROCESS eprocess_explorer;
DriverObject->DriverUnload = DriverUnload;
KeServiceDescriptorShadowTable = GetShadowTableAddress();
if (KeServiceDescriptorShadowTable)
{
// 我们得到一个gui进程的对象,因为我们切换进程的时候需要用到
if (STATUS_SUCCESS == LookupProcessByName("explorer.exe",&eprocess_explorer) )
{
KeAttachProcess(eprocess_explorer);//附加到目标进程
//这里为什么要KeServiceDescriptorShadowTable[1],正如我们所说的,第二个表才是ShadowSSDT
for (i = 0;i<KeServiceDescriptorShadowTable[1].TableSize;i++)
{
KdPrint(("Number:%d Address:0x%08X\r\n",i,KeServiceDescriptorShadowTable[1].ServiceTableBase[i]));
}
KeDetachProcess();//解除附加
}
}
return STATUS_SUCCESS;
}