#include <ntifs.h>
#include <windef.h>
/*
好久不玩SSDT了,因为微软不建议,甚至抵触,特别是在64位系统上。
但是有时候,知道原理对于分析问题和解决功能还是有好处的,
所以有此文。
SSDT再分析。
首先这是系统的一个机制。
其次是32位的系统导出了一个变量。
0: kd> x nt!_KeServiceDescriptorTable
8055d700 nt!KeServiceDescriptorTable = <no type information>
这个变量是啥类型呢?
REGMON的一个版本的代码,声明如下:
typedef struct _SRVTABLE {
PVOID *ServiceTable;
ULONG LowCall;
ULONG HiCall;
PVOID *ArgTable;
} SRVTABLE, *PSRVTABLE;
不过我们可以这样看:
0: kd> dps 8055d700 L 8
8055d700 80505570 nt!KiServiceTable
8055d704 00000000
8055d708 0000011c
8055d70c 805059e4 nt!KiArgumentTable
8055d710 00000000
8055d714 00000000
8055d718 00000000
8055d71c 00000000
再下就是这样看:
0: kd> dps 80505570 L 11c
80505570 805a5664 nt!NtAcceptConnectPort
80505574 805f23ea nt!NtAccessCheck
80505578 805f5c20 nt!NtAccessCheckAndAuditAlarm
8050557c 805f241c nt!NtAccessCheckByType
80505580 805f5c5a nt!NtAccessCheckByTypeAndAuditAlarm
80505584 805f2452 nt!NtAccessCheckByTypeResultList
80505588 805f5c9e nt!NtAccessCheckByTypeResultListAndAuditAlarm
8050558c 805f5ce2 nt!NtAccessCheckByTypeResultListAndAuditAlarmByHandle
80505590 80616e80 nt!NtAddAtom
...
再下就是本文了。
功能:枚举SSDT。有的是分析PE文件的,觉得不太好。
made by correy
made at 2014.11.22
再看看一个宏:
#define SYSCALL_INDEX(_Function) (*(PULONG)((PUCHAR)_Function+1))
这是求一个函数的INDEX,输入是函数名/函数地址,不过这个函数特殊必须是SSDT。
0: kd> u nt!ZwQuerySystemInformation
nt!ZwQuerySystemInformation:
80501b0c b8ad000000 mov eax,0ADh
80501b11 8d542404 lea edx,[esp+4]
80501b15 9c pushfd
80501b16 6a08 push 8
80501b18 e8f40a0400 call nt!KiSystemService (80542611)
80501b1d c21000 ret 10h
nt!ZwQuerySystemTime:
80501b20 b8ae000000 mov eax,0AEh
80501b25 8d542404 lea edx,[esp+4]
也就是说函数的地址前移一个字节就是函数INDEX。这个宏的定义是一样的。
made at 2015.03.13
*/
#define TAG 'tset' //test
#if defined(_WIN64)
//啥也没有。
#else
//这里的内容摘自:REGMON的某个版本的代码。
// Definition for system call service table
typedef struct _SRVTABLE {
PVOID *ServiceTable;
ULONG LowCall;
ULONG HiCall;
PVOID *ArgTable;
} SRVTABLE, *PSRVTABLE;
extern PSRVTABLE KeServiceDescriptorTable;// Pointer to the image of the system service table
// Macro for easy hook/unhook.
// On X86 implementations of Zw* functions, the DWORD following the first byte is the system call number, so we reach into the Zw function passed as a parameter, and pull the number out.
// This makes system call hooking dependent ONLY on the Zw* function implementation not changing.
//#if defined(_ALPHA_)
//#define SYSCALL(_function) KeServiceDescriptorTable->ServiceTable[ (*(PULONG)_function) & 0x0000FFFF ]
//#else
//#define SYSCALL(_function) KeServiceDescriptorTable->ServiceTable[ *(PULONG)((PUCHAR)_function+1)]
//#endif
#endif
DRIVER_UNLOAD Unload;
VOID Unload(__in PDRIVER_OBJECT DriverObject)
{
}
VOID enumerate_SSDT()
{
ULONG i = 0;
for ( ; i < KeServiceDescriptorTable->HiCall ;i++ )
{
KdPrint(("第%d个函数的地址是:0x%p.\n", i, KeServiceDescriptorTable->ServiceTable[i]));
/*
这里要做的还有更多,如参数的个数,函数的名字,函数所在的模块(区分是不是被劫持)。
根据函数的名字找到ID等。
*/
}
}
DRIVER_INITIALIZE DriverEntry;
NTSTATUS DriverEntry( __in struct _DRIVER_OBJECT * DriverObject, __in PUNICODE_STRING RegistryPath)
{
KdBreakPoint();
DriverObject->DriverUnload = Unload;
enumerate_SSDT();
return 0;
}
-------------------------------------------------------------------------------------------------------------------------
#include <ntifs.h>
#include <windef.h>
#include <Aux_klib.h> //source里面要有TARGETLIBS=$(DDK_LIB_PATH)\Aux_klib.lib
/*
made by correy
made at 2015.04.11
*/
#define TAG 'tset' //test
#pragma pack(1)
typedef struct ServiceDescriptorEntry{
unsigned int *ServiceTableBase;
unsigned int *ServiceCounterTableBase; //Used only in checked build
unsigned int NumberOfServices;
unsigned char *ParamTableBase;
} ServiceDescriptorTableEntry_t, *PServiceDescriptorTableEntry_t;
#pragma pack()
extern __declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable;
#define SYSCALL_INDEX(_Function) (*(PULONG)((PUCHAR)_Function+1)) //获取SSDT函数的ID,X64没有测试和观察。
#define SYSTEMSERVICE(index) KeServiceDescriptorTable.ServiceTableBase[index]
DRIVER_UNLOAD Unload;
VOID Unload(__in PDRIVER_OBJECT DriverObject)
{
}
PVOID RtlImageDirectoryEntryToData (IN PVOID Base, IN BOOLEAN MappedAsImage, IN USHORT DirectoryEntry, OUT PULONG Size);
PVOID MiFindExportedRoutineByName (IN PVOID DllBase, IN PANSI_STRING AnsiImageRoutineName)
/*++
Routine Description:
This function searches the argument module looking for the requested exported function name.
Arguments:
DllBase - Supplies the base address of the requested module.
AnsiImageRoutineName - Supplies the ANSI routine name being searched for.
Return Value:
The virtual address of the requested routine or NULL if not found.
--*/
{
USHORT OrdinalNumber;
PULONG NameTableBase;
PUSHORT NameOrdinalTableBase;
PULONG Addr;
LONG High;
LONG Low;
LONG Middle;
LONG Result;
ULONG ExportSize;
PVOID FunctionAddress = 0;
PIMAGE_EXPORT_DIRECTORY ExportDirectory;
PAGED_CODE();
__try
{
FunctionAddress = *(PVOID *)DllBase;
FunctionAddress = 0;
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
return FunctionAddress;
}
//确保DllBase可以访问。否则蓝屏。
ExportDirectory = (PIMAGE_EXPORT_DIRECTORY) RtlImageDirectoryEntryToData (DllBase, TRUE, IMAGE_DIRECTORY_ENTRY_EXPORT, &ExportSize);
if (ExportDirectory == NULL) {
return NULL;
}
NameTableBase = (PULONG)((PCHAR)DllBase + (ULONG)ExportDirectory->AddressOfNames);// Initialize the pointer to the array of RVA-based ansi export strings.
NameOrdinalTableBase = (PUSHORT)((PCHAR)DllBase + (ULONG)ExportDirectory->AddressOfNameOrdinals);// Initialize the pointer to the array of USHORT ordinal numbers.
Low = 0;
Middle = 0;
High = ExportDirectory->NumberOfNames - 1;
while (High >= Low) // Lookup the desired name in the name table using a binary search.
{
Middle = (Low + High) >> 1;// Compute the next probe index and compare the import name with the export name entry.
Result = strcmp (AnsiImageRoutineName->Buffer, (PCHAR)DllBase + NameTableBase[Middle]);
if (Result < 0) {
High = Middle - 1;
} else if (Result > 0) {
Low = Middle + 1;
} else {
break;
}
}
// If the high index is less than the low index, then a matching table entry was not found.
// Otherwise, get the ordinal number from the ordinal table.
if (High < Low) {
return NULL;
}
OrdinalNumber = NameOrdinalTableBase[Middle];
// If the OrdinalNumber is not within the Export Address Table,then this image does not implement the function.
// Return not found.
if ((ULONG)OrdinalNumber >= ExportDirectory->NumberOfFunctions) {
return NULL;
}
// Index into the array of RVA export addresses by ordinal number.
Addr = (PULONG)((PCHAR)DllBase + (ULONG)ExportDirectory->AddressOfFunctions);
FunctionAddress = (PVOID)((PCHAR)DllBase + Addr[OrdinalNumber]);
// Forwarders are not used by the kernel and HAL to each other.
ASSERT ((FunctionAddress <= (PVOID)ExportDirectory) || (FunctionAddress >= (PVOID)((PCHAR)ExportDirectory + ExportSize)));
return FunctionAddress;
}
PVOID get_module_image_base(__in UCHAR * name)
{
NTSTATUS status = 0;
ULONG modulesSize;
AUX_MODULE_EXTENDED_INFO * modules;
ULONG numberOfModules;
ULONG i;
PIMAGE_EXPORT_DIRECTORY pied = 0;
PVOID ImageBase = 0;
status = AuxKlibInitialize();
if (!NT_SUCCESS( status ))
{
KdPrint(( "AuxKlibInitialize fail %d\n", status));
return ImageBase;
}
status = AuxKlibQueryModuleInformation(&modulesSize, sizeof(AUX_MODULE_EXTENDED_INFO), NULL);// Get the required array size.
if (!NT_SUCCESS(status) || modulesSize == 0) {
return ImageBase;
}
numberOfModules = modulesSize / sizeof(AUX_MODULE_EXTENDED_INFO);// Calculate the number of modules.
modules = (AUX_MODULE_EXTENDED_INFO*) ExAllocatePoolWithTag(PagedPool, modulesSize, TAG);// Allocate memory to receive data.
if (modules == NULL) {
status = STATUS_INSUFFICIENT_RESOURCES;
return ImageBase;
}
RtlZeroMemory(modules, modulesSize);
status = AuxKlibQueryModuleInformation(&modulesSize, sizeof(AUX_MODULE_EXTENDED_INFO), modules);// Obtain the module information.
if (!NT_SUCCESS(status)) {
ExFreePoolWithTag(modules,TAG);
return ImageBase;
}
for (i = 0;i<numberOfModules;i++)
{
UCHAR * pfilename = modules[i].FullPathName + modules[i].FileNameOffset;
if (_stricmp(pfilename, name) == 0)
{
ImageBase = modules[i].BasicInfo.ImageBase;
break;
}
}
ExFreePoolWithTag(modules,TAG);
return ImageBase;
}
ULONG get_ssdt_function_index(IN char * function_name)
/*
功能:获取SSDT函数的调用号。
这里支持内核没有导出的SSDT的函数,即ntoskrnl.exe没有导出,而ntdll.dll导出的,在SSDT里有的函数。
*/
{
PVOID ImageBase = 0;
PVOID FunctionAddress;
ANSI_STRING test = RTL_CONSTANT_STRING(function_name);
ImageBase = get_module_image_base("ntdll.dll");
if (ImageBase == 0)
{
return -1;
}
FunctionAddress = MiFindExportedRoutineByName (ImageBase, &test);
if (FunctionAddress == 0)
{
return -1;
}
return SYSCALL_INDEX(FunctionAddress);
}
BOOL get_function_name_by_ssdt_index (int index, OUT PANSI_STRING AnsiImageRoutineName)
/*++
--*/
{
PULONG NameTableBase;
PUSHORT NameOrdinalTableBase;
ULONG Ordinals = 0;
ULONG ExportSize;
PIMAGE_EXPORT_DIRECTORY ExportDirectory;
PVOID DllBase = 0;
BOOL B = FALSE ;
PAGED_CODE();
if (AnsiImageRoutineName == NULL || AnsiImageRoutineName->Buffer == NULL)
{
return FALSE;
}
DllBase = get_module_image_base("ntdll.dll");
if (DllBase == 0)
{
return FALSE;
}
//确保DllBase可以访问。否则蓝屏。
ExportDirectory = (PIMAGE_EXPORT_DIRECTORY) RtlImageDirectoryEntryToData (DllBase, TRUE, IMAGE_DIRECTORY_ENTRY_EXPORT, &ExportSize);
if (ExportDirectory == NULL) {
return FALSE;
}
NameTableBase = (PULONG)((PCHAR)DllBase + (ULONG)ExportDirectory->AddressOfNames);// Initialize the pointer to the array of RVA-based ansi export strings.
NameOrdinalTableBase = (PUSHORT)((PCHAR)DllBase + (ULONG)ExportDirectory->AddressOfNameOrdinals);// Initialize the pointer to the array of USHORT ordinal numbers.
while (Ordinals < ExportDirectory->NumberOfNames) // Lookup the desired name in the name table using a binary search.
{
PCHAR RoutineName = (PCHAR)DllBase + NameTableBase[Ordinals];
//如果是Nt开头的函数。
if (_strnicmp(RoutineName, "nt", 2) == 0)// || _strnicmp(RoutineName, "zw", 2) == 0
{
USHORT OrdinalNumber = NameOrdinalTableBase[Ordinals];
// Index into the array of RVA export addresses by ordinal number.
PULONG Addr = (PULONG)((PCHAR)DllBase + (ULONG)ExportDirectory->AddressOfFunctions);
PVOID FunctionAddress = (PVOID)((PCHAR)DllBase + Addr[OrdinalNumber]);
//这个函数的地址的第二个字节的INT等于index,就退出,并返回返函数的名字。
if (SYSCALL_INDEX(FunctionAddress) == index)
{
RtlCopyMemory(AnsiImageRoutineName->Buffer, RoutineName, strlen(RoutineName));
//KdPrint(("function_name:%s.\n", RoutineName));
B = TRUE;
break;
}
}
Ordinals++;
}
return B;
}
VOID enumerate_SSDT_32()
{
ULONG i = 0;
for ( ; i < KeServiceDescriptorTable.NumberOfServices ;i++ )
{
CHAR RoutineName[MAX_PATH] = {0};
ANSI_STRING AnsiImageRoutineName = {0};
AnsiImageRoutineName.Buffer = RoutineName;
AnsiImageRoutineName.MaximumLength = MAX_PATH;
get_function_name_by_ssdt_index (i, &AnsiImageRoutineName);
RtlInitAnsiString(&AnsiImageRoutineName, AnsiImageRoutineName.Buffer);
KdPrint(("index:%d, address:0x%p, name:%Z.\n", i, KeServiceDescriptorTable.ServiceTableBase[i], &AnsiImageRoutineName));
/*
这里要做的还有更多,如参数的个数,函数的名字,函数所在的模块,原始的地址。
*/
}
}
DRIVER_INITIALIZE DriverEntry;
NTSTATUS DriverEntry( __in struct _DRIVER_OBJECT * DriverObject, __in PUNICODE_STRING RegistryPath)
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
ULONG index = 0;
PVOID p_NtCreateMutant = 0;
KdBreakPoint();
DriverObject->DriverUnload = Unload;
//index = get_ssdt_function_index("NtCreateMutant");
//p_NtCreateMutant = (PVOID)SYSTEMSERVICE(index);//注意这个是Nt*的函数。
enumerate_SSDT_32();
return status;
}
/*
0: kd> !process 0 0 explorer.exe
PROCESS 81d4d588 SessionId: 0 Cid: 0658 Peb: 7ffde000 ParentCid: 0604
DirBase: 02940200 ObjectTable: e149da90 HandleCount: 379.
Image: explorer.exe
0: kd> .process /r /p 81d4d588
Implicit process is now 81d4d588
.cache forcedecodeuser done
Loading User Symbols
................................................................
........................
0: kd> u ntdll!ntcreatefile
ntdll!ZwCreateFile:
7c92d0ae b825000000 mov eax,25h
7c92d0b3 ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300)
7c92d0b8 ff12 call dword ptr [edx]
7c92d0ba c22c00 ret 2Ch
*/
/*
1: kd> uf nt!Zwcreatefile
Flow analysis was incomplete, some code may be missing
nt!ZwCreateFile:
fffff800`018d8680 488bc4 mov rax,rsp
fffff800`018d8683 fa cli
fffff800`018d8684 4883ec10 sub rsp,10h
fffff800`018d8688 50 push rax
fffff800`018d8689 9c pushfq
fffff800`018d868a 6a10 push 10h
fffff800`018d868c 488d05dd270000 lea rax,[nt!KiServiceLinkage (fffff800`018dae70)]
fffff800`018d8693 50 push rax
fffff800`018d8694 b852000000 mov eax,52h 注意这个。
fffff800`018d8699 e9225f0000 jmp nt!KiServiceInternal (fffff800`018de5c0)
nt!KiServiceInternal:
fffff800`018de5c0 4883ec08 sub rsp,8
fffff800`018de5c4 55 push rbp
fffff800`018de5c5 4881ec58010000 sub rsp,158h
fffff800`018de5cc 488dac2480000000 lea rbp,[rsp+80h]
fffff800`018de5d4 48899dc0000000 mov qword ptr [rbp+0C0h],rbx
fffff800`018de5db 4889bdc8000000 mov qword ptr [rbp+0C8h],rdi
fffff800`018de5e2 4889b5d0000000 mov qword ptr [rbp+0D0h],rsi
fffff800`018de5e9 fb sti
fffff800`018de5ea 65488b1c2588010000 mov rbx,qword ptr gs:[188h]
fffff800`018de5f3 0f0d8bd8010000 prefetchw [rbx+1D8h]
fffff800`018de5fa 0fb6bbf6010000 movzx edi,byte ptr [rbx+1F6h]
fffff800`018de601 40887da8 mov byte ptr [rbp-58h],dil
fffff800`018de605 c683f601000000 mov byte ptr [rbx+1F6h],0
fffff800`018de60c 4c8b93d8010000 mov r10,qword ptr [rbx+1D8h]
fffff800`018de613 4c8995b8000000 mov qword ptr [rbp+0B8h],r10
fffff800`018de61a 4c8d1d3d010000 lea r11,[nt!KiSystemServiceStart (fffff800`018de75e)]
fffff800`018de621 41ffe3 jmp r11
1: kd> !process 0 0 explorer.exe
PROCESS fffffa801ad91910
SessionId: 1 Cid: 06a4 Peb: 7fffffdd000 ParentCid: 0680
DirBase: 66ef5000 ObjectTable: fffff8a0011323b0 HandleCount: 755.
Image: explorer.exe
1: kd> .process /p /r fffffa801ad91910
Implicit process is now fffffa80`1ad91910
.cache forcedecodeuser done
Loading User Symbols
................................................................
................................................................
...........
1: kd> uf ntdll!NtCreateFile
ntdll!ZwCreateFile:
00000000`77051860 4c8bd1 mov r10,rcx
00000000`77051863 b852000000 mov eax,52h 注意这个
00000000`77051868 0f05 syscall
00000000`7705186a c3 ret
知道64位SSDT的结构及地址,根据上面的示例不难写出X64的相应的例子。
*/
没有评论:
发表评论