Write in front
this series is written word by word, including examples and experimental screenshots. Due to the complexity of the system kernel, there may be errors or incompleteness. If there are errors, criticism and correction are welcome. This tutorial will be updated for a long time. If you have any good suggestions, welcome feedback. Code words are not easy. If this article is helpful to you, if you have spare money, you can reward and support my creation. If you want to reprint, please attach my reprint information to the back of the article and state my personal information and my blog address, but you must notify me in advance.
If you look from the middle, please read it carefully Yu Xia's view of Win system kernel -- a brief introduction , easy to learn this tutorial.
before reading this tutorial, ask a few questions. Have you prepared the basic knowledge? Have you learned the protection mode? Have you learned the system call? Have you finished your exercise? If not, don't continue.
β
π Gorgeous dividing line π
β
Exercises and references
The answers are for reference and may not be consistent with my answers, but they must be passed successfully. The code is in the folding area, and the reason for thinking will be explained in the article.
one οΈβ£ Broken chain process structure, realize hiding, and think about why the broken chain process can still be executed.
π Click to view the application code πβ π Click to view the driver code π#include "stdafx.h" #include <windows.h> #include <winioctl.h> #include <stdlib.h> //Opcode: 0x0-0x7FF reserved, 0x800-0xFFF available #define Hide CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) #define _Show CTL_CODE(FILE_DEVICE_UNKNOWN,0x900,METHOD_BUFFERED,FILE_ANY_ACCESS) #define SYMBOL_LINK_NAME L"\\\\.\\HideProcess" HANDLE g_Device; int main(int argc, char* argv[]) { //Get driver link object handle g_Device=CreateFileW(SYMBOL_LINK_NAME,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0); if (g_Device==INVALID_HANDLE_VALUE) { puts("Failed to access driver symbolic link!"); goto endproc; } DWORD pid; DWORD outBuffer; DWORD m[2]; DWORD re; puts("Please enter the program you want to hide pid : "); scanf("%d",&pid); if (pid) { if (DeviceIoControl(g_Device,Hide,&pid,sizeof(DWORD), outBuffer,sizeof(DWORD),&re,NULL)) { puts("The hidden command was sent successfully. Please check whether the program is hidden"); } puts("Check the task manager and press any key to restore hiding!"); m[0]=GetCurrentProcessId(); m[1]=outBuffer; if (DeviceIoControl(g_Device,_Show,m,sizeof(DWORD)*2, outBuffer,sizeof(DWORD),&re,NULL)) { puts("The recovery command was sent successfully. Please check whether the program displays"); } } else { puts("pid Illegal!"); } endproc: CloseHandle(g_Device); system("pause"); return 0; }
β π Click to see the answer π#include <ntifs.h> #include <ntddk.h> UNICODE_STRING Devicename; UNICODE_STRING SymbolLink; #define DEVICE_NAME L"\\Device\\HideProcess" #define SYMBOL_LINK L"\\??\\HideProcess" #define Hide CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) #define Show CTL_CODE(FILE_DEVICE_UNKNOWN,0x900,METHOD_BUFFERED,FILE_ANY_ACCESS) NTSTATUS UnloadDriver(PDRIVER_OBJECT DriverObject) { IoDeleteSymbolicLink(&SymbolLink); IoDeleteDevice(DriverObject->DeviceObject); //Assuming that the dobj variable in the DriverEntry function is declared global, why not use this to delete the device object DbgPrint("Uninstall succeeded!!!"); } NTSTATUS DispatchFunction(PDEVICE_OBJECT pDevObj, PIRP pIrp) { NTSTATUS status = STATUS_INVALID_DEVICE_REQUEST; PIO_STACK_LOCATION pIrqlStack; ULONG uIoControlCode; PVOID pIoBuffer; ULONG uInLength; ULONG uOutLength; ULONG uRead[2]; ULONG uWrite; pIrqlStack = IoGetCurrentIrpStackLocation(pIrp); uIoControlCode = pIrqlStack->Parameters.DeviceIoControl.IoControlCode; pIoBuffer = pIrp->AssociatedIrp.SystemBuffer; uInLength = pIrqlStack->Parameters.DeviceIoControl.InputBufferLength; uOutLength = pIrqlStack->Parameters.DeviceIoControl.OutputBufferLength; LIST_ENTRY* lethis; LIST_ENTRY* fle; LIST_ENTRY* ble; PEPROCESS eProcess; switch (uIoControlCode) { case Hide: RtlMoveMemory(uRead, pIoBuffer, 4); PsLookupProcessByProcessId(uRead[0], &eProcess); RtlMoveMemory(pIoBuffer, &eProcess, 4); pIrp->IoStatus.Information = 4; lethis = (LIST_ENTRY*)((ULONG)eProcess + 0x88); fle = lethis->Flink; ble = lethis->Blink; fle->Blink = ble; ble->Flink = fle; DbgPrint("Chain broken successfully!!!"); break; case Show: RtlMoveMemory(uRead, pIoBuffer, 8); PsLookupProcessByProcessId(uRead[0], &eProcess); //First member: pid lethis = (LIST_ENTRY*)((ULONG)eProcess + 0x88); //Self pid ble= (LIST_ENTRY*)((ULONG)uRead[1] + 0x88); fle = lethis->Flink; lethis->Flink = ble; ble->Blink = lethis; ble->Flink = fle; fle->Blink = ble; DbgPrint("Recovery succeeded!!!"); pIrp->IoStatus.Information = 0; break; } pIrp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS DefaultDispatchFunction(PDEVICE_OBJECT pDevObj, PIRP pIrp) { pIrp->IoStatus.Information = 0; pIrp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { DriverObject->DriverUnload = UnloadDriver; UNICODE_STRING Devicename; RtlInitUnicodeString(&Devicename, DEVICE_NAME); DEVICE_OBJECT dobj; IoCreateDevice(DriverObject, 0, &Devicename, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &dobj); RtlInitUnicodeString(&SymbolLink, SYMBOL_LINK); IoCreateSymbolicLink(&SymbolLink, &Devicename); DriverObject->Flags |= DO_BUFFERED_IO; DriverObject->MajorFunction[IRP_MJ_CREATE] = DefaultDispatchFunction; DriverObject->MajorFunction[IRP_MJ_CLOSE] = DefaultDispatchFunction; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchFunction; DbgPrint("Loading succeeded!!!"); return STATUS_SUCCESS; }
let's give an effect picture first:
there is a problem in the comment: assuming that the dobj variable in the DriverEntry function is declared global, why not use this to delete the device object. You can try. The answer is No. It will lead to a blue screen. The reason is that this thing uses paging memory, and the unloading driver thread cannot use paging memory.
two οΈβ£ Use DebugPort to clear to realize anti debugging.
π Click to view the application code πβ π Click to view the driver code π#include "stdafx.h" #include <windows.h> #include <winioctl.h> #include <stdlib.h> //Opcode: 0x0-0x7FF reserved, 0x800-0xFFF available #define Hide CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) #define SYMBOL_LINK_NAME L"\\\\.\\HideProcess" HANDLE g_Device; int main(int argc, char* argv[]) { //Get driver link object handle g_Device=CreateFileW(SYMBOL_LINK_NAME,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0); if (g_Device==INVALID_HANDLE_VALUE) { puts("Failed to access driver symbolic link!"); goto endproc; } DWORD pid; DWORD outBuffer; DWORD re; puts("Please enter the name of the program that needs de debugging protection pid : "); scanf("%d",&pid); if (pid) { if (DeviceIoControl(g_Device,Hide,&pid,sizeof(DWORD), &outBuffer,sizeof(DWORD),&re,NULL)) { puts("The anti debugging protection command was sent successfully"); } } else { puts("pid Illegal!"); } endproc: CloseHandle(g_Device); system("pause"); return 0; } }
#include <ntifs.h> #include <ntddk.h> UNICODE_STRING Devicename; UNICODE_STRING SymbolLink; ULONG* pDebugPort=NULL; HANDLE hThread; #define DEVICE_NAME L"\\Device\\HideProcess" #define SYMBOL_LINK L"\\??\\HideProcess" #define Hide CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) VOID HideThread(_In_ PVOID StartContext) { while (1) { *pDebugPort = 0; } } NTSTATUS UnloadDriver(PDRIVER_OBJECT DriverObject) { IoDeleteDevice(DriverObject); IoDeleteSymbolicLink(&SymbolLink); DbgPrint("Uninstall succeeded!!!"); } NTSTATUS DispatchFunction(PDEVICE_OBJECT pDevObj, PIRP pIrp) { NTSTATUS status = STATUS_INVALID_DEVICE_REQUEST; PIO_STACK_LOCATION pIrqlStack; ULONG uIoControlCode; PVOID pIoBuffer; ULONG uInLength; ULONG uOutLength; ULONG uRead; ULONG uWrite; pIrqlStack = IoGetCurrentIrpStackLocation(pIrp); uIoControlCode = pIrqlStack->Parameters.DeviceIoControl.IoControlCode; pIoBuffer = pIrp->AssociatedIrp.SystemBuffer; uInLength = pIrqlStack->Parameters.DeviceIoControl.InputBufferLength; uOutLength = pIrqlStack->Parameters.DeviceIoControl.OutputBufferLength; PEPROCESS eProcess; switch (uIoControlCode) { case Hide: RtlMoveMemory(&uRead, pIoBuffer, 4); PsLookupProcessByProcessId(uRead, &eProcess); if (pDebugPort) break; pDebugPort = (ULONG*)((ULONG)eProcess + 0xbc); PsCreateSystemThread(&hThread, GENERIC_ALL, NULL, NULL, NULL, HideThread, NULL); DbgPrint("Chain broken successfully!!!"); break; } pIrp->IoStatus.Information = 0; pIrp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS DefaultDispatchFunction(PDEVICE_OBJECT pDevObj, PIRP pIrp) { pIrp->IoStatus.Information = 0; pIrp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { DriverObject->DriverUnload = UnloadDriver; UNICODE_STRING Devicename; RtlInitUnicodeString(&Devicename, DEVICE_NAME); DEVICE_OBJECT dobj; IoCreateDevice(DriverObject, 0, &Devicename, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &dobj); RtlInitUnicodeString(&SymbolLink, SYMBOL_LINK); IoCreateSymbolicLink(&SymbolLink, &Devicename); DriverObject->Flags |= DO_BUFFERED_IO; DriverObject->MajorFunction[IRP_MJ_CREATE] = DefaultDispatchFunction; DriverObject->MajorFunction[IRP_MJ_CLOSE] = DefaultDispatchFunction; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchFunction; DbgPrint("Loading succeeded!!!"); return STATUS_SUCCESS; }
three οΈβ£ How to determine whether a process is a GUI thread.
π Click to see the answer πI start a DebugView in the virtual machine in advance, and then input instructions in WinDbug to traverse the process:
kd> !process 0 0 ****NT ACTIVE PROCESS DUMP**** Failed to get VadRoot PROCESS 89c258c0 SessionId: 0 Cid: 0758 Peb: 7ffd3000 ParentCid: 05f4 DirBase: 13ac01a0 ObjectTable: e1d63088 HandleCount: 57. Image: Dbgview.exe
then look at the process information:
kd> !process 89c258c0 Failed to get VadRoot PROCESS 89c258c0 SessionId: 0 Cid: 0758 Peb: 7ffd3000 ParentCid: 05f4 DirBase: 13ac01a0 ObjectTable: e1d63088 HandleCount: 57. Image: Dbgview.exe VadRoot 00000000 Vads 0 Clone 0 Private 192. Modified 7. Locked 0. DeviceMap e1638c88 Token e176a7d0 ElapsedTime 00:00:05.476 UserTime 00:00:00.010 KernelTime 00:00:00.010 QuotaPoolUsage[PagedPool] 0 QuotaPoolUsage[NonPagedPool] 0 Working Set Sizes (now,min,max) (974, 50, 345) (3896KB, 200KB, 1380KB) PeakWorkingSetSize 974 VirtualSize 30 Mb PeakVirtualSize 32 Mb PageFaultCount 1030 MemoryPriority FOREGROUND BasePriority 8 CommitCharge 392THREAD 898b0020 Cid 0758.0760 Teb: 7ffdf000 Win32Thread: e1e6d2c8 WAIT: (UserRequest) UserMode Non-Alertable
89b0b6a8 SynchronizationEvent
89b43930 SynchronizationEvent
Not impersonating
DeviceMap e1638c88
Owning Process 00000000 Image:
Attached Process 89c258c0 Image: Dbgview.exe
Wait Start TickCount 6228 Ticks: 1 (0:00:00:00.010)
Context Switch Count 225 IdealProcessor: 0 LargeStack
UserTime 00:00:00.000
KernelTime 00:00:00.010
Win32 Start Address 0x00413487
Stack Init b8138000 Current b813795c Base b8138000 Limit b8132000 Call 00000000
Priority 9 BasePriority 8 PriorityDecrement 0 IoPriority 0 PagePriority 0
ChildEBP RetAddr
b8137974 80501cd6 nt!KiSwapContext+0x2e (FPO: [Uses EBP] [0,0,4])
b8137980 804faae2 nt!KiSwapThread+0x46 (FPO: [0,0,0])
b81379b8 805b7418 nt!KeWaitForMultipleObjects+0x284 (FPO: [Non-Fpo])
b8137d48 8053e638 nt!NtWaitForMultipleObjects+0x2a2 (FPO: [Non-Fpo])
b8137d48 7c92e4f4 (T) nt!KiFastCallEntry+0xf8 (FPO: [0,0] TrapFrame @ b8137d64)
0012fc84 00000000 (T) ntdll!KiFastSystemCallRet (FPO: [0,0,0])THREAD 89cca928 Cid 0758.0764 Teb: 7ffde000 Win32Thread: 00000000 WAIT: (DelayExecution) UserMode Alertable
89ccaa18 NotificationTimer
Not impersonating
DeviceMap e1638c88
Owning Process 00000000 Image:
Attached Process 89c258c0 Image: Dbgview.exe
Wait Start TickCount 6228 Ticks: 1 (0:00:00:00.010)
Context Switch Count 13 IdealProcessor: 0
UserTime 00:00:00.000
KernelTime 00:00:00.000
Win32 Start Address 0x0043261b
Stack Init b873e000 Current b873dcbc Base b873e000 Limit b873b000 Call 00000000
Priority 8 BasePriority 8 PriorityDecrement 0 IoPriority 0 PagePriority 0
ChildEBP RetAddr
b873dcd4 80501cd6 nt!KiSwapContext+0x2e (FPO: [Uses EBP] [0,0,4])
b873dce0 804fa79f nt!KiSwapThread+0x46 (FPO: [0,0,0])
b873dd0c 8060db19 nt!KeDelayExecutionThread+0x1c9 (FPO: [Non-Fpo])
b873dd54 8053e638 nt!NtDelayExecution+0x87 (FPO: [Non-Fpo])
b873dd54 7c92e4f4 (T) nt!KiFastCallEntry+0xf8 (FPO: [0,0] TrapFrame @ b873dd64)
00d4ff68 00000000 (T) ntdll!KiFastSystemCallRet (FPO: [0,0,0])
there can be two processes, one of which is a GUI thread and the other is an ordinary thread. How do you know? Let's view their ServiceTable:
kd> dt _KTHREAD 898b0020 ntdll!_KTHREAD+0x0e0 ServiceTable : 0x80553f60 Void
kd> dt _KTHREAD 89cca928
ntdll!_KTHREAD+0x0e0 ServiceTable : 0x80553fa0 Void
kd> dd KeServiceDescriptorTable
80553fa0 80502b8c 00000000 0000011c 80503000
80553fb0 00000000 00000000 00000000 00000000
80553fc0 00000000 00000000 00000000 00000000
80553fd0 00000000 00000000 00000000 00000000kd> dd KeServiceDescriptorTableShadow
80553f60 80502b8c 00000000 0000011c 80503000
80553f70 bf999b80 00000000 0000029b bf99a890
80553f80 00000000 00000000 00000000 00000000
80553f90 00000000 00000000 00000000 00000000
KeServiceDescriptorTableShadow has a GUI thread related service table that KeServiceDescriptorTable does not have. Is it easy to determine which GUI thread is? KeServiceDescriptorTableShadow is a GUI thread, and KeServiceDescriptorTable is a normal thread.
four οΈβ£ Broken thread structure, realize hiding, and think about why broken threads can still execute.
π Click to view the application code πβ π Click to view the driver code π#include "stdafx.h" #include <windows.h> #include <winioctl.h> #include <stdlib.h> //Opcode: 0x0-0x7FF reserved, 0x800-0xFFF available #define Hide CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) #define SYMBOL_LINK_NAME L"\\\\.\\HideThread" HANDLE g_Device; DWORD WINAPI ThreadProc(LPVOID lpParam) { int i=0; while (1) { printf("\n%d: I'm still alive!!!\n",i++); Sleep(1500); } return 0; } int main(int argc, char* argv[]) { //Get driver link object handle g_Device=CreateFileW(SYMBOL_LINK_NAME,GENERIC_READ GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0 ; if (g_Device!=INVALID_HANDLE_VALUE) { DWORD tid; DWORD re; DWORD outBuffer; HANDLE hthread = CreateThread(NULL,NULL (LPTHREAD_START_ROUTINE)ThreadProc,NULL,NULL,&tid); CloseHandle(hthread); if (hthread==INVALID_HANDLE_VALUE) { puts("Thread creation failed!!!"); goto endproc; } system("pause"); if (DeviceIoControl(g_Device,Hide,&tid,sizeof(DWORD), outBuffer,sizeof(DWORD),&re,NULL)) { puts("The hidden command is being sent. Please check whether the number of threads is reduced"); } system("pause"); } else { puts("Failed to access driver symbolic link!"); } endproc: CloseHandle(g_Device); system("pause"); return 0; }
β π Click to see the answer π#include <ntifs.h> #include <ntddk.h> UNICODE_STRING Devicename; UNICODE_STRING SymbolLink; #define DEVICE_NAME L"\\Device\\HideThread" #define SYMBOL_LINK L"\\??\\HideThread" #define Hide CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) NTSTATUS UnloadDriver(PDRIVER_OBJECT DriverObject) { IoDeleteSymbolicLink(&SymbolLink); IoDeleteDevice(DriverObject->DeviceObject); DbgPrint("Uninstall succeeded!!!"); } NTSTATUS DispatchFunction(PDEVICE_OBJECT pDevObj, PIRP pIrp) { NTSTATUS status = STATUS_INVALID_DEVICE_REQUEST; PIO_STACK_LOCATION pIrqlStack; ULONG uIoControlCode; PVOID pIoBuffer; ULONG uInLength; ULONG uOutLength; ULONG uRead; ULONG uWrite; pIrqlStack = IoGetCurrentIrpStackLocation(pIrp); uIoControlCode = pIrqlStack->Parameters.DeviceIoControl.IoControlCode; pIoBuffer = pIrp->AssociatedIrp.SystemBuffer; uInLength = pIrqlStack->Parameters.DeviceIoControl.InputBufferLength; uOutLength = pIrqlStack->Parameters.DeviceIoControl.OutputBufferLength; LIST_ENTRY* lethis; LIST_ENTRY* fle; LIST_ENTRY* ble; PETHREAD eThread; switch (uIoControlCode) { case Hide: RtlMoveMemory(&uRead, pIoBuffer,4); PsLookupThreadByThreadId(uRead, &eThread); lethis = (LIST_ENTRY*)((ULONG)eThread + 0x1B0); fle = lethis->Flink; ble = lethis->Blink; fle->Blink = ble; ble->Flink = fle; lethis = (LIST_ENTRY*)((ULONG)eThread + 0x22C); fle = lethis->Flink; ble = lethis->Blink; fle->Blink = ble; ble->Flink = fle; DbgPrint("Chain broken successfully!!!"); break; } pIrp->IoStatus.Information = 0; pIrp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS DefaultDispatchFunction(PDEVICE_OBJECT pDevObj, PIRP pIrp) { pIrp->IoStatus.Information = 0; pIrp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { DriverObject->DriverUnload = UnloadDriver; UNICODE_STRING Devicename; RtlInitUnicodeString(&Devicename, DEVICE_NAME); DEVICE_OBJECT dobj; IoCreateDevice(DriverObject, 0, &Devicename, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &dobj); RtlInitUnicodeString(&SymbolLink, SYMBOL_LINK); IoCreateSymbolicLink(&SymbolLink, &Devicename); DriverObject->Flags |= DO_BUFFERED_IO; DriverObject->MajorFunction[IRP_MJ_CREATE] = DefaultDispatchFunction; DriverObject->MajorFunction[IRP_MJ_CLOSE] = DefaultDispatchFunction; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchFunction; DbgPrint("Loading succeeded!!!"); return STATUS_SUCCESS; }
let's give an effect picture first:
there are no difficulties and doubts left. See the folding code analysis for yourself.
Waiting list and scheduling list
the EPROCESS linked list of the process structure has two places surrounding all threads of the current process. For process chain breaking, the program can run normally because CPU execution and scheduling are based on threads. Process chain breaking only affects some API s that traverse system processes, and will not affect program execution. The same is true for thread disconnection. After disconnection, the disconnected thread cannot be seen in Windbg or OD, but its execution is not affected. The reason is that the CPU does not use this linked list at all when scheduling threads.
threads have three states: ready, wait ing and running.
the running threads are stored in KPCR, and the ready and waiting threads are all in another 33 linked lists. A waiting list and 32 ready lists. These linked lists are used_ KTHREAD + 0x060 this position. This one location, two names, as shown below. In other words, a thread can only belong to one of the circles at a certain time.
+0x060 WaitListEntry : _LIST_ENTRY +0x060 SwapListEntry : _SINGLE_LIST_ENTRY
Waiting list
when a thread calls a Sleep or WaitForSingleObject function, it hangs in a linked list, which is a waiting list. Before learning to wait for the linked list, we need to know a global variable:
kd> dd KiWaitListHead 80553d88 89b59540 89bb0300 00000010 00000000 80553d98 b6de6b92 a787ea13 01000013 ffdff980 80553da8 ffdff980 80500df0 00000000 00006029 80553db8 00000000 00000000 80553dc0 80553dc0 80553dc8 00000000 00000000 80553dd0 80553dd0 80553dd8 00000000 00000000 00000000 89da7da8 80553de8 00000000 00000000 00040001 00000000 80553df8 89da7e18 89da7e18 00000001 00000000
this global variable stores a two-way linked list. Let's test:
kd> dt _ETHREAD 89b59540-60 ntdll!_ETHREAD +0x000 Tcb : _KTHREAD +0x1c0 CreateTime : _LARGE_INTEGER 0x0ebf1cd6`795268e0 +0x1c0 NestedFaultCount : 0y00 +0x1c0 ApcNeeded : 0y0 +0x1c8 ExitTime : _LARGE_INTEGER 0x89b596a8`89b596a8 +0x1c8 LpcReplyChain : _LIST_ENTRY [ 0x89b596a8 - 0x89b596a8 ] +0x1c8 KeyedWaitChain : _LIST_ENTRY [ 0x89b596a8 - 0x89b596a8 ] +0x1d0 ExitStatus : 0n0 +0x1d0 OfsChain : (null) +0x1d4 PostBlockList : _LIST_ENTRY [ 0xe1828d30 - 0xe198e730 ] +0x1dc TerminationPort : 0xe18e94d8 _TERMINATION_PORT +0x1dc ReaperLink : 0xe18e94d8 _ETHREAD +0x1dc KeyedWaitValue : 0xe18e94d8 Void +0x1e0 ActiveTimerListLock : 0 +0x1e4 ActiveTimerListHead : _LIST_ENTRY [ 0x89b596c4 - 0x89b596c4 ] +0x1ec Cid : _CLIENT_ID +0x1f4 LpcReplySemaphore : _KSEMAPHORE +0x1f4 KeyedWaitSemaphore : _KSEMAPHORE +0x208 LpcReplyMessage : (null) +0x208 LpcWaitingOnPort : (null) +0x20c ImpersonationInfo : (null) +0x210 IrpList : _LIST_ENTRY [ 0x89b596f0 - 0x89b596f0 ] +0x218 TopLevelIrp : 0 +0x21c DeviceToVerify : (null) +0x220 ThreadsProcess : 0x89c2cd40 _EPROCESS +0x224 StartAddress : 0x7c8106e9 Void +0x228 Win32StartAddress : 0x7c930230 Void +0x228 LpcReceivedMessageId : 0x7c930230 +0x22c ThreadListEntry : _LIST_ENTRY [ 0x89c2ced0 - 0x89aeafd4 ] +0x234 RundownProtect : _EX_RUNDOWN_REF +0x238 ThreadLock : _EX_PUSH_LOCK +0x23c LpcReplyMessageId : 0 +0x240 ReadClusterSize : 7 +0x244 GrantedAccess : 0x1f03ff +0x248 CrossThreadFlags : 0 +0x248 Terminated : 0y0 +0x248 DeadThread : 0y0 +0x248 HideFromDebugger : 0y0 +0x248 ActiveImpersonationInfo : 0y0 +0x248 SystemThread : 0y0 +0x248 HardErrorsAreDisabled : 0y0 +0x248 BreakOnTermination : 0y0 +0x248 SkipCreationMsg : 0y0 +0x248 SkipTerminationMsg : 0y0 +0x24c SameThreadPassiveFlags : 0 +0x24c ActiveExWorker : 0y0 +0x24c ExWorkerCanWaitUser : 0y0 +0x24c MemoryMaker : 0y0 +0x250 SameThreadApcFlags : 0 +0x250 LpcReceivedMsgIdValid : 0y0 +0x250 LpcExitThreadCalled : 0y0 +0x250 AddressSpaceOwner : 0y0 +0x254 ForwardClusterOnly : 0 '' +0x255 DisablePageFaultClustering : 0 ''
the reason why 0x60 should be subtracted from the obtained value is that it is concatenated at the offset position of this structure. Subtracting the offset is the head of the real thread structure. We can also find the process to which it belongs through its members:
kd> dt _EPROCESS 0x89c2cd40 ntdll!_EPROCESS +0x000 Pcb : _KPROCESS +0x06c ProcessLock : _EX_PUSH_LOCK +0x070 CreateTime : _LARGE_INTEGER 0x01d7e39a`7e928c10 +0x078 ExitTime : _LARGE_INTEGER 0x0 +0x080 RundownProtect : _EX_RUNDOWN_REF +0x084 UniqueProcessId : 0x00000374 Void +0x088 ActiveProcessLinks : _LIST_ENTRY [ 0x89b12e28 - 0x89c3e7a0 ] +0x090 QuotaUsage : [3] 0x1a30 +0x09c QuotaPeak : [3] 0x1b20 +0x0a8 CommitCharge : 0x316 +0x0ac PeakVirtualSize : 0x3fc6000 +0x0b0 VirtualSize : 0x3f86000 +0x0b4 SessionProcessLinks : _LIST_ENTRY [ 0x89b12e54 - 0x89c3e7cc ] +0x0bc DebugPort : (null) +0x0c0 ExceptionPort : 0xe12aa720 Void +0x0c4 ObjectTable : 0xe172c078 _HANDLE_TABLE +0x0c8 Token : _EX_FAST_REF +0x0cc WorkingSetLock : _FAST_MUTEX +0x0ec WorkingSetPage : 0x14b84 +0x0f0 AddressCreationLock : _FAST_MUTEX +0x110 HyperSpaceLock : 0 +0x114 ForkInProgress : (null) +0x118 HardwareTrigger : 0 +0x11c VadRoot : 0x89caf290 Void +0x120 VadHint : 0x89caf290 Void +0x124 CloneRoot : (null) +0x128 NumberOfPrivatePages : 0x14c +0x12c NumberOfLockedPages : 0 +0x130 Win32Process : 0xe179b368 Void +0x134 Job : (null) +0x138 SectionObject : 0xe177e0b0 Void +0x13c SectionBaseAddress : 0x01000000 Void +0x140 QuotaBlock : 0x8055b200 _EPROCESS_QUOTA_BLOCK +0x144 WorkingSetWatch : (null) +0x148 Win32WindowStation : 0x00000038 Void +0x14c InheritedFromUniqueProcessId : 0x00000290 Void +0x150 LdtInformation : (null) +0x154 VadFreeHint : (null) +0x158 VdmObjects : (null) +0x15c DeviceMap : 0xe1005450 Void +0x160 PhysicalVadList : _LIST_ENTRY [ 0x89c2cea0 - 0x89c2cea0 ] +0x168 PageDirectoryPte : _HARDWARE_PTE_X86 +0x168 Filler : 0 +0x170 Session : 0xbadce000 Void +0x174 ImageFileName : [16] "svchost.exe" +0x184 JobLinks : _LIST_ENTRY [ 0x0 - 0x0 ] +0x18c LockedPagesList : (null) +0x190 ThreadListHead : _LIST_ENTRY [ 0x89c2cc44 - 0x89b5970c ] +0x198 SecurityPort : (null) +0x19c PaeTop : 0xbaf190e0 Void +0x1a0 ActiveThreads : 0x14 +0x1a4 GrantedAccess : 0x1f0fff +0x1a8 DefaultHardErrorProcessing : 0 +0x1ac LastThreadExitStatus : 0n0 +0x1b0 Peb : 0x7ffda000 _PEB +0x1b4 PrefetchTrace : _EX_FAST_REF +0x1b8 ReadOperationCount : _LARGE_INTEGER 0x3e +0x1c0 WriteOperationCount : _LARGE_INTEGER 0x9 +0x1c8 OtherOperationCount : _LARGE_INTEGER 0x195 +0x1d0 ReadTransferCount : _LARGE_INTEGER 0x3864a +0x1d8 WriteTransferCount : _LARGE_INTEGER 0x1c4 +0x1e0 OtherTransferCount : _LARGE_INTEGER 0x7506 +0x1e8 CommitChargeLimit : 0 +0x1ec CommitChargePeak : 0x16d4 +0x1f0 AweInfo : (null) +0x1f4 SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO +0x1f8 Vm : _MMSUPPORT +0x238 LastFaultCount : 0 +0x23c ModifiedPageCount : 1 +0x240 NumberOfVads : 0x80 +0x244 JobStatus : 0 +0x248 Flags : 0xd2800 +0x248 CreateReported : 0y0 +0x248 NoDebugInherit : 0y0 +0x248 ProcessExiting : 0y0 +0x248 ProcessDelete : 0y0 +0x248 Wow64SplitPages : 0y0 +0x248 VmDeleted : 0y0 +0x248 OutswapEnabled : 0y0 +0x248 Outswapped : 0y0 +0x248 ForkFailed : 0y0 +0x248 HasPhysicalVad : 0y0 +0x248 AddressSpaceInitialized : 0y10 +0x248 SetTimerResolution : 0y0 +0x248 BreakOnTermination : 0y1 +0x248 SessionCreationUnderway : 0y0 +0x248 WriteWatch : 0y0 +0x248 ProcessInSession : 0y1 +0x248 OverrideAddressSpace : 0y0 +0x248 HasAddressSpace : 0y1 +0x248 LaunchPrefetched : 0y1 +0x248 InjectInpageErrors : 0y0 +0x248 VmTopDown : 0y0 +0x248 Unused3 : 0y0 +0x248 Unused4 : 0y0 +0x248 VdmAllowed : 0y0 +0x248 Unused : 0y00000 (0) +0x248 Unused1 : 0y0 +0x248 Unused2 : 0y0 +0x24c ExitStatus : 0n259 +0x250 NextPageColor : 0xbd35 +0x252 SubSystemMinorVersion : 0 '' +0x253 SubSystemMajorVersion : 0x4 '' +0x252 SubSystemVersion : 0x400 +0x254 PriorityClass : 0x2 '' +0x255 WorkingSetAcquiredUnsafe : 0 '' +0x258 Cookie : 0x7f42570f
we easily found that this process belongs to svchost.exe.
Scheduling linked list
the scheduling linked list has 32 cycles, that is, the priority is 0-31. 0 is the lowest priority and 31 is the highest. The default priority is generally 8. Changing the priority is to unload from one circle and hang it on another circle. These 32 circles are threads under scheduling, including running and ready to run. For example, if there is only one CPU but 10 threads are running, at a certain time, the running threads are in KPCR and the other 9 are in these 32 cycles.
since there are 32 linked lists, there must be 32 chain headers. Let's take a look:
kd> dd KiDispatcherReadyListHead L50 80554820 80554820 80554820 80554828 80554828 80554830 80554830 80554830 80554838 80554838 80554840 80554840 80554840 80554848 80554848 80554850 80554850 80554850 80554858 80554858 80554860 80554860 80554860 80554868 80554868 80554870 80554870 80554870 80554878 80554878 80554880 80554880 80554880 80554888 80554888 80554890 80554890 80554890 80554898 80554898 805548a0 805548a0 805548a0 805548a8 805548a8 805548b0 805548b0 805548b0 805548b8 805548b8 805548c0 805548c0 805548c0 805548c8 805548c8 805548d0 805548d0 805548d0 805548d8 805548d8 805548e0 805548e0 805548e0 805548e8 805548e8 805548f0 805548f0 805548f0 805548f8 805548f8 80554900 80554900 80554900 80554908 80554908 80554910 80554910 80554910 80554918 80554918 80554920 00000000 00000000 00000000 00000000 80554930 00000000 00000000 00000000 00000000 80554940 00000000 00000000 00000000 00000000 80554950 00000000 e1006000 00000000 00000000
each member is a two-way linked list. If you find it carefully. Now the address of each member is the same as the current address, and the front node is the same as the back node, which means that there is no thread scheduling the linked list at present. This is because when we interrupt the operating system debugging input command, it will suspend all threads of the operating system, so they are waiting in the linked list. If you really remove the thread structure from the above, the thread will really not run. Therefore, threads can never be hidden.
different Windows versions are slightly different. XP has only 33 cycles in total, that is, there is only one array above and only one multi-core. Win7 is the same. There is only one circle. If it is 64 bit, there are 64 circles. The server version of KiWaitListHead is only one in the whole system, but the array of kidispatcher readylisthead has several CPU groups.
Simulate thread switching
before formally learning Windows thread switching, we need to read a code and click This blueplay cloud link Download. The password is hwso. You can open it directly with VC6.0.
Key structure
let's take a look at the structure of simulated threads:
typedef struct { char* name; //Thread name int Flags; //Thread state int SleepMillsecondDot; //Dormancy time void* initialStack; //Start position of thread stack void* StackLimit; //Thread stack bounds void* KernelStack; //The current position of the thread stack, that is, ESP void* lpParameter; //Parameters of thread function void(*func)(void* lpParameter); //Thread function }GMThread_t;
the key parameters related to thread switching are the following items:
void* initialStack; //Start position of thread stack void* StackLimit; //Thread stack bounds void* KernelStack; //The current position of the thread stack, that is, ESP
Simulation scheduling linked list
thread switching has a scheduling linked list. How do we handle it? We can see the following code:
//List of threads GMThread_t GMThreadList[MAXGMTHREAD] = {NULL, 0};
creating a thread is to create a structure and hang it in the array. At this time, the thread state is create. We see that the main function creates a thread with a line of code:
RegisterGMThread("Thread1", Thread1, NULL);
follow in and have a look. The specific functions are as follows:
//Register a function as a separate thread for execution int RegisterGMThread(char* name, void(*func)(void*lpParameter), void* lpParameter) { int i; for (i = 1; GMThreadList[i].name; i++) { if (0 == _stricmp(GMThreadList[i].name, name)) { break; } } initGMThread(&GMThreadList[i], name, func, lpParameter); return (i & 0x55AA0000); }
you can see that it is to find out whether there is a thread. If so, initialize the structure with initGMThread and follow it:
//Initialization thread information void initGMThread(GMThread_t* GMThreadp, char* name, void (*func)(void* lpParameter), void* lpParameter) { unsigned char* StackPages; unsigned int* StackDWordParam; GMThreadp->Flags = GMTHREAD_CREATE; GMThreadp->name = name; GMThreadp->func = func; GMThreadp->lpParameter = lpParameter; StackPages = (unsigned char*)VirtualAlloc(NULL, GMTHREADSTACKSIZE, MEM_COMMIT, PAGE_READWRITE); ZeroMemory(StackPages, GMTHREADSTACKSIZE); GMThreadp->initialStack = StackPages + GMTHREADSTACKSIZE; StackDWordParam = (unsigned int* GMThreadp->initialStack; //Push PushStack(&StackDWordParam, (unsigned int)GMThreadp ; //Parameters required for the startup function PushStack(&StackDWordParam, (unsigned int)0); / You wonder why you put 0 here. In short, it is to balance the stack, and secondly, it is because of the tone startup It's a parameter, pop startup->eip after esp That is, here, after the function mov ebp,esp οΌthen ebp+8 Is the default parameter setting of the function. PushStack(&StackDWordParam, (unsigned int GMThreadStartup); PushStack(&StackDWordParam, (unsigned int)5); //push ebp PushStack(&StackDWordParam, (unsigned int)7); //push edi PushStack(&StackDWordParam, (unsigned int)6); //push esi PushStack(&StackDWordParam, (unsigned int)3); //push ebx PushStack(&StackDWordParam, (unsigned int)2); //push ecx PushStack(&StackDWordParam, (unsigned int)1); //push edx PushStack(&StackDWordParam, (unsigned int)0); //push eax //Stack top of current thread GMThreadp->KernelStack = StackDWordParam; GMThreadp->Flags = GMTHREAD_READY; return; }
we can see that it first initializes the so-called thread structure, hangs it in the array, allocates a memory as the stack, and then performs a series of stack operations.
Initialize thread stack
the initGMThread function contains a series of pushstacks. In fact, this is what we call the initialization thread stack operation. The schematic diagram is as follows:
Simulate thread switching
this is the core of simulating thread switching. Let's take a look at the code:
//Switch thread __declspec(naked) void SwitchContext(GMThread_t* SrcGMThreadp, GMThread_t* DstGMThreadp) { __asm { push ebp mov ebp, esp push edi push esi push ebx push ecx push edx push eax mov esi, SrcGMThreadp mov edi, DstGMThreadp mov [esi+GMThread_t.KernelStack], esp //Classic thread switching, another thread Resurrection mov esp, [edi+GMThread_t.KernelStack] pop eax //The esp has been switched to the new thread stack. This stack pop eax, and you get the saved esp (initialized esp / runtime esp) pop edx pop ecx pop ebx pop esi pop edi pop ebp ret //Pop the value at the top of the stack into the eip, where the startup address is popped into the eip } }
as can be seen from the above code, the above code presses the values of the stack we define one by one, then replaces the stack values of the new thread in turn, and then bounces the value of the new stack back to the register to continue execution. This is the so-called thread switching. Let's see who called this function:
//This function will give up the cpu and reselect a thread from the queue for execution void Scheduling(void) { int i; int TickCount; GMThread_t* SrcGMThreadp; GMThread_t* DstGMThreadp; TickCount = GetTickCount(); SrcGMThreadp = &GMThreadList[CurrentThreadIndex]; DstGMThreadp = &GMThreadList[0]; for (i = 1; GMThreadList[i].name; i++) { if (GMThreadList[i].Flags & GMTHREAD_SLEEP) { if (TickCount > GMThreadList[i].SleepMillsecondDot) { GMThreadList[i].Flags = GMTHREAD_READY; } } if (GMThreadList[i].Flags & GMTHREAD_READY) { DstGMThreadp = &GMThreadList[i]; break; } } CurrentThreadIndex = DstGMThreadp - GMThreadList; SwitchContext(SrcGMThreadp, DstGMThreadp); return; }
let's see who called this function:
void GMSleep(int MilliSeconds) { GMThread_t* GMThreadp; GMThreadp = &GMThreadList[CurrentThreadIndex]; if (GMThreadp->Flags != 0) { GMThreadp->Flags = GMTHREAD_SLEEP; GMThreadp->SleepMillsecondDot = GetTickCount() + MilliSeconds; } Scheduling(); return; }
and this function is actively called by the thread:
void Thread1(void*) { while(1){ printf("Thread1\n"); GMSleep(500); } }
to sum up, threads are not passively switched, but actively give up the CPU. Thread switching does not use TSS to save registers, but uses the stack. The process of thread switching is the process of stack switching.
we can see the effect. Since the thread is simulated by itself, we can see only one thread in the task manager, that is, the main thread created by the operating system for us:
Thread switching
before, we introduced simulating windows thread switching. In this project, we introduced an important function: SwitchContext. Calling this function will lead to thread switching. Windows also has a similar function: KiSwapContext. As long as this function is called, thread switching will be triggered. Please analyze this function by yourself. The part about thread switching will be revealed in the next article.
Next
process threads -- thread switching (Part 2)