看了combojiang大侠的rootkit专题,发现少了一个导出表钩子,既EAT;HOOK,刚好前几天自己搞了个IAT HOOK,然后就把其中的代码稍做修改,于是有这篇文章,
偶学的东西不久,很多东西还不知道,请多指教,呵呵
导出表钩子比导入表钩子感觉好用多,先说下原理吧,函数导入的函数的地址是再运行时候才确定的,比如我们的一个驱动程序导入了PsGetCurrentProcessId这个ntkrnlpa.exe
导出的函数,那在我们驱动程序加载运行的时候,装载程序会确定ntkrnlpa.exe在内存的基地址,接着遍历它的导出表,在AddressOfNames指向的"函数名字表"中找到
PsGetCurrentProcessId的位置,也就是如果在AddressOfNames[i]中找到PsGetCurrentProcessId,那就用i在AddressOfNameOrdinals中索引,假使得到是X,那么
AddressOfFunctions[index]的值就是PsGetCurrentProcessId的RVA了,最后就可以知道PsGetCurrentProcessId在内存的值是MM=ntkrnlpa.exe在内存的基地址
+PsGetCurrentProcessId的RVA,然后转载程序就把这个值写到我们驱动程序的IAT中,好了知道这些后,EAT HOOK就是修改PsGetCurrentProcessId的RVA,使得
PsGetCurrentProcessId的RVA(修改后的)+ntkrnlpa.exe在内存的基地址=我们自己函数的值,这样装载程序会把我们的函数的地址写入那些调用PsGetCurrentProcessId
的驱动程序的IAT,那么当那些驱动程序调用PsGetCurrentProcessId时,实际上是执行了我们自己的函数...呵呵.是不是比IAT HOOK更好用呢
EAT HOOK可以用来监控系统函数的调用情况,比如我们EAT HOOK了
PsGetCurrentProcessId,那谁调用该函数我们就知道了,你也可以HOOK KeInitializeApc等热门函数,其实知道了EAT HOOK原理后,我们
可以修改函数名字表,比如把PsGetCurrentProcessId改成其它名字,这样装载程序遍历"函
数名字表"就找不到匹对的名字,那驱动程序就宣告装载失败,详细代码请看
<<利用导出表来禁止一些驱动程序的加载>>http://bbs.pediy.com/showthread.php?t=62531
那怎么防止EAT HOOK,一个方法是自己定位函数在内存地址,请看下面的代码,用于枚举
ntkrnlpa.exe导出函数在内存的地址
VOID ListKernelFunctionAndAddress()
{
HANDLE hMod;
PVOID BaseAddress = NULL;
IMAGE_DOS_HEADER * dosheader;
IMAGE_OPTIONAL_HEADER * opthdr;
PIMAGE_EXPORT_DIRECTORY exports;
USHORT index=0 ;
ULONG addr, i;
PVOID FuncNameRVA;
PUCHAR pFuncName = NULL;
PULONG pAddressOfFunctions,pAddressOfNames,pAddressOfNameOrdinals;
BaseAddress= GetDriverBaseAdress("ntkrnlpa.exe");
DbgPrint("Map BaseAddress is:%x\n",BaseAddress);
hMod = BaseAddress;
dosheader = (IMAGE_DOS_HEADER *)hMod;
opthdr =(IMAGE_OPTIONAL_HEADER *) ((BYTE*)hMod+dosheader->e_lfanew+24);
exports = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)dosheader+ opthdr->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
pAddressOfFunctions=(ULONG*)((BYTE*)hMod+exports->AddressOfFunctions);
pAddressOfNames=(ULONG*)((BYTE*)hMod+exports->AddressOfNames);
pAddressOfNameOrdinals=(USHORT*)((BYTE*)hMod+exports->AddressOfNameOrdinals);
for (i = 0; i < exports->NumberOfNames; i++)
{
index=pAddressOfNameOrdinals[i];
addr=pAddressOfFunctions[index];
pFuncName = (PUCHAR)( (BYTE*)hMod + pAddressOfNames[i]);
addr = pAddressOfFunctions[index];
DbgPrint("the function: %s is at: 0x%x\n",pFuncName,addr+(BYTE*)hMod);
}
}
运行后:
--------------------
--------------
-----------------
---------------------
好了,讲了这么多时候进去正题,怎样EAT HOOK,这里我们以HOOK ntkrnlpa.exe导出的
PsGetCurrentProcessId
首先我们是定位ntkrnlpa.exe被加载在内存中的什么地方,那就写一个函数吧,
PVOID GetModlueBaseAdress(char* ModlueName)
{
ULONG size,index;
PULONG buf;
NTSTATUS status;
PSYSTEM_MODULE_INFORMATION module;
PVOID driverAddress=0;
ZwQuerySystemInformation(SystemModuleInformation,&size, 0, &size);
if(NULL==(buf = (PULONG)ExAllocatePool(PagedPool, size)))
{
DbgPrint("failed alloc memory failed \n");
return 0;
}
status=ZwQuerySystemInformation(SystemModuleInformation,buf, size , 0);
if(!NT_SUCCESS( status ))
{
DbgPrint("failed query\n");
return 0;
}
module = (PSYSTEM_MODULE_INFORMATION)(( PULONG )buf + 1);
for (index = 0; index < *buf; index++)
if (_stricmp(module[index].ImageName + module[index].ModuleNameOffset, ModlueName) == 0)
{
driverAddress = module[index].Base;
DbgPrint("Module found at:%x\n",driverAddress);
}
ExFreePool(buf);
return driverAddress;
}
自己添加点测试代码编译下,没什么问题,这样我们就完成了第一个问题
接着是写自己的函数了,就是替换PsGetCurrentProcessId的函数,这里我们很简单的输
出点内容就可以了
ULONG g_OriginalPsGetCurrentProcessId;
typedef HANDLE (*PSGETCURRENTPROCESSID)();
HANDLE
MyPsGetCurrentProcessId()
{
HANDLE handle;
DbgPrint("HOOK_PsGetCurrentProcessId called!\n");
handle =((PSGETCURRENTPROCESSID)(g_OriginalPsGetCurrentProcessId))();
return handle;
}
好了,那就开始写安装钩子程序吧,因为在卸在钩子时需要用到一些变量,这里我们就把安
装和卸载写成一个函数就可以了,注意IN unsigned int test,传入1表示安装钩子,否则表
示卸载,IN PCSTR funName这里我们传入PsGetCurrentProcessId,好了请看代码
VOID StartHook_And_Unhook(IN PCSTR funName, IN unsigned int test)
{
HANDLE hMod;
PUCHAR BaseAddress = NULL;
IMAGE_DOS_HEADER * dosheader;
IMAGE_OPTIONAL_HEADER * opthdr;
PIMAGE_EXPORT_DIRECTORY exports;
USHORT index=0 ;
ULONG addr ,i;
PUCHAR pFuncName = NULL;
PULONG pAddressOfFunctions,pAddressOfNames;
PUSHORT pAddressOfNameOrdinals;
BaseAddress= GetModlueBaseAdress("ntkrnlpa.exe");
DbgPrint("Map BaseAddress is:%x\n",BaseAddress);
hMod = BaseAddress;
dosheader = (IMAGE_DOS_HEADER *)hMod;
opthdr =(IMAGE_OPTIONAL_HEADER *) ((BYTE*)hMod+dosheader->e_lfanew+24);
exports = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)dosheader+ opthdr->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
pAddressOfFunctions=(ULONG*)((BYTE*)hMod+exports->AddressOfFunctions);
pAddressOfNames=(ULONG*)((BYTE*)hMod+exports->AddressOfNames);
pAddressOfNameOrdinals=(USHORT*)((BYTE*)hMod+exports->AddressOfNameOrdinals);
for (i = 0; i < exports->NumberOfNames; i++)
{
index=pAddressOfNameOrdinals[i];
pFuncName = (PUCHAR)( (BYTE*)hMod + pAddressOfNames[i]);
if (_stricmp( (char*)pFuncName,funName) == 0)
{
addr=pAddressOfFunctions[index];
break;
}
}
if(test==1)
{
_asm
{
CLI
MOV EAX, CR0
AND EAX, NOT 10000H
MOV CR0, EAX
}
DbgPrint("PsGetCurrentProcessId is:%x\n",(PUCHAR)hMod + pAddressOfFunctions[index]);
pAddressOfFunctions[index] = ( PCHAR )MyPsGetCurrentProcessId - BaseAddress;
DbgPrint("g_OriginalPsGetCurrentProcessId is:%x\n",g_OriginalPsGetCurrentProcessId);
g_OriginalPsGetCurrentProcessId= (PUCHAR)hMod + pAddressOfFunctions[index] ;
_asm
{
MOV EAX, CR0
OR EAX, 10000H
MOV CR0, EAX
STI
}
}
else
{
_asm
{
CLI
MOV EAX, CR0
AND EAX, NOT 10000H
MOV CR0, EAX
}
pAddressOfFunctions[index] = ( PCHAR )g_OriginalPsGetCurrentProcessId - BaseAddress;
_asm
{
MOV EAX, CR0
OR EAX, 10000H
MOV CR0, EAX
STI
}
}
}
好了,基本框架就差不多了,接着就是一些结构的声明,我们把它放在hookiat.h这个头文件
里,因为很长就不帖了,可以在附件里看,上面的代码很多地方不是很好,需要自己修改,不
保证在你机器不蓝,呵呵,学习靠思考,在代码里我修改了一处地方
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)