When it comes to spin locks, we must talk about linked lists. In the previous article "Linked Lists and Structures in the Kernel"
, we simply used the linked list structure to store the process information list. I believe readers should have understood it. The basic use of kernel linked lists. This article will explain the simple application of spin locks. Spin locks are used to solve thread synchronization problems when reading and writing kernel linked lists. Locks must be used to solve multi-thread synchronization problems. Spin locks are usually used. A spin lock is a high IRQL lock provided in the kernel, which accesses a resource synchronously and exclusively.
Before understanding spin locks, we need to briefly introduce how to allocate memory in the kernel. Generally speaking, there are two functions to allocate memory. ExAllocatePool
can allocate memory space without any labels, and ExAllocatePoolWithTag
can allocate tagged memory. There is no difference in use between the two. Correspondingly, ExFreePool
is used to release non-tagged memory, while ExFreePoolWithTag
is used to release the corresponding memory through the passed in tag.
There are three commonly used paging attributes here. NonPagedPool
is used to allocate non-paged memory, PagedPool
is paged memory, and NonPagedPoolExecute
has execution permissions. of non-paged memory.
-
NonPagedPool: Used to allocate non-paged memory, which will not be swapped to disk and can be directly accessed by the kernel. Suitable for memory that requires fast access, such as driver code, interrupt handlers, system calls, etc.
-
PagedPool: used to allocate paged memory, which may be swapped to disk and needs to be accessed through the paging mechanism. It is suitable for memories that occupy large space and have low access frequency, such as caches, data structures, etc.
-
NonPagedPoolExecute: It is non-paged memory with execution permissions, suitable for situations where code needs to be executed, such as some special drivers.
It should be noted that there are certain security risks in using NonPagedPoolExecute
to allocate memory, because malware may use this memory to carry out attacks. Therefore, it is recommended to use this pagination property only when necessary.
#include <ntifs.h> typedef struct _MyStruct {<!-- --> ULONGx; ULONG y; }MyStruct, *pMyStruct; VOID UnDriver(PDRIVER_OBJECT driver) {<!-- --> DbgPrint("The driver has been uninstalled \ "); } NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) {<!-- --> DbgPrint("hello lyshark \ "); // Used to allocate and release non-tagged memory PVOID buffer = ExAllocatePool(NonPagedPool, 1024); DbgPrint("[*] allocated memory address = %p \ ", buffer); ExFreePool(buffer); // Used to allocate memory with tags pMyStruct ptr_buffer = (pMyStruct)ExAllocatePoolWithTag(NonPagedPoolExecute, sizeof(pMyStruct),"lyshark"); ptr_buffer->x = 100; ptr_buffer->y = 200; DbgPrint("[*] Allocate memory x = %d y = %d \ ", ptr_buffer->x, ptr_buffer->y); ExFreePoolWithTag(ptr_buffer, "lyshark"); UNICODE_STRING dst = {<!-- --> 0 }; UNICODE_STRING src = RTL_CONSTANT_STRING(L"hello lyshark"); dst.Buffer = (PWCHAR)ExAllocatePool(NonPagedPool, src.Length); if (dst.Buffer == NULL) {<!-- --> DbgPrint("[-] Error in allocating space \ "); } dst.Length = dst.MaximumLength = src.Length; RtlCopyUnicodeString( & amp;dst, & amp;src); DbgPrint("[*] Output copy = %wZ \ ", dst); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }
The code output effect is shown in the figure below;
Then let’s get to the point, taking a simple linked list as an example. Linked lists are mainly divided into one-way linked lists and two-way linked lists. There is only one linked list pointer in the linked list node of the one-way linked list, which points to the next linked list element, while there are two linked list nodes in the doubly linked list. Linked list node pointer, where Blink
points to the previous linked list node and Flink
points to the next node, taking a doubly linked list as an example.
#include <ntifs.h> #include <ntstrsafe.h> /* // Linked list node pointer typedef struct _LIST_ENTRY { struct _LIST_ENTRY *Flink; // The node after the current node struct _LIST_ENTRY *Blink; // The previous node of the current node }LIST_ENTRY, *PLIST_ENTRY; */ typedef struct _MyStruct {<!-- --> ULONGx; ULONG y; LIST_ENTRY lpListEntry; }MyStruct,*pMyStruct; VOID UnDriver(PDRIVER_OBJECT driver) {<!-- --> DbgPrint("Driver uninstalled successfully \ "); } NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) {<!-- --> DbgPrint("hello lyshark \ "); //Initialize the head node LIST_ENTRY ListHeader = {<!-- --> 0 }; InitializeListHead( & amp;ListHeader); //Define linked list elements MyStruct testA = {<!-- --> 0 }; MyStruct testB = {<!-- --> 0 }; MyStruct testC = {<!-- --> 0 }; testA.x = 100; testA.y = 200; testB.x = 1000; testB.y = 2000; testC.x = 10000; testC.y = 20000; // Insert nodes into the head and tail respectively InsertHeadList( & amp;ListHeader, & amp;testA.lpListEntry); InsertTailList( & amp;ListHeader, & amp;testB.lpListEntry); InsertTailList( & amp;ListHeader, & amp;testC.lpListEntry); // If the node is not empty, remove a node if (IsListEmpty( & amp;ListHeader) == FALSE) {<!-- --> RemoveEntryList( & amp;testA.lpListEntry); } // Output linked list data PLIST_ENTRY pListEntry = NULL; pListEntry = ListHeader.Flink; while (pListEntry != & amp;ListHeader) {<!-- --> // Calculate the memory distance between members and the top of the structure pMyStruct ptr = CONTAINING_RECORD(pListEntry, MyStruct, lpListEntry); DbgPrint("Node element X = %d Node element Y = %d \ ", ptr->x, ptr->y); // Get the address of the next element pListEntry = pListEntry->Flink; } Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }
The output effect of the linked list is shown in the figure below;
As mentioned above, in kernel development, thread synchronization problems will occur when multiple threads access the same data structure. In order to solve this problem, a lock mechanism can be used for synchronization. Spin lock is a commonly used lock mechanism. It is a high IRQL lock used for synchronized and exclusive access to a resource.
- The basic idea of a spin lock is: when a thread tries to acquire a lock, if the lock is already occupied, the thread keeps looping (that is, spinning) until the lock is released. Spin locks are suitable for situations where the lock holding time is short and there are few competitors, so that process context switching and scheduling overhead can be avoided.
The Windows kernel provides multiple types of spin locks, such as KSPIN_LOCK, KIRQL
, etc. Among them, KSPIN_LOCK
is the most commonly used type of spin lock. You can initialize a spin lock through the KeInitializeSpinLock
function, and pass KeAcquireSpinLock
and The KeReleaseSpinLock
function performs locking and unlocking operations.
It should be noted that when using spin locks, you need to pay attention to problems such as deadlock and priority inversion, so you need to use it with caution in actual applications.
#include <ntifs.h> #include <ntstrsafe.h> /* // Linked list node pointer typedef struct _LIST_ENTRY { struct _LIST_ENTRY *Flink; // The node after the current node struct _LIST_ENTRY *Blink; // The previous node of the current node }LIST_ENTRY, *PLIST_ENTRY; */ typedef struct _MyStruct {<!-- --> ULONGx; ULONG y; LIST_ENTRY lpListEntry; }MyStruct, *pMyStruct; //Define global linked list and global lock LIST_ENTRY my_list_header; KSPIN_LOCK my_list_lock; // initialization voidInit() {<!-- --> InitializeListHead( & amp;my_list_header); KeInitializeSpinLock( & amp;my_list_lock); } //Use lock within function void function_ins() {<!-- --> KIRQL Irql; // lock KeAcquireSpinLock( & amp;my_list_lock, & amp;Irql); DbgPrint("Lock internal execution\ "); // release lock KeReleaseSpinLock( & amp;my_list_lock, Irql); } VOID UnDriver(PDRIVER_OBJECT driver) {<!-- --> DbgPrint("Driver uninstalled successfully \ "); } NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) {<!-- --> DbgPrint("hello lyshark \ "); //Initialize the linked list Init(); //Allocate linked list space pMyStruct testA = (pMyStruct)ExAllocatePool(NonPagedPoolExecute, sizeof(pMyStruct)); pMyStruct testB = (pMyStruct)ExAllocatePool(NonPagedPoolExecute, sizeof(pMyStruct)); //Assign value testA->x = 100; testA->y = 200; testB->x = 1000; testB->y = 2000; //Insert data into the global linked list if (NULL != testA & amp; & amp; NULL != testB) {<!-- --> ExInterlockedInsertHeadList( & amp;my_list_header, (PLIST_ENTRY) & amp;testA->lpListEntry, & amp;my_list_lock); ExInterlockedInsertTailList( & amp;my_list_header, (PLIST_ENTRY) & amp;testB->lpListEntry, & amp;my_list_lock); } function_ins(); // Remove node A and put it in remove_entry PLIST_ENTRY remove_entry = ExInterlockedRemoveHeadList( & amp;testA->lpListEntry, & amp;my_list_lock); // Output linked list data while (remove_entry != & amp;my_list_header) {<!-- --> // Calculate the memory distance between members and the top of the structure pMyStruct ptr = CONTAINING_RECORD(remove_entry, MyStruct, lpListEntry); DbgPrint("Node element X = %d Node element Y = %d \ ", ptr->x, ptr->y); // Get the address of the next element remove_entry = remove_entry->Flink; } Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }
The execution effect after locking is as shown in the figure below;