一、实战篇
本不想摘代码,既然实战,就不多讲废话了,还是贴上吧,谁都有违背原则的时候:)。
代码一:经典案例,替换NtQuerySystemInformation,列取所有查询到的进程名,我使用修改CR0寄存器的方法。
#include
typedef struct _SYSTEM_THREADS
{
LARGE_INTEGER KernelTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER CreateTime;
ULONG WaitTime;
PVOID StartAddress;
CLIENT_ID ClientIs;
KPRIORITY Priority;
KPRIORITY BasePriority;
ULONG ContextSwitchCount;
ULONG ThreadState;
KWAIT_REASON WaitReason;
}SYSTEM_THREADS, *PSYSTEM_THREADS;
typedef struct _SYSTEM_PROCESSES
{
ULONG NextEntryDelta;
ULONG ThreadCount;
ULONG Reserved[6];
LARGE_INTEGER CreateTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER KernelTime;
UNICODE_STRING ProcessName;
KPRIORITY BasePriority;
ULONG ProcessId;
ULONG InheritedFromProcessId;
ULONG HandleCount;
ULONG Reserved2[2];
VM_COUNTERS VmCounters;
IO_COUNTERS IoCounters;
struct _SYSTEM_THREADS Threads[1];
}SYSTEM_PROCESSES, *PSYSTEM_PROCESSES;
NTSYSAPI NTSTATUS NTAPI ZwQuerySystemInformation(
IN ULONG SystemInformationClass,
IN PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength);
NTSTATUS NewNtQuerySystemInformation(
IN ULONG SystemInformationClass,
IN PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength);
// SDT Structure
typedef struct _ServiceDescriptorTableEntry
{
unsigned int *ServiceTableBase;
unsigned int *ServiceCounterTableBase;
unsigned int NumberOfServices;
unsigned char *ParamTableBase;
}ServiceDescriptorTableEntry, *PServiceDescriptorTableEntry;
// import KeServiceDescriptorTable
extern PServiceDescriptorTableEntry KeServiceDescriptorTable;
PServiceDescriptorTableEntry pSDT;
// real NtQuerySystemInformation
typedef NTSTATUS (*NTQUERYSYSTEMINFORMATION)(
IN ULONG SystemInformationClass,
IN PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength);
NTQUERYSYSTEMINFORMATION OldNtQuerySystemInformation;
UNICODE_STRING usLinkDeviceNameString;
VOID UnloadDriver(IN PDRIVER_OBJECT pDriverObject)
{
ULONG _cr0;
PDEVICE_OBJECT pDeviceObject;
// recovery SSDT
_asm
{
// WP off
cli
mov eax, cr0
mov _cr0, eax
and eax, 0fffeffffh
mov cr0,eax
// recovery SSDT
mov ecx, DWORD PTR [ZwQuerySystemInformation]
mov edx, [ecx+1]
mov eax, DWORD PTR [pSDT];
mov esi, [eax]
mov ebx, DWORD PTR [OldNtQuerySystemInformation]
mov [esi+edx*4],ebx
// WP on
mov eax, _cr0
mov cr0, eax
sti
}
pDeviceObject= pDriverObject->DeviceObject;
IoDeleteSymbolicLink(&usLinkDeviceNameString);
ASSERT(!pDeviceObject->AttachedDevice);
if ( pDeviceObject != NULL )
{
IoDeleteDevice(pDeviceObject);
}
}
NTSTATUS DriverEntry (IN PDRIVER_OBJECT pDriverObject,IN PUNICODE_STRING usRegistryPath)
{
ULONG _cr0;
NTSTATUS ntStatus;
PDEVICE_OBJECT pDeviceObject;
UNICODE_STRING usDeviceNameString;
RtlInitUnicodeString(&usDeviceNameString, L"//Device//SSDTHOOK" );
RtlInitUnicodeString(&usLinkDeviceNameString, L"//DosDevices//SSDTHOOK" );
ntStatus = IoCreateDevice(
pDriverObject,
0,
&usDeviceNameString,
FILE_DEVICE_DISK_FILE_SYSTEM,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&pDeviceObject);
if (!NT_SUCCESS(ntStatus))
{
return ntStatus;
}
ntStatus = IoCreateSymbolicLink(&usLinkDeviceNameString,&usDeviceNameString);
if (!NT_SUCCESS(ntStatus))
{
IoDeleteDevice(pDeviceObject);
return ntStatus;
}
pDriverObject->DriverUnload=UnloadDriver;
pSDT = KeServiceDescriptorTable;
// modify SSDT
_asm
{
// WP off
cli
mov eax, cr0
mov _cr0, eax
and eax, 0fffeffffh
mov cr0, eax
// replace NtQuerySystemInformation with NewNtQuerySystemInformation
mov ecx, DWORD PTR [ZwQuerySystemInformation]
mov edx, [ecx+1]
mov eax, DWORD PTR [pSDT]
mov esi, [eax]
mov edx, [esi+edx*4]
mov DWORD PTR [OldNtQuerySystemInformation], edx
mov ecx, [ecx+1]
mov eax, [eax]
mov dword ptr [eax+ecx*4], offset NewNtQuerySystemInformation
// WP on
mov eax, _cr0
mov cr0, eax
sti
}
return ntStatus;
}
NTSTATUS NewNtQuerySystemInformation(
IN ULONG SystemInformationClass,
IN PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength)
{
ANSI_STRING asProcessName;
NTSTATUS ntQuerySystemInformation = (OldNtQuerySystemInformation)
(SystemInformationClass,
SystemInformation,
SystemInformationLength,
ReturnLength);
if(NT_SUCCESS(ntQuerySystemInformation))
{
if(5 == SystemInformationClass)
{
PSYSTEM_PROCESSES curr = (PSYSTEM_PROCESSES)SystemInformation;
PSYSTEM_PROCESSES prev = NULL;
if(curr->NextEntryDelta)((char *)curr += curr->NextEntryDelta);
while(curr)
{
RtlUnicodeStringToAnsiString(&asProcessName, &(curr->ProcessName), TRUE);
DbgPrint(asProcessName.Buffer);
RtlFreeAnsiString(&asProcessName);
if(curr != NULL)
{
prev = curr;
if(curr->NextEntryDelta)((char *)curr += curr->NextEntryDelta);
else curr = NULL;
}
}
}
}
return ntQuerySystemInformation;
}
案例二:修改SDT中NtCreateSection的地址,列出所有创建SECTION的文件名称,我使用了MDL。其实,搞进程很麻烦凡的,windows的大麻烦之一。
#include
NTSYSAPI
NTSTATUS
NTAPI ZwCreateSection(
OUT PHANDLE SectionHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN PLARGE_INTEGER MaximumSize OPTIONAL,
IN ULONG SectionPageProtection,
IN ULONG AllocationAttributes,
IN HANDLE FileHandle OPTIONAL);
NTSTATUS NewNtCreateSection(
OUT PHANDLE SectionHandle,
IN ULONG DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN PLARGE_INTEGER MaximumSize OPTIONAL,
IN ULONG PageAttributess,
IN ULONG SectionAttributes,
IN HANDLE FileHandle OPTIONAL);
// SDT Structure
#pragma pack(1)
typedef struct _ServiceDescriptorTableEntry
{
unsigned int *ServiceTableBase;
unsigned int *ServiceCounterTableBase;
unsigned int NumberOfServices;
unsigned char *ParamTableBase;
}ServiceDescriptorTableEntry, *PServiceDescriptorTableEntry;
#pragma pack()
__declspec(dllimport) ServiceDescriptorTableEntry KeServiceDescriptorTable;
#define SYSTEMSERVICE(_function) KeServiceDescriptorTable.ServiceTableBase[*(PULONG)((PUCHAR)_function+1)]
// real NtCreateSection
typedef NTSTATUS (*NTCREATESECTION)(
OUT PHANDLE SectionHandle,
IN ULONG DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN PLARGE_INTEGER MaximumSize OPTIONAL,
IN ULONG PageAttributess,
IN ULONG SectionAttributes,
IN HANDLE FileHandle OPTIONAL);
NTCREATESECTION OldNtCreateSection;
UNICODE_STRING usLinkDeviceNameString;
// MDL define
PMDL g_pmdlSystemCall;
PVOID *pMappedSystemCallTable;
#define SYSCALL_INDEX(_Function) *(PULONG)((PUCHAR)_Function+1)
#define HOOK_SYSCALL(_Function, _Hook, _Orig)/
_Orig = (PVOID) InterlockedExchange( (PLONG) &pMappedSystemCallTable[SYSCALL_INDEX(_Function)], (LONG) _Hook)
#define UNHOOK_SYSCALL(_Function, _Hook, _Orig)/
InterlockedExchange( (PLONG) &pMappedSystemCallTable[SYSCALL_INDEX(_Function)], (LONG) _Hook)
VOID UnloadDriver(IN PDRIVER_OBJECT pDriverObject)
{
PDEVICE_OBJECT pDeviceObject;
// recovery SSDT
UNHOOK_SYSCALL(ZwCreateSection, OldNtCreateSection, NewNtCreateSection);
// Unlock and Free MDL
if(g_pmdlSystemCall)
{
MmUnmapLockedPages(pMappedSystemCallTable, g_pmdlSystemCall);
IoFreeMdl(g_pmdlSystemCall);
}
pDeviceObject= pDriverObject->DeviceObject;
IoDeleteSymbolicLink(&usLinkDeviceNameString);
ASSERT(!pDeviceObject->AttachedDevice);
if ( pDeviceObject != NULL )
{
IoDeleteDevice(pDeviceObject);
}
}
NTSTATUS DriverEntry (IN PDRIVER_OBJECT pDriverObject,IN PUNICODE_STRING usRegistryPath)
{
NTSTATUS ntStatus;
PDEVICE_OBJECT pDeviceObject;
UNICODE_STRING usDeviceNameString;
RtlInitUnicodeString(&usDeviceNameString, L"//Device//SSDTHOOK" );
RtlInitUnicodeString(&usLinkDeviceNameString, L"//DosDevices//SSDTHOOK" );
ntStatus = IoCreateDevice(
pDriverObject,
0,
&usDeviceNameString,
FILE_DEVICE_DISK_FILE_SYSTEM,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&pDeviceObject);
if (!NT_SUCCESS(ntStatus))
{
return ntStatus;
}
ntStatus = IoCreateSymbolicLink(&usLinkDeviceNameString,&usDeviceNameString);
if (!NT_SUCCESS(ntStatus))
{
IoDeleteDevice(pDeviceObject);
return ntStatus;
}
pDriverObject->DriverUnload=UnloadDriver;
OldNtCreateSection =(NTCREATESECTION)(SYSTEMSERVICE(ZwCreateSection));
g_pmdlSystemCall = MmCreateMdl(NULL, KeServiceDescriptorTable.ServiceTableBase, KeServiceDescriptorTable.NumberOfServices*4);
if(!g_pmdlSystemCall) return STATUS_UNSUCCESSFUL;
MmBuildMdlForNonPagedPool(g_pmdlSystemCall);
// Change the flags of the MDL
g_pmdlSystemCall->MdlFlags = g_pmdlSystemCall->MdlFlags | MDL_MAPPED_TO_SYSTEM_VA;
pMappedSystemCallTable = MmMapLockedPages(g_pmdlSystemCall, KernelMode);
// hook system calls
HOOK_SYSCALL(ZwCreateSection, NewNtCreateSection, OldNtCreateSection);
return ntStatus;
}
NTSTATUS NewNtCreateSection(
OUT PHANDLE SectionHandle,
IN ULONG DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN PLARGE_INTEGER MaximumSize OPTIONAL,
IN ULONG PageAttributess,
IN ULONG SectionAttributes,
IN HANDLE FileHandle OPTIONAL)
{
HANDLE hHandle;
PFILE_OBJECT pFileObject;
OBJECT_HANDLE_INFORMATION HandleInformationObject;
ANSI_STRING asFileName;
hHandle=(HANDLE)FileHandle;
ObReferenceObjectByHandle(hHandle,0,0,KernelMode,&pFileObject,&HandleInformationObject);
if(pFileObject != NULL)
{
RtlUnicodeStringToAnsiString(&asFileName,&(pFileObject->FileName),TRUE);
DbgPrint(asFileName.Buffer);
RtlFreeAnsiString(&asFileName);
}
return ((NTCREATESECTION)(OldNtCreateSection))(SectionHandle, DesiredAccess, ObjectAttributes, MaximumSize, PageAttributess, SectionAttributes, FileHandle);
}
以上两个例子都是在XP SP2下测试,用来验证第一部分我说的那些废话。代码本身没任何破坏性,有兴趣可以尝试,如有疑问欢迎交流。
二、Anti-SSDT Hook
小学时,课本有一篇寓言《自相矛盾》,当时觉得不可思议,像现在想来,矛与盾的较量永未停止。对SSDT Hook最终都要修改掉KeServiceDescriptorTable的成员ServiceTableBase所指向的内存空间的Nt*系列函数地址,那么只要扫描这段内存空间,抓出非法居民即可。原理是这样的。
先看看‘ROOTKITS: SUVVERING THE WINDOWS KERNEL’的高招。首先,获取ntoskrnl.exe模块的内存地址范围,如果所检测的SDT中的函数地址不在这个范围内就是被HOOK了。不再重复人家的代码了。
Tan Chew Keong在他的’Win2K/XP SDT Restore’大作中的基本思路是不变的,但是,他的SDT是在ntoskrnl.exe的原始文件中加载的,然后使用这个SDT跟操作系统内核中正在使用的那个进行对比,这样更精确到位,而且可以对可疑的HOOK进行恢复。相关代码在网络上有提供。这种加载SDT的方法是90210在rootkit.com中‘A more stable way to locate real KiServiceTable’提出来的。
其他的方法我在师还没有注意到,应该是有的,如果发现新方法,欢迎交流。
三、参考资料
1. ‘ROOTKITS: SUVVERING THE WINDOWS KERNEL’, Greg Hoglund, James Butler
2. ‘Defeating Kernel Native API Hookers by Direct KiServiceTable Restoration’, Tan Chew Keong
3. ‘Win2K/XP SDT Restore 0.2 (Proof-Of-Concept)’, Tan Chew Keong
4. ‘SDT Hooking 무력화에 대한 연구’, Dual5651
‘A more stable way to locate real KiServiceTable’, 90210