寫在前面
此係列是本人一個字一個字碼出來的,包括示例和實驗截圖。由於系統核心的複雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閒錢,可以打賞支援我的創作。如想轉載,請把我的轉載資訊附在文章後面,並宣告我的個人資訊和本人部落格地址即可,但必須事先通知我。
你如果是從中間插過來看的,請仔細閱讀 羽夏看Win系統核心——簡述 ,方便學習本教程。
看此教程之前,問幾個問題,基礎知識儲備好了嗎?保護模式篇學會了嗎?系統呼叫篇學會了嗎?練習做完了嗎?沒有的話就不要繼續了。
? 華麗的分割線 ?
練習及參考
本次答案均為參考,可以與我的答案不一致,但必須成功通過。程式碼在摺疊區,而思考原因解釋將會在文章內部講解。
1️⃣ 斷鏈程式結構體,實現隱藏,並思考為什麼斷鏈程式為什麼還能夠執行。
? 點選檢視應用程式碼 ?
#include "stdafx.h"
#include <windows.h>
#include <winioctl.h>
#include <stdlib.h>
//操作碼: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 <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); //假設 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); //第一個成員:pid
lethis = (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 <windows.h>
#include <winioctl.h>
#include <stdlib.h>
//操作碼: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 <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("解除安裝成功!!!");
}
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 89c258c0 SessionId: 0 Cid: 0758 Peb: 7ffd3000 ParentCid: 05f4
DirBase: 13ac01a0 ObjectTable: e1d63088 HandleCount: 57.
Image: Dbgview.exe
然後再看看這個程式資訊:
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 392
THREAD 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])
可以裡面有兩個程式,其中有一個是GUI
執行緒,一個為普通執行緒,那麼是如何知道的呢?我們檢視它們的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 00000000
kd> dd KeServiceDescriptorTableShadow
80553f60 80502b8c 00000000 0000011c 80503000
80553f70 bf999b80 00000000 0000029b bf99a890
80553f80 00000000 00000000 00000000 00000000
80553f90 00000000 00000000 00000000 00000000
KeServiceDescriptorTableShadow
有KeServiceDescriptorTable
沒有的有關GUI
執行緒相關的服務表,是不是很容易判斷出來哪個是GUI
執行緒了嗎?是KeServiceDescriptorTableShadow
為GUI
執行緒,是KeServiceDescriptorTable
則為普通執行緒。
4️⃣ 斷鏈執行緒結構體,實現隱藏,並思考為什麼斷鏈執行緒為什麼還能夠執行。
? 點選檢視應用程式碼 ?
#include "stdafx.h"
#include <windows.h>
#include <winioctl.h>
#include <stdlib.h>
//操作碼: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 <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("解除安裝成功!!!");
}
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
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
這個全域性變數儲存的是雙向連結串列,我們測試一下:
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
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
其中每一個成員都是一個雙向連結串列。如果你心細地發現。現在每個成員的地址都和當前地址一樣,前結點和後結點一樣,這就是說明當前沒有在排程連結串列的執行緒。這是因為我們中斷作業系統除錯輸入命令的時候,它會把作業系統的所有執行緒掛起,所以都在等待連結串列中。如果你真把執行緒結構體從上面幾個摘掉的話,這執行緒真的就跑不起來了。由此可知,執行緒是永遠沒法隱藏的。
不同的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); //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
//當前執行緒的棧頂
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 eax
mov 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
,只要呼叫這個函式,就會觸發執行緒切換。這個函式請自行分析,有關執行緒切換的部分將會在下一篇進行揭曉。
下一篇
程式執行緒篇——執行緒切換(下)