<span style="font-family:SimSun;font-size:12px;">typedef struct _KSERVICE_TABLE_DESCRIPTOR { PULONG_PTR ServiceTableBase;<span style="white-space: pre;"> </span>//SSTD基地址 PULONG Count; //包含着 SSDT 中每个服务被调用次数的计数器。这个计数器一般由sysenter 更新 ULONG TableSize; //由 ServiceTableBase 描述的服务的数目 PUCHAR ArgumentTable; <span style="white-space: pre;"> </span>//包含每个系统服务参数字节数表的基地址-系统服务参数表 } 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:
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
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
#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; }