NTSTATUS DriverEntry(IN PDRIVER_OBJECT theDriverObject, IN PUNICODE_STRING theRegistryPath) { NTSTATUS ntStatus; gb_Hooked = FALSE; // We have not hooked yet ntStatus = PsSetLoadImageNotifyRoutine(MyImageLoadNotify); return ntStatus; }
<p>在驱动入口处加入一个通知例程,当可执行程序被加载的时候,我们的函数会得到通知,然后在我们的函数里面调用相应的函数对可执行文件的IAT进行填充。</p><pre code_snippet_id="251431" snippet_file_name="blog_20140323_2_5231287" class="cpp" name="code">VOID MyImageLoadNotify(IN PUNICODE_STRING FullImageName, IN HANDLE ProcessId, // Process where image is mapped IN PIMAGE_INFO ImageInfo) { HookImportsOfImage(ImageInfo->ImageBase, ProcessId); }
在IAT填充函数当中,程序首先找到函数导入表,然后循环搜索整个函数导入表,直到找到我们所需要挂钩的函数导入表或者搜索完所有的函数导入表。假设关于某一个特定的DLL库的文件导入表存在于我们的PE文件当中,那么就搜索这个文件导入表的整个导入函数,看看是否有我们需要的导入函数,如果有就将这个函数在IAT当中的函数地址换成我们的函数地址。
NTSTATUS HookImportsOfImage(PIMAGE_DOS_HEADER image_addr, HANDLE h_proc)
{
PIMAGE_DOS_HEADER dosHeader;
PIMAGE_NT_HEADERS pNTHeader;
PIMAGE_IMPORT_DESCRIPTOR importDesc;
PIMAGE_IMPORT_BY_NAME p_ibn;
DWORD importsStartRVA;
PDWORD pd_IAT, pd_INTO;
int count, index;
char *dll_name = NULL;
char *pc_dlltar = "kernel32.dll";
char *pc_fnctar = "GetProcAddress";
PMDL p_mdl;
PDWORD MappedImTable;
DWORD d_sharedM = 0x7ffe0800;
DWORD d_sharedK = 0xffdf0800;
unsigned char new_code[] = {
0x90, // NOP make INT 3 to see
0xb8, 0xff, 0xff, 0xff, 0xff, // mov eax, 0xffffffff
0xff, 0xe0 // jmp eax
};
dosHeader = (PIMAGE_DOS_HEADER) image_addr;
pNTHeader = MakePtr( PIMAGE_NT_HEADERS, dosHeader,
dosHeader->e_lfanew );
//这一段通过PE文件的DOS头部,生成相应的NT头部
if ( pNTHeader->Signature != IMAGE_NT_SIGNATURE )
return STATUS_INVALID_IMAGE_FORMAT;
//验证NT头部是否是正常的NT头部
importsStartRVA = pNTHeader->OptionalHeader.DataDirectory
[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
//通过NT头部的数据目录表得到相应的RVA。因为我们的数据目录不是以绝对地址来寻址,所以需要这一项。
if (!importsStartRVA)
return STATUS_INVALID_IMAGE_FORMAT;
importDesc = (PIMAGE_IMPORT_DESCRIPTOR) (importsStartRVA + (DWORD) dosHeader);
//通过上面的RVA寻址到相应的导入表
for (count = 0; importDesc[count].Characteristics != 0; count++)
{
dll_name = (char*) (importDesc[count].Name + (DWORD) dosHeader);
pd_IAT = (PDWORD)(((DWORD) dosHeader) + (DWORD)importDesc[count].FirstThunk);
pd_INTO = (PDWORD)(((DWORD) dosHeader) + (DWORD)importDesc[count].OriginalFirstThunk);
for (index = 0; pd_IAT[index] != 0; index++)
{
if ((pd_INTO[index] & IMAGE_ORDINAL_FLAG) != IMAGE_ORDINAL_FLAG)
{//这个验证是要剔除函数以序号调用的情况
p_ibn = (PIMAGE_IMPORT_BY_NAME)(pd_INTO[index]+((DWORD) dosHeader));
if ((_stricmp(dll_name, pc_dlltar) == 0) && \
(strcmp(p_ibn->Name, pc_fnctar) == 0))
{//假设找到我们想要的DLL导入表,并且在导入表当中找到相应的导入函数名字,则进行挂钩
p_mdl = MmCreateMdl(NULL, &pd_IAT[index], 4);
if(!p_mdl)
return STATUS_UNSUCCESSFUL;
//首先建立一个到相应导入函数的MDL内存描述映射表
MmBuildMdlForNonPagedPool(p_mdl);
p_mdl->MdlFlags = p_mdl->MdlFlags | MDL_MAPPED_TO_SYSTEM_VA;
MappedImTable = MmMapLockedPages(p_mdl, KernelMode);
//然后改变相应的内存模式,是这一块内存是非换出的
if (!gb_Hooked)
{
RtlCopyMemory((PVOID)d_sharedK, new_code, 8);
RtlCopyMemory((PVOID)(d_sharedK+2),(PVOID)&pd_IAT[index], 4);
gb_Hooked = TRUE;
//在这里进行真正的挂钩,由二进制指令可以看到,这里实际上就是跳转到IAT执行
}
*MappedImTable = d_sharedM;
//由于在windows下面有一个在内核模式和用户模式下面共享的数据段,所以这里实际上是将d_sharedK与需要加载的PE文件的IAT进行挂钩,由于上面是在内核模式下操作,所以用共享内存的内核模式端,而PE文件的IAT是在用户模式,所以这里用用户模式端。
MmUnmapLockedPages(MappedImTable, p_mdl);
IoFreeMdl(p_mdl);
}
}
}
}
return STATUS_SUCCESS;
}