本文主要为学习各位大牛的技术,不敢擅称原创,见谅
SSDT HOOK最终是要修改KeServiceDescriptorTable的成员ServiceTableBase所指向内存空间的Nt××函数地址,那么我们就可以通过扫描这段内存空间就可以抓到hook的痕迹,唔,这就是我们anti-ssdt hook的基本思路,或者说scan为anti提供了可行性
最经典的,该是Greg Hoglund与James Butler合著的《Rootkits: Subverting the Windows Kernel》,唔这么好的书没人翻译太可惜了(据FC说有人翻译过,可惜我没有看到),不过也避免了恶劣翻译对经典的破坏,唉,矛于盾,就像我们的 ssdt hook与anti-ssdt hook一样
其具体步骤为(原理的演绎,不多说了,需要的看代码)获取ntoskrnl模块的内存地址范围,如果所检测的SDT中的函数地址不在这个范围,那就认为被hook了
下面相关原文:
Finding SSDT Hooks
The following DriverEntry function calls the GetListOfModules function and then walks each entry, looking for the one named ntoskrnl.exe. When it is found, a global variable containing the beginning and end addresses of that module is initialized. This information will be used to look for addresses in the SSDT that are outside of ntoskrnl.exe's range.
typedef struct _NTOSKRNL {
DWORD Base;
DWORD End;
} NTOSKRNL, *PNTOSKRNL;
PMODULE_LIST g_pml;
NTOSKRNL g_ntoskrnl;
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath)
{
int count;
g_pml = NULL;
g_ntoskrnl.Base = 0;
g_ntoskrnl.End = 0;
g_pml = GetListOfModules();
if (!g_pml)
return STATUS_UNSUCCESSFUL;
for (count = 0; count < g_pml->d_Modules; count++)
{
// Find the entry for ntoskrnl.exe.
if (_stricmp("ntoskrnl.exe", g_pml->a_Modules[count].a_bPath + g_pml-
>a_Modules[count].w_NameOffset) == 0)
{
g_ntoskrnl.Base = (DWORD)g_pml->a_Modules[count].p_Base;
g_ntoskrnl.End = ((DWORD)g_pml->a_Modules[count].p_Base + g_pml-
>a_Modules[count].d_Size);
}
}
ExFreePool(g_pml);
if (g_ntoskrnl.Base != 0)
return STATUS_SUCCESS;
else
return STATUS_UNSUCCESSFUL;
}
The following
function will print a debug message if it finds an SSDT address out of
acceptable range:#pragma pack(1)
typedef struct ServiceDescriptorEntry {
unsigned int *ServiceTableBase;
unsigned int *ServiceCounterTableBase;
unsigned int NumberOfServices;
unsigned char *ParamTableBase;
} SDTEntry_t;
#pragma pack()
// Import KeServiceDescriptorTable from ntoskrnl.exe.
__declspec(dllimport) SDTEntry_t KeServiceDescriptorTable;
void IdentifySSDTHooks(void)
{
int i;
for (i = 0; i < KeServiceDescriptorTable.NumberOfServices; i++)
{
if ((KeServiceDescriptorTable.ServiceTableBase <
g_ntoskrnl.Base) ||
(KeServiceDescriptorTable.ServiceTableBase >
g_ntoskrnl.End))
{
DbgPrint("System call %d is hooked at address %x!/n", i,
KeServiceDescriptorTable.ServiceTableBase);
}
}
}
Finding SSDT hooks is very powerful, but do not be surprised if you find a few that are not rootkits. Remember, a lot of protection software today also hooks the kernel and various APIs.
In the next section, you will learn how to detect certain inline function hooks, which are discussed in Chapter 4
Tan Chew Keong在他的’Win2K/XP SDT Restore’大作中的基本思路是不变的,但是,他的SDT是在ntoskrnl.exe的原始文件中加载的,然后使用这个SDT跟操作系统内核中正在使用的那个进行对比,这样更精确到位,而且可以对可疑的HOOK进行恢复。相关代码在网络上有提供。这种加载SDT的方法是90210在 rootkit.com中‘A more stable way to locate real KiServiceTable’提出来的。
Tan Chew Keong后来写了篇文章——《Win2K/XP SDT Restore》,基本思路仍如前篇所述,不过他的SDT在ntoskrnl.exe原始文件中加载,然用这个SDT与OS内核中那个对比,相较之更为精确,且未恢复HOOK提供了途径。
相关原文如下:
This POC code restores the values of the ServiceTable entries by writing directly to /device/physicalmemory. Hence, it works entirely in user-space and do not need to load a driver. The following steps describe how the code works.
1.Use NtOpenSection to get a handle to /device/physicalmemory with SECTION_MAP_READ | SECTION_MAP_WRITE access. If this fails, modify the DACL of /device/physicalmemory by adding SECTION_MAP_WRITE access permission to the current user. Try to open /device/physicalmemory again.
2.Load ntoskrnl.exe into memory with proper alignment and locate the address of KeServiceDescriptorTable from the export table of ntoskrnl.exe
3.Use NtMapViewOfSection to map in the physical memory page at the address of KeServiceDescriptorTable.
4.Get the address of KeServiceDescriptorTable.ServiceDescriptor[0].ServiceTable from the page.
5.Use NtMapViewOfSection to map in the physical memory page containing the running kernel's SerivceTable. This address is available at KeServiceDescriptorTable.ServiceDescriptor[0].ServiceTable.
6.Use the address of KeServiceDescriptorTable.ServiceDescriptor[0].ServiceTable to offset into the loaded ntoskrnl.exe
7.Loop through all entries in KeServiceDescriptorTable.ServiceDescriptor[0].ServiceTable, comparing the copy in the kernel memory with the copy in the loaded ntoskrnl.exe. Restore to kernel memory (i.e. into the mapped page) any discrepancies that are detected. This code works based on the fact that a complete original copy of the ServiceTable exists in ntoskrnl.exe.
具体code可在baidu到,由于思路相同,就不再赘述
如果你对这种load方法不理解的话,参看90210的“A more stable way to locate real KiServiceTable”
相关代码如下:
#include
#include
#include
#define RVATOVA(base,offset) ((PVOID)((DWORD)(base)+(DWORD)(offset)))
#define ibaseDD *(PDWORD)&ibase
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
#define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0)
typedef struct {
WORD offset:12;
WORD type:4;
} IMAGE_FIXUP_ENTRY, *PIMAGE_FIXUP_ENTRY;
typedef LONG NTSTATUS;
#ifdef __cplusplus
extern "C" {
#endif
NTSTATUS
WINAPI
NtQuerySystemInformation(
DWORD SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);
#ifdef __cplusplus
}
#endif
typedef struct _SYSTEM_MODULE_INFORMATION {//Information Class 11
ULONG Reserved[2];
PVOID Base;
ULONG Size;
ULONG Flags;
USHORT Index;
USHORT Unknown;
USHORT LoadCount;
USHORT ModuleNameOffset;
CHAR ImageName[256];
}SYSTEM_MODULE_INFORMATION,*PSYSTEM_MODULE_INFORMATION;
typedef struct {
DWORD dwNumberOfModules;
SYSTEM_MODULE_INFORMATION smi;
} MODULES, *PMODULES;
#define SystemModuleInformation 11
DWORD GetHeaders(PCHAR ibase,
PIMAGE_FILE_HEADER *pfh,
PIMAGE_OPTIONAL_HEADER *poh,
PIMAGE_SECTION_HEADER *psh)
{
PIMAGE_DOS_HEADER mzhead=(PIMAGE_DOS_HEADER)ibase;
if ((mzhead->e_magic!=IMAGE_DOS_SIGNATURE) ||
(ibaseDD[mzhead->e_lfanew]!=IMAGE_NT_SIGNATURE))
return FALSE;
*pfh=(PIMAGE_FILE_HEADER)&ibase[mzhead->e_lfanew];
if (((PIMAGE_NT_HEADERS)*pfh)->Signature!=IMAGE_NT_SIGNATURE)
return FALSE;
*pfh=(PIMAGE_FILE_HEADER)((PBYTE)*pfh+sizeof(IMAGE_NT_SIGNATURE));
*poh=(PIMAGE_OPTIONAL_HEADER)((PBYTE)*pfh+sizeof(IMAGE_FILE_HEADER));
if ((*poh)->Magic!=IMAGE_NT_OPTIONAL_HDR32_MAGIC)
return FALSE;
*psh=(PIMAGE_SECTION_HEADER)((PBYTE)*poh+sizeof(IMAGE_OPTIONAL_HEADER));
return TRUE;
}
DWORD FindKiServiceTable(HMODULE hModule,DWORD dwKSDT)
{
PIMAGE_FILE_HEADER pfh;
PIMAGE_OPTIONAL_HEADER poh;
PIMAGE_SECTION_HEADER psh;
PIMAGE_BASE_RELOCATION pbr;
PIMAGE_FIXUP_ENTRY pfe;
DWORD dwFixups=0,i,dwPointerRva,dwPointsToRva,dwKiServiceTable;
BOOL bFirstChunk;
GetHeaders((PBYTE)hModule,&pfh,&poh,&psh);
// loop thru relocs to speed up the search
if ((poh->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress) &&
(!((pfh->Characteristics)&IMAGE_FILE_RELOCS_STRIPPED))) {
pbr=(PIMAGE_BASE_RELOCATION)RVATOVA(poh->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress,hModule);
bFirstChunk=TRUE;
// 1st IMAGE_BASE_RELOCATION.VirtualAddress of ntoskrnl is 0
while (bFirstChunk || pbr->VirtualAddress) {
bFirstChunk=FALSE;
pfe=(PIMAGE_FIXUP_ENTRY)((DWORD)pbr+sizeof(IMAGE_BASE_RELOCATION));
for (i=0;i<(pbr->SizeOfBlock-sizeof(IMAGE_BASE_RELOCATION))>>1;i++,pfe++) {
if (pfe->type==IMAGE_REL_BASED_HIGHLOW) {
dwFixups++;
dwPointerRva=pbr->VirtualAddress+pfe->offset;
// DONT_RESOLVE_DLL_REFERENCES flag means relocs aren't fixed
dwPointsToRva=*(PDWORD)((DWORD)hModule+dwPointerRva)-(DWORD)poh->ImageBase;
// does this reloc point to KeServiceDescriptorTable.Base?
if (dwPointsToRva==dwKSDT) {
// check for mov [mem32],imm32. we are trying to find
// "mov ds:_KeServiceDescriptorTable.Base, offset _KiServiceTable"
// from the KiInitSystem.
if (*(PWORD)((DWORD)hModule+dwPointerRva-2)==0x05c7) {
// should check for a reloc presence on KiServiceTable here
// but forget it
dwKiServiceTable=*(PDWORD)((DWORD)hModule+dwPointerRva+4)-poh->ImageBase;
return dwKiServiceTable;
}
}
} else
if (pfe->type!=IMAGE_REL_BASED_ABSOLUTE)
// should never get here
printf("/trelo type %d found at .%X/n",pfe->type,pbr->VirtualAddress+pfe->offset);
}
*(PDWORD)&pbr+=pbr->SizeOfBlock;
}
}
if (!dwFixups)
// should never happen - nt, 2k, xp kernels have relocation data
printf("No fixups!/n");
return 0;
}
void main(int argc,char *argv[])
{
HMODULE hKernel;
DWORD dwKSDT; // rva of KeServiceDescriptorTable
DWORD dwKiServiceTable; // rva of KiServiceTable
PMODULES pModules=(PMODULES)&pModules;
DWORD dwNeededSize,rc;
DWORD dwKernelBase,dwServices=0;
PCHAR pKernelName;
PDWORD pService;
PIMAGE_FILE_HEADER pfh;
PIMAGE_OPTIONAL_HEADER poh;
PIMAGE_SECTION_HEADER psh;
// get system modules - ntoskrnl is always first there
rc=NtQuerySystemInformation(SystemModuleInformation,pModules,4,&dwNeededSize);
if (rc==STATUS_INFO_LENGTH_MISMATCH) {
pModules=GlobalAlloc(GPTR,dwNeededSize);
rc=NtQuerySystemInformation(SystemModuleInformation,pModules,dwNeededSize,NULL);
} else {
strange:
printf("strange NtQuerySystemInformation()!/n");
return;
}
if (!NT_SUCCESS(rc)) goto strange;
// imagebase
dwKernelBase=(DWORD)pModules->smi.Base;
// filename - it may be renamed in the boot.ini
pKernelName=pModules->smi.ModuleNameOffset+pModules->smi.ImageName;
// map ntoskrnl - hopefully it has relocs
hKernel=LoadLibraryEx(pKernelName,0,DONT_RESOLVE_DLL_REFERENCES);
if (!hKernel) {
printf("Failed to load! LastError=%i/n",GetLastError());
return;
}
GlobalFree(pModules);
// our own export walker is useless here - we have GetProcAddress :)
if (!(dwKSDT=(DWORD)GetProcAddress(hKernel,"KeServiceDescriptorTable"))) {
printf("Can't find KeServiceDescriptorTable/n");
return;
}
// get KeServiceDescriptorTable rva
dwKSDT-=(DWORD)hKernel;
// find KiServiceTable
if (!(dwKiServiceTable=FindKiServiceTable(hKernel,dwKSDT))) {
printf("Can't find KiServiceTable.../n");
return;
}
printf("&KiServiceTable==%08X/n/nDumping 'old' ServiceTable:/n/n",
dwKiServiceTable+dwKernelBase);
// let's dump KiServiceTable contents
// MAY FAIL!!!
// should get right ServiceLimit here, but this is trivial in the kernel mode
GetHeaders((PBYTE)hKernel,&pfh,&poh,&psh);
for (pService=(PDWORD)((DWORD)hKernel+dwKiServiceTable);
*pService-poh->ImageBase SizeOfImage;
pService++,dwServices++)
printf("%08X/n",*pService-poh->ImageBase+dwKernelBase);
printf("/n/nPossibly KiServiceLimit==%08X/n",dwServices);
FreeLibrary(hKernel);
}
参考资料:
《ROOTKITS: SUVVERING THE WINDOWS KERNEL》
by Greg Hoglund, James Butler
《Defeating Kernel Native API Hookers by Direct KiServiceTable Restoration》
by Tan Chew Keong
《Win2K/XP SDT Restore 0.2 (Proof-Of-Concept)》
同样 byTan Chew Keong
《A more stable way to locate real KiServiceTable》
by 90210