进程线程篇——线程切换(上)

写在前面 ??此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我。

你如果是从中间插过来看的,请仔细阅读 羽夏看Win系统内核——简述 ,方便学习本教程。
??看此教程之前,问几个问题,基础知识储备好了吗?保护模式篇学会了吗?系统调用篇学会了吗?练习做完了吗?没有的话就不要继续了。
?
华丽的分割线
?
练习及参考
本次答案均为参考,可以与我的答案不一致,但必须成功通过。代码在折叠区,而思考原因解释将会在文章内部讲解。
1?? 断链进程结构体,实现隐藏,并思考为什么断链进程为什么还能够执行。
点击查看应用代码
#include "stdafx.h" #include #include #include //操作码:0x0-0x7FF 被保留,0x800-0xFFF 可用 #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[]) { //获取驱动链接对象句柄 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("访问驱动符号链接失败!"); goto endproc; }DWORD pid; DWORD outBuffer; DWORD m[2]; DWORD re; puts("请输入需要隐藏程序的 pid :"); scanf("%d",&pid); if (pid) { if (DeviceIoControl(g_Device,Hide,&pid,sizeof(DWORD), outBuffer,sizeof(DWORD),&re,NULL)) { puts("隐藏命令发送成功,请查看程序是否隐藏……"); } puts("检查任务管理器,按任意键恢复隐藏!"); m[0]=GetCurrentProcessId(); m[1]=outBuffer; if (DeviceIoControl(g_Device,_Show,m,sizeof(DWORD)*2, outBuffer,sizeof(DWORD),&re,NULL)) { puts("恢复命令发送成功,请查看程序是否显示……"); } } else { puts("pid 非法!"); }endproc:CloseHandle(g_Device); system("pause"); return 0; }

?
点击查看驱动代码
#include #include 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); //假设 DriverEntry 函数中的 dobj 变量声明为全局的,为什么不使用这个删除设备对象 DbgPrint("卸载成功!!!"); }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("断链成功!!!"); break; case Show:RtlMoveMemory(uRead, pIoBuffer, 8); PsLookupProcessByProcessId(uRead[0], &eProcess); //第一个成员:pidlethis = (LIST_ENTRY*)((ULONG)eProcess + 0x88); //自身的pid ble= (LIST_ENTRY*)((ULONG)uRead[1] + 0x88); fle = lethis->Flink; lethis->Flink = ble; ble->Blink = lethis; ble->Flink = fle; fle->Blink = ble; DbgPrint("恢复成功!!!"); 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("加载成功!!!"); return STATUS_SUCCESS; }

?
点击查看答案
??先给个效果图来看看:
进程线程篇——线程切换(上)
文章图片

??注释中有一个问题:假设DriverEntry函数中的dobj变量声明为全局的,为什么不使用这个删除设备对象。你可以试试,答案是不可以,会导致蓝屏,原因这个东西是使用的分页内存,而这个卸载驱动的线程是不能使用分页内存的。
2?? 使用DebugPort清零实现反调试。
点击查看应用代码
#include "stdafx.h" #include #include #include //操作码:0x0-0x7FF 被保留,0x800-0xFFF 可用 #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[]) { //获取驱动链接对象句柄 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("访问驱动符号链接失败!"); goto endproc; }DWORD pid; DWORD outBuffer; DWORD re; puts("请输入需要反调试保护程序的 pid :"); scanf("%d",&pid); if (pid) { if (DeviceIoControl(g_Device,Hide,&pid,sizeof(DWORD), &outBuffer,sizeof(DWORD),&re,NULL)) { puts("反调试保护命令发送成功……"); } } else { puts("pid 非法!"); }endproc:CloseHandle(g_Device); system("pause"); return 0; } }

?
点击查看驱动代码
#include #include 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("卸载成功!!!"); }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("断链成功!!!"); 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("加载成功!!!"); return STATUS_SUCCESS; }

3?? 如何判断一个进程是否为GUI线程。
点击查看答案
??我预先在虚拟机开启一个DebugView,然后在WinDbug开始输入指令,遍历进程:
kd> !process 0 0 ****NT ACTIVE PROCESS DUMP**** Failed to get VadRoot PROCESS 89c258c0SessionId: 0Cid: 0758Peb: 7ffd3000ParentCid: 05f4 DirBase: 13ac01a0ObjectTable: e1d63088HandleCount:57. Image: Dbgview.exe

??然后再看看这个进程信息:
kd> !process 89c258c0 Failed to get VadRoot PROCESS 89c258c0SessionId: 0Cid: 0758Peb: 7ffd3000ParentCid: 05f4 DirBase: 13ac01a0ObjectTable: e1d63088HandleCount:57. Image: Dbgview.exe VadRoot 00000000 Vads 0 Clone 0 Private 192. Modified 7. Locked 0. DeviceMap e1638c88 Tokene176a7d0 ElapsedTime00:00:05.476 UserTime00:00:00.010 KernelTime00:00:00.010 QuotaPoolUsage[PagedPool]0 QuotaPoolUsage[NonPagedPool]0 Working Set Sizes (now,min,max)(974, 50, 345) (3896KB, 200KB, 1380KB) PeakWorkingSetSize974 VirtualSize30 Mb PeakVirtualSize32 Mb PageFaultCount1030 MemoryPriorityFOREGROUND BasePriority8 CommitCharge392THREAD 898b0020Cid 0758.0760Teb: 7ffdf000 Win32Thread: e1e6d2c8 WAIT: (UserRequest) UserMode Non-Alertable
89b0b6a8SynchronizationEvent
89b43930SynchronizationEvent
Not impersonating
DeviceMape1638c88
Owning Process00000000Image:
Attached Process89c258c0Image:Dbgview.exe
Wait Start TickCount6228Ticks: 1 (0:00:00:00.010)
Context Switch Count225IdealProcessor: 0LargeStack
UserTime00:00:00.000
KernelTime00: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 80501cd6nt!KiSwapContext+0x2e (FPO: [Uses EBP] [0,0,4])
b8137980 804faae2nt!KiSwapThread+0x46 (FPO: [0,0,0])
b81379b8 805b7418nt!KeWaitForMultipleObjects+0x284 (FPO: [Non-Fpo])
b8137d48 8053e638nt!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 89cca928Cid 0758.0764Teb: 7ffde000 Win32Thread: 00000000 WAIT: (DelayExecution) UserMode Alertable
89ccaa18NotificationTimer
Not impersonating
DeviceMape1638c88
Owning Process00000000Image:
Attached Process89c258c0Image:Dbgview.exe
Wait Start TickCount6228Ticks: 1 (0:00:00:00.010)
Context Switch Count13IdealProcessor: 0
UserTime00:00:00.000
KernelTime00: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 80501cd6nt!KiSwapContext+0x2e (FPO: [Uses EBP] [0,0,4])
b873dce0 804fa79fnt!KiSwapThread+0x46 (FPO: [0,0,0])
b873dd0c 8060db19nt!KeDelayExecutionThread+0x1c9 (FPO: [Non-Fpo])
b873dd54 8053e638nt!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])



??可以里面有两个进程,其中有一个是GUI线程,一个为普通线程,那么是如何知道的呢?我们查看它们的ServiceTable
kd> dt _KTHREAD 898b0020 ntdll!_KTHREAD+0x0e0 ServiceTable: 0x80553f60 Void
kd> dt _KTHREAD 89cca928
ntdll!_KTHREAD
+0x0e0 ServiceTable: 0x80553fa0 Void
kd> ddKeServiceDescriptorTable
80553fa080502b8c 00000000 0000011c 80503000
80553fb000000000 00000000 00000000 00000000
80553fc000000000 00000000 00000000 00000000
80553fd000000000 00000000 00000000 00000000
kd> ddKeServiceDescriptorTableShadow
80553f6080502b8c 00000000 0000011c 80503000
80553f70bf999b80 00000000 0000029b bf99a890
80553f8000000000 00000000 00000000 00000000
80553f9000000000 00000000 00000000 00000000



??KeServiceDescriptorTableShadowKeServiceDescriptorTable没有的有关GUI线程相关的服务表,是不是很容易判断出来哪个是GUI线程了吗?是KeServiceDescriptorTableShadowGUI线程,是KeServiceDescriptorTable则为普通线程。
4?? 断链线程结构体,实现隐藏,并思考为什么断链线程为什么还能够执行。
点击查看应用代码
#include "stdafx.h" #include #include #include //操作码:0x0-0x7FF 被保留,0x800-0xFFF 可用 #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:线程老子还活着!!!\n",i++); Sleep(1500); } return 0; }int main(int argc, char* argv[]) { //获取驱动链接对象句柄 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("线程创建失败!!!"); goto endproc; } system("pause"); if (DeviceIoControl(g_Device,Hide,&tid,sizeof(DWORD), outBuffer,sizeof(DWORD),&re,NULL)) { puts("隐藏命令正在发送,请查看线程数是否减少……"); } system("pause"); } else { puts("访问驱动符号链接失败!"); }endproc:CloseHandle(g_Device); system("pause"); return 0; }

?
点击查看驱动代码
#include #include 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("卸载成功!!!"); }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("断链成功!!!"); 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("加载成功!!!"); return STATUS_SUCCESS; }

?
点击查看答案
??先给个效果图来看看:
进程线程篇——线程切换(上)
文章图片

??剩下的就没难点和疑点了,自己看折叠代码分析。
等待链表与调度链表 ??进程结构体EPROCESS链表,里面有两处圈着当前进程所有的线程。对进程断链,程序可以正常运行,原因是CPU执行与调度是基于线程的,进程断链只是影响一些遍历系统进程的API,并不会影响程序执行。对线程断链也是一样的,断链后在Windbg或者OD中无法看到被断掉的线程,但并不影响其执行,原因是CPU调度线程的时候压根不用这个链表。
??线程有3种状态:就绪(ready)、等待(wait)、运行(running)。
??正在运行中的线程存储在KPCR中,就绪和等待的线程全在另外的33个链表中。一个等待链表,32个就绪链表。这些链表都使用了_KTHREAD + 0x060这个位置。这个一个位置,两个名字,如下所示。也就是说,线程在某一时刻,只能属于其中一个圈。
+0x060 WaitListEntry: _LIST_ENTRY +0x060 SwapListEntry: _SINGLE_LIST_ENTRY

等待链表
??线程调用了Sleep或者WaitForSingleObject等函数时,就挂到一个链表之中,它是等待链表。学习等待链表之前,我们需要知道一个全局变量:
kd> dd KiWaitListHead 80553d8889b59540 89bb0300 00000010 00000000 80553d98b6de6b92 a787ea13 01000013 ffdff980 80553da8ffdff980 80500df0 00000000 00006029 80553db800000000 00000000 80553dc0 80553dc0 80553dc800000000 00000000 80553dd0 80553dd0 80553dd800000000 00000000 00000000 89da7da8 80553de800000000 00000000 00040001 00000000 80553df889da7e18 89da7e18 00000001 00000000

??这个全局变量存储的是双向链表,我们测试一下:
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 ''

??为什么拿到的值还要减去0x60,是因为它串在这个结构体的偏移位置,需要减去偏移才是真正线程结构体的头部,我们还可以通过它其中的成员找到它所属的进程:
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

??我们很轻松地找到了,这个进程属于svchost.exe这个程序。
调度链表
??调度链表有32个圈,就是优先级是0-31,0为最低优先级,31为最高,默认优先级一般是8。改变优先级就是从一个圈里面卸下来挂到另外一个圈上,这32个圈是正在调度中的线程,包括正在运行的和准备运行的。比如:只有一个CPU但有10个线程在运行,那么某一时刻,正在运行的线程在KPCR中,其他9个在这32个圈中。
??既然有32个链表,就要有32个链表头,我们来看一下:
kd> dd KiDispatcherReadyListHead L50 8055482080554820 80554820 80554828 80554828 8055483080554830 80554830 80554838 80554838 8055484080554840 80554840 80554848 80554848 8055485080554850 80554850 80554858 80554858 8055486080554860 80554860 80554868 80554868 8055487080554870 80554870 80554878 80554878 8055488080554880 80554880 80554888 80554888 8055489080554890 80554890 80554898 80554898 805548a0805548a0 805548a0 805548a8 805548a8 805548b0805548b0 805548b0 805548b8 805548b8 805548c0805548c0 805548c0 805548c8 805548c8 805548d0805548d0 805548d0 805548d8 805548d8 805548e0805548e0 805548e0 805548e8 805548e8 805548f0805548f0 805548f0 805548f8 805548f8 8055490080554900 80554900 80554908 80554908 8055491080554910 80554910 80554918 80554918 8055492000000000 00000000 00000000 00000000 8055493000000000 00000000 00000000 00000000 8055494000000000 00000000 00000000 00000000 8055495000000000 e1006000 00000000 00000000

??其中每一个成员都是一个双向链表。如果你心细地发现。现在每个成员的地址都和当前地址一样,前结点和后结点一样,这就是说明当前没有在调度链表的线程。这是因为我们中断操作系统调试输入命令的时候,它会把操作系统的所有线程挂起,所以都在等待链表中。如果你真把线程结构体从上面几个摘掉的话,这线程真的就跑不起来了。由此可知,线程是永远没法隐藏的。
??不同的Windows版本略有不同,XP只有一共33个圈,也就是说上面这个数组只有一个,多核也只有一个。Win7也是一样的只有一个圈,如果是64位的,那就有64个圈。而服务器版本KiWaitListHead整个系统只有一个,但KiDispatcherReadyListHead这个数组有几个CPU就有几组。
模拟线程切换 ??在正式学习Windows线程切换,我们需要读懂一份代码,点击 此蓝奏云链接 下载,密码为hwso,可以直接用VC6.0直接打开。
关键结构体
??我们来看一下模拟线程的结构体:
typedef struct { char* name; //线程名 int Flags; //线程状态 int SleepMillsecondDot; //休眠时间 void* initialStack; //线程堆栈起始位置 void* StackLimit; //线程堆栈界限 void* KernelStack; //线程堆栈当前位置,也就是ESP void* lpParameter; //线程函数的参数 void(*func)(void* lpParameter); //线程函数 }GMThread_t;

??跟线程切换相关的最关键的参数是如下几个项目:
void* initialStack; //线程堆栈起始位置 void* StackLimit; //线程堆栈界限 void* KernelStack; //线程堆栈当前位置,也就是ESP

模拟调度链表
??线程切换有调度链表,我们是如何处理的呢,我们可以看到如下代码:
//线程的列表 GMThread_t GMThreadList[MAXGMTHREAD] = {NULL, 0};

??所谓创建线程,就是创建一个结构体,并且挂到这个数组中此时的线程状态为创建。我们看到主函数用行代码实现创建线程:
RegisterGMThread("Thread1", Thread1, NULL);

??跟进去看一下,具体函数内容如下:
//将一个函数注册为单独线程执行 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); }

??可以看到,它是查找有没有线程,如果有就用initGMThread初始化这个结构体,跟进去看看:
//初始化线程的信息 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; //入栈 PushStack(&StackDWordParam, (unsigned int)GMThreadp; //startup 函数所需要的参数 PushStack(&StackDWordParam, (unsigned int)0); /你好奇这里为什么放0,简单来说是为了平衡堆栈,其次是因为调startup是要参数的,pop startup->eip 后 esp也就是这里,函数后会把 mov ebp,esp ,然后 ebp+8 就是函数默认的参数置。 PushStack(&StackDWordParam, (unsigned int GMThreadStartup); PushStack(&StackDWordParam, (unsigned int)5); //pushebp PushStack(&StackDWordParam, (unsigned int)7); //pushedi PushStack(&StackDWordParam, (unsigned int)6); //pushesi PushStack(&StackDWordParam, (unsigned int)3); //pushebx PushStack(&StackDWordParam, (unsigned int)2); //pushecx PushStack(&StackDWordParam, (unsigned int)1); //pushedx PushStack(&StackDWordParam, (unsigned int)0); //pusheax //当前线程的栈顶 GMThreadp->KernelStack = StackDWordParam; GMThreadp->Flags = GMTHREAD_READY; return; }

??我们可以看到,它首先初始化所谓的线程结构体,挂到数组中,分配一个内存作为堆栈,然后进行一系列的堆栈操作。
初始化线程堆栈
??initGMThread这个函数里面有一系列的PushStack,其实这个就是我们所谓的初始化线程堆栈操作,示意图如下:
进程线程篇——线程切换(上)
文章图片

模拟线程切换
??这个就是我们模拟线程切换的核心,我们看一下代码:
//切换线程 __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 eaxmov esi, SrcGMThreadp mov edi, DstGMThreadp mov [esi+GMThread_t.KernelStack], esp //经典线程切换,另外一个线程复活 mov esp, [edi+GMThread_t.KernelStack] pop eax//esp在上面已经切换到新的线程栈中,这个栈pop eax,拿到的就是保存的esp(初始化的esp/运行时esp) pop edx pop ecx pop ebx pop esi pop edi pop ebp ret//把栈顶的值弹到eip中,在这里弹出的就是startup的地址到eip中 } }

??从上面的代码看出,上面的代码把我们定义堆栈的值挨个压入,然后把新线程的堆栈的值依次替换,然后把新堆栈的值弹回给寄存器继续执行,这就是所谓了线程切换。那么我们看看是谁调用了这个函数:
//这个函数会让出cpu,从队列里重新选择一个线程执行 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; }

??我们再看看是谁调用这个函数:
void GMSleep(int MilliSeconds) { GMThread_t* GMThreadp; GMThreadp = &GMThreadList[CurrentThreadIndex]; if (GMThreadp->Flags != 0) { GMThreadp->Flags = GMTHREAD_SLEEP; GMThreadp->SleepMillsecondDot = GetTickCount() + MilliSeconds; }Scheduling(); return; }

??而这个函数又是线程主动调用的:
void Thread1(void*) { while(1){ printf("Thread1\n"); GMSleep(500); } }

??综上可知:线程不是被动切换的,而是主动让出CPU。线程切换并没有使用TSS来保存寄存器,而是使用堆栈。线程切换的过程就是堆栈切换的过程。
??我们可以看一下效果,由于线程是自己模拟的,所以在任务管理器看到只是一个线程,也就是操作系统帮我们创建的主线程:
进程线程篇——线程切换(上)
文章图片

线程切换 ??之前我们介绍了模拟Windows线程切换,在这个项目里面我们介绍了一个重要的函数:SwitchContext,只要调用这个函数,就会导致线程切换。Windows也有类似的函数:KiSwapContext,只要调用这个函数,就会触发线程切换。这个函数请自行分析,有关线程切换的部分将会在下一篇进行揭晓。
下一篇 【进程线程篇——线程切换(上)】??进程线程篇——线程切换(下)

    推荐阅读