重载内核的相关文章实在是太多了,鉴于还是有很多初学者研究这一块,本文仅作为一个引导作用,文笔不好,见谅。
我的博客: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
第三部分链接:http://bbs.pediy.com/showthread.php?t=187982
第四部分链接:http://bbs.pediy.com/showthread.php?t=188012
第五部分链接:http://bbs.pediy.com/showthread.php?t=188050
第六部分:IDT HOOK
对我来说IDT一直是一个很难理解的东西,尤其是段选择子什么的,这里我不敢教大家如何HOOK IDT表,我只会INLINE HOOK,囧。再说每一个CPU都有各自独立的IDT表,做HOOK的话每一个CPU都要处理,也很麻烦。
为什么要HOOK IDT?除了正常的服务请求会经过KiFastCallEntry,那么不正常的就只能走中断门了,比如DEBUG,BREAKPOINT等等,我想这是大家有兴趣的地方。
和之前一样,对于大部分函数表来说,在NT内核中都会有一个对应的地方来存储原始地址,初始化函数是在KiSystemStartup中,但是WRK里并没有源代码,被可恶的编译成了lib文件,所以来看IDA吧:
显然这里的77E164也可以理解为是一个函数表,里面存储了中断门的线性地址,起个名字吧,我自己叫做KiTrapTable,不知道对不对。有一个特点就是这个地址表并不是元素连续的,每隔一个才是地址,也就是中断函数地址=函数表[索引*2],至于另外一个元素是什么我就不敢断言了,老办法,利用重定位来搜索:
PVOID FindKiTrapTable(PVOID lpNtoskrnlAddress)
{
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)
{
if (lpRelocateTable->VirtualAddress)
{
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; j++)
{
if ((lpItem[j] >> 12) == IMAGE_REL_BASED_HIGHLOW)
{
PUCHAR lpFixAddress = (PUCHAR)((PCHAR)lpNtoskrnlAddress + lpRelocateTable->VirtualAddress + (lpItem[j] & 0x0FFF));
/*
MOV ESI, OFFSET KiTrapTable
MOV ECX, 0x0800
SHR ECX, 2
REP MOVSD
*/
if (*(lpFixAddress - sizeof(UCHAR)) == 0xBE)
{
UCHAR CodeBytes[] = {0xB9, 0x00, 0x08, 0x00, 0x00, 0xC1, 0xE9, 0x02, 0xF3, 0xA5};
if (RtlEqualMemory(lpFixAddress + sizeof(PVOID), CodeBytes, sizeof(CodeBytes)))
{
return (PVOID)*(ULONG*)lpFixAddress;
}
}
}
}
}
lpRelocateTable = (IMAGE_BASE_RELOCATION *)((PCHAR)lpRelocateTable + lpRelocateTable->SizeOfBlock);
}
return NULL;
}
PVOID BuildKiTrapTable(PVOID lpNtoskrnlAddress)
{
PVOID lpKiTrapTable = NULL;
PVOID lpNewKiTrapTable = NULL;
if (lpKiTrapTable = FindKiTrapTable(lpNtoskrnlAddress))
{
if (lpNewKiTrapTable = ExAllocatePool(NonPagedPool, sizeof(PVOID) * 0xFF * 2))
{
RtlCopyMemory(lpNewKiTrapTable, lpKiTrapTable, sizeof(PVOID) * 0xFF * 2);
DbgPrint("BuildKiTrapTable:%08X\n", lpKiTrapTable);
}
}
return lpNewKiTrapTable;
}
找到原始地址,INLINE HOOK就非常简单了,直接在函数头写JMP到新内核中或者额外处理什么,这里我就不再贴代码了,节省篇幅。
其实HOOK IDT并没有什么很大的意义,毕竟正常的内核调用都是调用sysenter的,也是HOOK竞争最激烈的地方,把握住了这里,就可以绕过绝大部分的钩子。
当然对于反反调试,接管IDT中的调试相关中断还是很有用的,具体怎么做那就不是我的事儿了,大家各自发挥,对症下药吧。
也许还有后续内容,也许直接是一个总结,希望大家能够在技术上得到真正的提高,不论大家重载内核的目的纯洁与否,学到东西才是最长久的。也不要认为x86在远离我们就开始忽视,其实x64也是建立在x86的基础上,变化不是很大,对于底层要进行革命性的变化还要好久好久。
[课程]FART 脱壳王!加量不加价!FART作者讲授!