重载内核的相关文章实在是太多了,鉴于还是有很多初学者研究这一块,本文仅作为一个引导作用,文笔不好,见谅。
我的博客:http://blog.csdn.net/sidyhe
开发环境:VS2010 + WinDDK
测试环境:VirtualDDK + VMware + Win7 sp1 x86
第一部分链接:http://bbs.pediy.com/showthread.php?t=187863
第二部分链接:http://bbs.pediy.com/showthread.php?t=187919
小结与修正
目前已经实现了一份简单的重载内核代码,但是如果你也跟着我实现了此部分,会发现此代码根本不能使用,甚至不能拿到本机来测试,是的,新的内核还是有大量的问题,驱动加载后会导致一些程序打不开,但是并不蓝屏,有意思的现象。
写上一篇文章的时候没有测试的那么完善,但是我拿出我之前写的重载内核代码,并没有上述问题,仔细分析代码并回忆,问题还是出在了重定位以及需要额外的处理。那么关于修复重定位的部分就需要重新写了,把修复过程分为两部分,第一次全部重定位到新模块上,第二次有选择的重定位到原模块上,为什么需要那么麻烦?这就涉及到了原始地址的获取方式问题,原始地址都存储在一个叫KiServiceTable的变量中,详见WRK。为什么要获取原始地址?因为当驱动加载的时候你无法确定当前的SSDT表是否被HOOK,所以在第一次修复重定位之后去找KiServiceTable,然后再进行第二次重定位的修复(我无法保证第二次修复不会破坏KiServiceTable中的地址)。
重新写一份代码,当然大部分还是从原来的代码复制过来,这样有利于逻辑上的思考,先不进行HOOK,把重载部分先理顺清楚,重定位修复代码修改为这个样子:
PVOID ReloadNtModule(PKLDR_DATA_TABLE_ENTRY PsLoadedModuleList)
{
PVOID lpImageAddress = NULL;
PKLDR_DATA_TABLE_ENTRY NtLdr = (PKLDR_DATA_TABLE_ENTRY)PsLoadedModuleList->InLoadOrderLinks.Flink;
PVOID lpFileBuffer;
DbgPrint("Nt Module File is %wZ\n", &NtLdr->FullDllName);
if (lpFileBuffer = KeGetFileBuffer(&NtLdr->FullDllName))
{
PIMAGE_DOS_HEADER lpDosHeader = (PIMAGE_DOS_HEADER)lpFileBuffer;
PIMAGE_NT_HEADERS lpNtHeader = (PIMAGE_NT_HEADERS)((PCHAR)lpDosHeader + lpDosHeader->e_lfanew);
if (lpImageAddress = ExAllocatePool(NonPagedPool, lpNtHeader->OptionalHeader.SizeOfImage))
{
PUCHAR lpImageBytes = (PUCHAR)lpImageAddress;
IMAGE_SECTION_HEADER *lpSection = IMAGE_FIRST_SECTION(lpNtHeader);
ULONG i;
RtlZeroMemory(lpImageAddress, lpNtHeader->OptionalHeader.SizeOfImage);
RtlCopyMemory(lpImageBytes, lpFileBuffer, lpNtHeader->OptionalHeader.SizeOfHeaders);
for (i = 0; i < lpNtHeader->FileHeader.NumberOfSections; i++)
{
RtlCopyMemory(lpImageBytes + lpSection[i].VirtualAddress, (PCHAR)lpFileBuffer + lpSection[i].PointerToRawData, lpSection[i].SizeOfRawData);
}
if (KeFixIAT(PsLoadedModuleList, lpImageAddress))
{
KeFixReloc1(lpImageAddress, NtLdr->DllBase);
}
else
{
ExFreePool(lpImageAddress);
lpImageAddress = NULL;
}
}
ExFreePool(lpFileBuffer);
}
if (lpImageAddress) DbgPrint("ImageAddress:0x%p\n", lpImageAddress);
return lpImageAddress;
}
VOID KeFixReloc1(PVOID ImageBaseAddress)
{
IMAGE_DOS_HEADER *lpDosHeader = (IMAGE_DOS_HEADER*)ImageBaseAddress;
IMAGE_NT_HEADERS *lpNtHeader = (IMAGE_NT_HEADERS *)((PCHAR)lpDosHeader + lpDosHeader->e_lfanew);
IMAGE_BASE_RELOCATION *lpRelocateTable = (IMAGE_BASE_RELOCATION*)((PCHAR)ImageBaseAddress + lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
ULONG_PTR DifferOffset = (ULONG_PTR)ImageBaseAddress - lpNtHeader->OptionalHeader.ImageBase;
while (lpRelocateTable->SizeOfBlock)
{
ULONG NumberOfItems = (lpRelocateTable->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(USHORT);
USHORT *lpItem = (USHORT*)((PCHAR)lpRelocateTable + sizeof(IMAGE_BASE_RELOCATION));
ULONG i;
for (i = 0; i < NumberOfItems; i++)
{
switch (lpItem[i] >> 12)
{
case IMAGE_REL_BASED_HIGHLOW:
{
ULONG_PTR *lpFixAddress = (ULONG_PTR *)((PCHAR)ImageBaseAddress + lpRelocateTable->VirtualAddress + (lpItem[i] & 0x0FFF));
*lpFixAddress += DifferOffset;
}
break;
case IMAGE_REL_BASED_ABSOLUTE://do nothing
break;
default:
DbgPrint("KeFixReloc1:Found unknown type(%X).\n", (lpItem[i] >> 12));
break;
}
}
lpRelocateTable = (IMAGE_BASE_RELOCATION *)((PCHAR)lpRelocateTable + lpRelocateTable->SizeOfBlock);
}
lpNtHeader->OptionalHeader.ImageBase = (ULONG)ImageBaseAddress;
return;
}
VOID KeFixReloc2(PVOID New, PVOID Old)
{
IMAGE_DOS_HEADER *lpDosHeader = (IMAGE_DOS_HEADER*)New;
IMAGE_NT_HEADERS *lpNtHeader = (IMAGE_NT_HEADERS *)((PCHAR)lpDosHeader + lpDosHeader->e_lfanew);
IMAGE_BASE_RELOCATION *lpRelocateTable = (IMAGE_BASE_RELOCATION*)((PCHAR)New + lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
while (lpRelocateTable->SizeOfBlock)
{
ULONG NumberOfItems = (lpRelocateTable->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(USHORT);
USHORT *lpItem = (USHORT*)((PCHAR)lpRelocateTable + sizeof(IMAGE_BASE_RELOCATION));
ULONG i;
for (i = 0; i < NumberOfItems; i++)
{
switch (lpItem[i] >> 12)
{
case IMAGE_REL_BASED_HIGHLOW:
{
PVOID lpFixAddress = (PCHAR)New + lpRelocateTable->VirtualAddress + (lpItem[i] & 0x0FFF);
KeFixRelocEx(New, Old, lpFixAddress);
}
break;
case IMAGE_REL_BASED_ABSOLUTE://do nothing
break;
default:
break;
}
}
lpRelocateTable = (IMAGE_BASE_RELOCATION *)((PCHAR)lpRelocateTable + lpRelocateTable->SizeOfBlock);
}
return;
}
重点还是KeFixRelocEx,重定位修正的是“地址的值”,希望大家能看懂,那么我就在这里做了相对比较多的判断。
“地址”可执行,“值”可执行。
“地址”可执行,“值”不可执行。指向原模块。如访问全局变量。
“地址”不可执行,“值”可执行。不修改。如函数表。
“地址”不可执行,“值”不可执行。想不出来是什么,指向原模块吧。
对于第一点,我原本是指向新模块,但测试了好半天,最后还是没有解决重载后某些程序打不开的问题。我曾尝试解析汇编,但字节组合方式太多了,我无法做出最正确的判断,既然我找不到是哪一类重定位存在问题,那么我就做没有问题的地方,如IAT,这种处理很像权限访问中的解决方式(1.我能干什么。2.我不能干什么)。最后代码变成这个样子:
BOOLEAN KeIsExecutable(PVOID ImageBase, PVOID Address)
{
IMAGE_DOS_HEADER *lpDosHeader = (IMAGE_DOS_HEADER*)ImageBase;
IMAGE_NT_HEADERS *lpNtHeader = (IMAGE_NT_HEADERS *)((PCHAR)lpDosHeader + lpDosHeader->e_lfanew);
IMAGE_SECTION_HEADER *lpSecHdr = IMAGE_FIRST_SECTION(lpNtHeader);
ULONG_PTR Rva = (ULONG_PTR)Address - (ULONG_PTR)ImageBase;
USHORT i;
for (i = 0; i < lpNtHeader->FileHeader.NumberOfSections; i++)
{
if (Rva >= lpSecHdr[i].VirtualAddress && Rva < lpSecHdr[i].VirtualAddress + lpSecHdr[i].SizeOfRawData)
{
return ((lpSecHdr[i].Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0);
}
}
return FALSE;
}
VOID KeFixRelocEx(PVOID New, PVOID Old, PVOID FixAddress)
{
if (KeIsExecutable(New, FixAddress))
{
if (KeIsExecutable(New, *(PVOID*)FixAddress))
{
if (KeIsIAT(New, *(PVOID*)FixAddress))
{
NOTHING;
}
else
{
*(ULONG_PTR*)FixAddress = *(ULONG_PTR*)FixAddress - (ULONG_PTR)New + (ULONG_PTR)Old;
}
}
else
{
*(ULONG_PTR*)FixAddress = *(ULONG_PTR*)FixAddress - (ULONG_PTR)New + (ULONG_PTR)Old;
}
}
else
{
if (KeIsExecutable(New, *(PVOID*)FixAddress))
{
NOTHING;
}
else
{
*(ULONG_PTR*)FixAddress = *(ULONG_PTR*)FixAddress - (ULONG_PTR)New + (ULONG_PTR)Old;
}
}
return;
}
啰嗦了那么多,非常不完美的解决了某些程序打不开的问题,如果有兴趣的朋友可以继续深入。但回头想想重载仅仅是为了防止HOOK而带来的麻烦,不妨假设HOOK没有那么猥琐,如果你发现重载内核不能绕过某些HOOK,再对症下药吧。
第三部分:获取原始地址
重头戏来啦!一开始说过原始SSDT函数都存储在一个叫做KiServiceTable的变量里,这个变量同样没有导出,利用字节码搜索的方式效率很低且未必找的准确,所以另求出路。网上已经有大牛研究出了一种方式,那就是利用重定位来查找。
首先来看WRK是如何做的:(KiInitSystem)
KeServiceDescriptorTable[0].Base = &KiServiceTable[0];
KeServiceDescriptorTable[0].Count = NULL;
KeServiceDescriptorTable[0].Limit = KiServiceLimit;
KeServiceDescriptorTable[0].Number = KiArgumentTable;
for (Index = 1; Index < NUMBER_SERVICE_TABLES; Index += 1) {
KeServiceDescriptorTable[Index].Limit = 0;
}
//
// Copy the system service descriptor table to the shadow table
// which is used to record the Win32 system services.
//
RtlCopyMemory(KeServiceDescriptorTableShadow,
KeServiceDescriptorTable,
sizeof(KeServiceDescriptorTable));
再来对比Windows 7 x86中是如何做的,IDA分析如下:
Ntoskrnl.exe的期望基址是00400000,所以第一句的RVA就是00395C12,来看一下重定位信息:
可以发现一个特点,有两个连续的重定位00395C14与00396C18,而第一个RVA就是KeServiceDescriptorTable,第二个就是KiServiceTable了,结合IDA对附近代码的综合判断,最后的代码就是这样:
PVOID FindKiServiceTable(PVOID lpNtoskrnlAddress)
{
PVOID lpKeServiceDescriptorTable = KeGetProcAddress(lpNtoskrnlAddress, "KeServiceDescriptorTable");
IMAGE_DOS_HEADER *lpDosHeader = (IMAGE_DOS_HEADER*)lpNtoskrnlAddress;
IMAGE_NT_HEADERS *lpNtHeader = (IMAGE_NT_HEADERS *)((PCHAR)lpDosHeader + lpDosHeader->e_lfanew);
IMAGE_BASE_RELOCATION *lpRelocateTable = (IMAGE_BASE_RELOCATION*)((PCHAR)lpNtoskrnlAddress + lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
while (lpRelocateTable->SizeOfBlock)
{
ULONG NumberOfItems = (lpRelocateTable->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(USHORT);
USHORT *lpItem = (USHORT*)((PCHAR)lpRelocateTable + sizeof(IMAGE_BASE_RELOCATION));
ULONG j;
for (j = 0; j < NumberOfItems - 1; j++)
{
if ((lpItem[j] >> 12) == IMAGE_REL_BASED_HIGHLOW && (lpItem[j + 1] >> 12) == IMAGE_REL_BASED_HIGHLOW)
{
ULONG *lpFixAddress1 = (ULONG*)((PCHAR)lpNtoskrnlAddress + lpRelocateTable->VirtualAddress + (lpItem[j] & 0x0FFF));
ULONG *lpFixAddress2 = (ULONG*)((PCHAR)lpNtoskrnlAddress + lpRelocateTable->VirtualAddress + (lpItem[j + 1] & 0x0FFF));
//两个连续的重定位
if ((ULONG)lpFixAddress2 - (ULONG)lpFixAddress1 == sizeof(ULONG))
{
//MOV DWORD PTR DS:[KeServiceDescriptorTable], XXX
if (*(USHORT*)((PCHAR)lpFixAddress1 - sizeof(USHORT)) == 0x05C7)
{
//DbgPrint("lpFixAddress1:%08X\n", (ULONG)lpFixAddress1 - 2);
if (*lpFixAddress1 == (ULONG)lpKeServiceDescriptorTable)
{
return (PVOID)*lpFixAddress2;
}
}
}
}
}
lpRelocateTable = (IMAGE_BASE_RELOCATION *)((PCHAR)lpRelocateTable + lpRelocateTable->SizeOfBlock);
}
return NULL;
}
PVOID BuildKeServiceTable(PVOID lpKernelAddress, PVOID lpOrgKernelAddress)
{
PVOID lpKiServiceTable = NULL;
PVOID lpKeServiceTable = NULL;
if (lpKiServiceTable = FindKiServiceTable(lpKernelAddress))
{
lpKeServiceTable = ExAllocatePool(NonPagedPool, KeServiceDescriptorTable->NumberOfService * sizeof(PVOID));
if (lpKeServiceTable)
{
RtlCopyMemory(lpKeServiceTable, lpKiServiceTable, KeServiceDescriptorTable->NumberOfService * sizeof(PVOID));
DbgPrint("BuildSSDT:0x%p\n", lpKeServiceTable);
}
}
return lpKeServiceTable;
}
而最后的DriverEntry与ServiceCallFilter则是:
PVOID ServiceCallFilter(PVOID *lppServiceTableBase, ULONG_PTR ServiceIndex)
{
if (lppServiceTableBase == KeServiceDescriptorTable->ServiceTableBase)
{
return g_KeServiceTable[ServiceIndex];
}
return lppServiceTableBase[ServiceIndex];
}
VOID __declspec(naked) _MyKiFastCallEntryFrame()
{
__asm
{
push ecx;
push eax;
push edi;
call ServiceCallFilter;
mov edx, eax;
pop ecx;
pop eax;
sub esp, ecx;
shr ecx, 2;
mov edi, esp;
jmp eax;
}
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
PVOID lpHookKiFastCallEntryAddress;
UCHAR HookCode[7];
DbgPrint("Driver Load.\n");
InitializePsLoadedModuleList(DriverObject);
g_lpNtoskrnlAddress = KeGetModuleHandle(PsLoadedModuleList, "ntoskrnl.exe");
g_lpNewNtoskrnlAddress = ReloadNtModule(PsLoadedModuleList);
g_KeServiceTable = (PVOID*)BuildKeServiceTable(g_lpNewNtoskrnlAddress, g_lpNtoskrnlAddress);
KeFixReloc2(g_lpNewNtoskrnlAddress, g_lpNtoskrnlAddress);
lpHookKiFastCallEntryAddress = FindHookKiFastCallEntryAddress(GetKiFastCallEntryAddress());
HookCode[0] = 0xE8;
*(ULONG*)&HookCode[1] = (ULONG_PTR)_MyKiFastCallEntryFrame - (ULONG_PTR)lpHookKiFastCallEntryAddress - 5;
HookCode[5] = 0x90;
HookCode[6] = 0x90;
RtlCopyMemoryEx(lpHookKiFastCallEntryAddress, HookCode, sizeof(HookCode));
DriverObject->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
来来来测试一下,就用HideToolz来看效果。
加载驱动咯。
好了,无视SSDT HOOK,同样无视INLINE HOOK,终于算是完成了一点像样的东西了,但这并不是全部,也不是此专题的完结,后面还有哦,我想后面应该是告诉大家如何再把SHADOW SSDT搞定。
最后,此代码仍然不能拿到本机测试,如果你想蓝的话。对于新模块最少还有两个问题没有搞定(别喷我),后续我同样会告诉大家。嗯,先这样吧。
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法
上传的附件: