IoCreateDriver() behaviour with Spin Locks

In this article i am going to talk about some interesting behaviour of IoCreateDriver() function with spin locks and linked lists. First off, IoCreateDriver() is undocumented and not supported. It should never be used in production code by any means, but since this is a blog we can play around with it and learn something useful.

This function takes two parameters, a driver name (optional) and a pointer to a PDRIVER_INITIALIZE, note the second parameter is the standard entry point for kernel mode drivers.

In our example we will skip the first parameter because we are going to manually map the driver into system memory.


We are going to code two drivers (ldr.sys and buggy.sys). ldr.sys will implement the loading part and buggy.sys will be the loaded driver. In the first scenario i am going with the standard way for protecting linked lists:

__acquire_lock()

__insert_tail_list()

__release_lock()

We will observe how can the above method blue screen the system, in the second scenario, i am simply going to use 

ExfInterlockedInsertTailList()

buggy.sys its just a name, we wont name this driver anything, since we are going to map it. We basically will access the pointer DriverObject->DriverStart to extract the resource where ''buggy.sys'' is stored.

We define a normal structure to store our value

Then we simply initialize parameters, protect our insertion and removal with spin locks

This operation inserts 10 into the LIST_ENTRY container we just defined above, then it will check if the list is not empty. If it is not, then we extract the value to print on screen, all protected by spin locks.

Now, this is our buggy.sys driver loaded. Lets see what happens to this code when we load it via IoCreateDriver().

In our ldr.sys main() code we specify two functions, za_decrypt_payload() which access the resource and decrypts it, and map_driver() which performs the actual buggy.sys loading.


The buffer after extracting the resource is encrypted as the debugger shows


After calling the Rtl() compression functions and decrypt enc_buffer we obtain the buggy.sys data


As the reader can observe, the debugger shows the MZ header and the corresponding 5A4D value, so we know the buffer is valid.
Now we are ready to use this memory and map it into system space. We perform various relocation imports in order to align the buffer correctly. Before calling IoCreateDriver(), we check the imports the buggy.sys has. By the looks of it, it looks alright to me.


But watch what happens after calling IoCreateDriver() 






System bug checks with SYSTEM_THREAD_EXCEPTION_NOT_HANDLED, in other words, some memory in the buggy.sys points to nowhere. Lets change the code for now


This time we will inset values to the list using the atomic way. Interlocked functions are safe and can be called at any IRQL, basically it disables all interrupts on the current processor, acquires the spin lock, updates the list and then restores the interrupts. Remember to release the spin lock before enabling the interrupts.
After calling IoCreateDriver() again from ldr.sys this the output on the debugger



As we can see, the buggy.sys gets loaded and prints 10 after extracting the value from the list without crashing the system this time.
We conclude this small demonstration. Usually rootkits map data using IoCreateDriver(), if they want to protect shared memory disabling interrupts before locking is definitely the way to go.

















Comments

  1. Spin lock functions should be imported from "ntoskrnl.exe" on Win8+, not "HAL.dll".

    ReplyDelete

Post a Comment