進程線程篇——總結與提升
- 2022 年 2 月 3 日
- 筆記
- Win系統內核, 羽夏看Win系統內核
寫在前面
此系列是本人一個字一個字碼出來的,包括示例和實驗截圖。由於系統內核的複雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閑錢,可以打賞支持我的創作。如想轉載,請把我的轉載信息附在文章後面,並聲明我的個人信息和本人博客地址即可,但必須事先通知我。
你如果是從中間插過來看的,請仔細閱讀 羽夏看Win系統內核——簡述 ,方便學習本教程。
看此教程之前,問幾個問題,基礎知識儲備好了嗎?保護模式篇學會了嗎?系統調用篇學會了嗎?練習做完了嗎?沒有的話就不要繼續了。
🔒 華麗的分割線 🔒
模擬線程切換分析
之前我們詳細分析了模擬線程切換的本質,裏面模擬了兩處線程切換,一處模擬了時鐘切換,一處模擬了主動切換,也就是通過API的方式進行的切換。
我們來看一下模擬時鐘切換的部分:
while(TRUE) {
Sleep(20);
Scheduling();
}
上面的Sleep(20)就是代表我每20ms繼續調用Scheduling()來實現線程切換,也就是說時鐘周期是20ms。
模擬通過API的方式進行的主動切換的就是如下函數:
void GMSleep(int MilliSeconds)
{
GMThread_t* GMThreadp;
GMThreadp = &GMThreadList[CurrentThreadIndex];
if (GMThreadp->Flags != 0) {
GMThreadp->Flags = GMTHREAD_SLEEP;
GMThreadp->SleepMillsecondDot = GetTickCount() + MilliSeconds;
}
Scheduling();
return;
}
而我們的每一個線程,都會調用GMSleep這個函數,這個函數模擬的就是模擬調用WinAPI:
void Thread1(void*) {
while(1){
printf("Thread1\n");
GMSleep(500);
}
}
我假設上面的你都搞明白了。那麼,接着上篇的課後練習,怎樣實現線程的掛起和恢復呢?這個問題你思考了嗎?沒思考的話就不要繼續了。
線程掛起恢複分析實現
為了實現線程的掛起恢複函數,我們應該把思路放在它是如何實現線程調度上。讓線程掛起,無非就是不讓給這個線程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;
}
可以看到,只要模擬線程是GMTHREAD_READY狀態,它就會調換線程,也就是說我們改一改這個標誌線程狀態的參數一改,就能實現我想要的掛起和恢復,我們來實現它。
首先,我們在ThreadSwitch.h來添加兩處聲明:
bool SyspendThread(char* Name);
bool ResumeThread(char* Name);
然後我們對它進行實現:
bool SyspendThread(char* Name)
{
for (int i=1;i<MAXGMTHREAD;i++)
{
if (!strcmp(Name,GMThreadList[i].name))
{
GMThreadList[i].Flags = GMTHREAD_EXIT;
return true;
}
}
return false;
}
bool ResumeThread(char* Name)
{
for (int i=1;i<MAXGMTHREAD;i++)
{
if (!strcmp(Name,GMThreadList[i].name))
{
GMThreadList[i].Flags = GMTHREAD_READY;
return true;
}
}
return false;
}
為了方便觀察,我們只保留了Thread1和Thread2,最終main.cpp的內容如下:
#include "stdafx.h"
#include <windows.h>
#include "ThreadSwitch.h"
extern int CurrentThreadIndex;
extern GMThread_t GMThreadList[MAXGMTHREAD];
void Thread1(void*) {
while(1){
printf("Thread1\n");
GMSleep(500);
}
}
void Thread2(void*) {
while (1) {
printf("Thread2\n");
GMSleep(500);
}
}
int main()
{
RegisterGMThread("Thread1", Thread1, NULL);
RegisterGMThread("Thread2", Thread2, NULL);
//SyspendThread("Thread2");
//ResumeThread("Thread2");
while(TRUE) {
Sleep(20);
Scheduling();
}
return 0;
}
我們接下來以動圖的形式進行演示:
上一篇留下的第0題到此就解決完畢了。
SwapContext 分析
本分析對於逆向水平有一定的要求,如果不行的話建議做多一些有關IDA的CrakeMe練習,分析流程以做熟悉和練習。不過沒有經驗也無所謂,仔細看看本部分,回去重新做一遍。
當你找到這個函數的是哦胡,你看到的應該是下面的情況:
SwapContext proc near ; CODE XREF: KiUnlockDispatcherDatabase(x)+72↑p
; KiSwapContext(x)+29↑p ...
or cl, cl
mov byte ptr es:[esi+2Dh], 2
pushf
loc_46A8E8: ; CODE XREF: KiIdleLoop()+5A↓j
mov ecx, [ebx] ; ebx = KPCR
cmp dword ptr [ebx+994h], 0
push ecx
jnz loc_46AA2D
cmp ds:_PPerfGlobalGroupMask, 0
jnz loc_46AA04
loc_46A905: ; CODE XREF: SwapContext+12C↓j
; SwapContext+13D↓j ...
mov ebp, cr0
mov edx, ebp
mov cl, [esi+2Ch]
mov [ebx+50h], cl
既然分析它的參數是什麼,首先我們得知道它們是如何傳參的,傳的是什麼,這樣我們才能解決上一篇留下的思考題。
然後我們找到它的來自KiSwapContext的一個引用,結果如下:
; __fastcall KiSwapContext(x)
@KiSwapContext@4 proc near ; CODE XREF: KiSwapThread()+41↑p
var_200FE4 = dword ptr -200FE4h
var_10 = dword ptr -10h
var_C = dword ptr -0Ch
var_8 = dword ptr -8
var_4 = dword ptr -4
sub esp, 10h
mov [esp+10h+var_4], ebx
mov [esp+10h+var_8], esi
mov [esp+10h+var_C], edi
mov [esp+10h+var_10], ebp
mov ebx, ds:0FFDFF01Ch ; ebx = &_KPCR
mov esi, ecx ; esi = ecx = NextReadyThread
mov edi, [ebx+124h]
mov [ebx+124h], esi
mov cl, [edi+58h]
call SwapContext
mov ebp, [esp+10h+var_10]
mov edi, [esp+10h+var_C]
mov esi, [esp+10h+var_8]
mov ebx, [esp+10h+var_4]
add esp, 10h
retn
@KiSwapContext@4 endp
但是這樣也看不出來參數是什麼,我們再往上找一級:
@KiSwapThread@0 proc near ; CODE XREF: KiAttachProcess(x,x,x,x)+F2↑p
; KeDelayExecutionThread(x,x,x):loc_42279A↑p ...
mov edi, edi
push esi
push edi
db 3Eh
mov eax, ds:0FFDFF020h
mov esi, eax
我們找到了KiSwapThread這個函數,明顯望文生義就是用來切換線程用的內核函數。前面我們知道0xFFDFF000這個地址存放的就是KPCR結構體的地址,那麼根據結構體可以知道0xFFDFF020存放的就是KPRCB這個結構體的首地址,最終分析得到如下結果:
@KiSwapThread@0 proc near ; CODE XREF: KiAttachProcess(x,x,x,x)+F2↑p
; KeDelayExecutionThread(x,x,x):loc_42279A↑p ...
mov edi, edi
push esi
push edi
db 3Eh
mov eax, ds:0FFDFF020h
mov esi, eax
mov eax, [esi+_KPRCB.NextThread] ; eax = NextThread
test eax, eax ; 測一測有沒有
mov edi, [esi+_KPRCB.CurrentThread] ; edi = CurrentThread
jz short loc_429CAC ; 沒有 NextThread 就跳
and [esi+_KPRCB.NextThread], 0 ; 把 NextThread 清零
jmp short loc_429CCF
; ---------------------------------------------------------------------------
loc_429CAC: ; CODE XREF: KiSwapThread()+14↑j
push ebx
movsx ebx, [esi+_KPRCB.Number]
xor edx, edx
mov ecx, ebx
call @KiFindReadyThread@8 ; KiFindReadyThread(x,x)
test eax, eax
jnz short loc_429CCE ; 如果找到了下一個線程就跳走
mov eax, [esi+_KPRCB.IdleThread]
xor edx, edx
inc edx
mov ecx, ebx
shl edx, cl
or _KiIdleSummary, edx
loc_429CCE: ; CODE XREF: KiSwapThread()+2C↑j
pop ebx
loc_429CCF: ; CODE XREF: KiSwapThread()+1A↑j
mov ecx, eax
call @KiSwapContext@4 ; KiSwapContext(x)
經過簡單的分析,我們很容易地判斷出執行到KiSwapContext前的ecx為一個線程結構體,如下圖所示:
根據符號顯示KiSwapContext是只有一個參數,經過簡單的分析可以得到下面的結果:
; __fastcall KiSwapContext(x)
@KiSwapContext@4 proc near ; CODE XREF: KiSwapThread()+41↑p
var_200FE4 = dword ptr -200FE4h
var_10 = dword ptr -10h
var_C = dword ptr -0Ch
var_8 = dword ptr -8
var_4 = dword ptr -4
sub esp, 10h
mov [esp+10h+var_4], ebx
mov [esp+10h+var_8], esi
mov [esp+10h+var_C], edi
mov [esp+10h+var_10], ebp
mov ebx, ds:0FFDFF01Ch ; ebx = &_KPCR
mov esi, ecx ; esi = ecx = NextReadyThread
mov edi, [ebx+_KPCR.PrcbData.CurrentThread]
mov [ebx+_KPCR.PrcbData.CurrentThread], esi
mov cl, [edi+_KTHREAD.WaitIrql]
call SwapContext
mov ebp, [esp+10h+var_10]
mov edi, [esp+10h+var_C]
mov esi, [esp+10h+var_8]
mov ebx, [esp+10h+var_4]
add esp, 10h
retn
@KiSwapContext@4 endp
執行到SwapContext這個函數前,esi成了下一個切換新線程,而edi成了需要被切換的老線程,而ebx是KPCR結構體,也就是說,改函數一共有3個參數,每一個參數的含義我們都已經知道了,我們利用IDA的F5也可以得到一定的驗證:
char __usercall SwapContext@<al>(int *a1@<ebx>, int a2@<edi>, int a3@<esi>)
接下來我們分析一下在哪裡實現了線程切換。既然操作系統線程切換是基於堆棧的,esp換了,必然導致線程的切換,我們很容易跟到下面的彙編:
loc_46A94C: ; CODE XREF: SwapContext+67↑j
mov ecx, [ebx+_KPCR.TSS]
mov [ecx+_KTSS.Esp0], eax
mov esp, [esi+_KTHREAD.KernelStack]
mov eax, [esi+_KTHREAD.Teb]
mov [ebx+_KPCR.NtTib.Self], eax
經歷過堆棧的彈出恢復操作,再調用retn,即可完成線程的切換。接下來我們看看什麼時候切換的CR3:
mov eax, [edi+_KTHREAD.ApcState.Process]
cmp eax, [esi+_KTHREAD.ApcState.Process]
mov [edi+_KTHREAD.IdleSwapBlock], 0
jz short loc_46A994
mov edi, [esi+_KTHREAD.ApcState.Process]
test word ptr [edi+_KTHREAD.Teb], 0FFFFh
jnz short loc_46A9CE
xor eax, eax
loc_46A975: ; CODE XREF: SwapContext+117↓j
lldt ax
xor eax, eax
mov gs, eax
assume gs:GAP
mov eax, [edi+_EPROCESS.Pcb.DirectoryTableBase]
mov ebp, [ebx+_KPCR.TSS]
mov ecx, dword ptr [edi+_KTHREAD.Iopl]
mov [ebp+_KTSS.CR3], eax
mov cr3, eax
mov [ebp+_KTSS.IoMapBase], cx
jmp short loc_46A994
; ---------------------------------------------------------------------------
align 4
loc_46A994: ; CODE XREF: SwapContext+86↑j
; SwapContext+AF↑j
mov eax, [ebx+_KPCR.NtTib.Self]
經過分析,切換CR3是需要條件的,它會判斷新的線程和老的線程的Process是不是一樣的,然後決定是否處理:
mov eax, [edi+_KTHREAD.ApcState.Process]
cmp eax, [esi+_KTHREAD.ApcState.Process]
mov [edi+_KTHREAD.IdleSwapBlock], 0
jz short loc_46A994
如果不相同的話,就會執行下面的代碼切換CR3:
mov eax, [edi+_EPROCESS.Pcb.DirectoryTableBase]
mov ebp, [ebx+_KPCR.TSS]
mov ecx, dword ptr [edi+_KTHREAD.Iopl]
mov [ebp+_KTSS.CR3], eax
mov cr3, eax
看明白到這個地方的時候,第2題就解決了。一個CPU一套寄存器,也就說明裏面只能存儲一個TSS地址的寄存器,那麼中斷門提權時TSS中存儲的一定是當前線程的ESP0和SS0嗎?我們接下來分析一下:
mov eax, [esi+_KTHREAD.InitialStack]
mov ecx, [esi+_KTHREAD.StackLimit]
sub eax, 210h
mov [ebx+_KPCR.NtTib.StackLimit], ecx
mov [ebx+_KPCR.NtTib.StackBase], eax
xor ecx, ecx
mov cl, [esi+_KTHREAD.NpxState]
and edx, 0FFFFFFF1h
or ecx, edx
or ecx, [eax+20Ch]
cmp ebp, ecx
jnz loc_46A9FC
lea ecx, [ecx+0]
loc_46A940: ; CODE XREF: SwapContext+11F↓j
test dword ptr [eax-1Ch], 20000h ; 檢查是否為虛擬8086模式
jnz short loc_46A94C
sub eax, 10h
loc_46A94C: ; CODE XREF: SwapContext+67↑j
mov ecx, [ebx+_KPCR.TSS]
mov [ecx+_KTSS.Esp0], eax ; 將修正好的棧頂放入到 TSS 中
mov esp, [esi+_KTHREAD.KernelStack]
mov eax, [esi+_KTHREAD.Teb]
可以看出在mov esp, [esi+_KTHREAD.KernelStack]之前,上面的代碼已經處理好並修正ESP0,所以中斷門提權時TSS中存儲的一定是當前線程的ESP0和SS0。至此,第3題解答完畢。
在我測試的虛擬機中,fs的段選擇子都是0x3B,但為什麼不同的線程段選擇子指向的TEB卻不一樣呢?是因為它直接修改了GDT表的內容,使它指向的地址是我們現在線程的TEB,代碼如下所示:
mov eax, [ebx+_KPCR.NtTib.Self]
mov ecx, [ebx+_KPCR.GDT]
mov [ecx+3Ah], ax
shr eax, 10h
mov [ecx+3Ch], al
mov [ecx+3Fh], ah
至此,第4題解答完畢。我們來看看0環的ExceptionList在哪裡備份的:
pop ecx
mov [ebx+_KPCR.NtTib.ExceptionList], ecx
第5題也就解決了,它把新線程的ExceptionList存於KPCR中。下面我們來看看IdleThread如何查找:
我們知道IdleThread存儲於KPCR之中,我們通過結構體的方式進行查詢,找到該成員存儲的地址。
kd> dt _KPCR 0xffdff000
nt!_KPCR
+0x000 NtTib : _NT_TIB
+0x01c SelfPcr : 0xffdff000 _KPCR
+0x020 Prcb : 0xffdff120 _KPRCB
+0x024 Irql : 0x1c ''
+0x028 IRR : 4
+0x02c IrrActive : 0
+0x030 IDR : 0xffff20f8
+0x034 KdVersionBlock : 0x80546ab8 Void
+0x038 IDT : 0x8003f400 _KIDTENTRY
+0x03c GDT : 0x8003f000 _KGDTENTRY
+0x040 TSS : 0x80042000 _KTSS
+0x044 MajorVersion : 1
+0x046 MinorVersion : 1
+0x048 SetMember : 1
+0x04c StallScaleFactor : 0x64
+0x050 DebugActive : 0 ''
+0x051 Number : 0 ''
+0x052 Spare0 : 0 ''
+0x053 SecondLevelCacheAssociativity : 0 ''
+0x054 VdmAlert : 0
+0x058 KernelReserved : [14] 0
+0x090 SecondLevelCacheSize : 0
+0x094 HalReserved : [16] 0
+0x0d4 InterruptMode : 0
+0x0d8 Spare1 : 0 ''
+0x0dc KernelReserved2 : [17] 0
+0x120 PrcbData : _KPRCB
kd> dx -id 0,0,805539a0 -r1 ((ntkrnlpa!_KPRCB *)0xffdff120)
((ntkrnlpa!_KPRCB *)0xffdff120) : 0xffdff120 [Type: _KPRCB *]
[+0x000] MinorVersion : 0x1 [Type: unsigned short]
[+0x002] MajorVersion : 0x1 [Type: unsigned short]
[+0x004] CurrentThread : 0x80553740 [Type: _KTHREAD *]
[+0x008] NextThread : 0x0 [Type: _KTHREAD *]
[+0x00c] IdleThread : 0x80553740 [Type: _KTHREAD *]
[+0x010] Number : 0 [Type: char]
[+0x011] Reserved : 0 [Type: char]
然後我們dt一下這個結構體:
kd> dt _ETHREAD 0x80553740
ntdll!_ETHREAD
+0x000 Tcb : _KTHREAD
+0x1c0 CreateTime : _LARGE_INTEGER 0x0
+0x1c0 NestedFaultCount : 0y00
+0x1c0 ApcNeeded : 0y0
+0x1c8 ExitTime : _LARGE_INTEGER 0x0
+0x1c8 LpcReplyChain : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x1c8 KeyedWaitChain : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x1d0 ExitStatus : 0n0
+0x1d0 OfsChain : (null)
+0x1d4 PostBlockList : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x1dc TerminationPort : (null)
+0x1dc ReaperLink : (null)
+0x1dc KeyedWaitValue : (null)
+0x1e0 ActiveTimerListLock : 0
+0x1e4 ActiveTimerListHead : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x1ec Cid : _CLIENT_ID
+0x1f4 LpcReplySemaphore : _KSEMAPHORE
+0x1f4 KeyedWaitSemaphore : _KSEMAPHORE
+0x208 LpcReplyMessage : (null)
+0x208 LpcWaitingOnPort : (null)
+0x20c ImpersonationInfo : (null)
+0x210 IrpList : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x218 TopLevelIrp : 0
+0x21c DeviceToVerify : (null)
+0x220 ThreadsProcess : (null)
+0x224 StartAddress : (null)
+0x228 Win32StartAddress : (null)
+0x228 LpcReceivedMessageId : 0
+0x22c ThreadListEntry : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x234 RundownProtect : _EX_RUNDOWN_REF
+0x238 ThreadLock : _EX_PUSH_LOCK
+0x23c LpcReplyMessageId : 0
+0x240 ReadClusterSize : 0
+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 ''
在上面的結構體中+0x224 StartAddress存儲的就是線程開始執行的地址,也就是我們在3環調用CreateThread傳遞的函數地址。但是對於這個線程比較特殊,直接全為空,那麼我們如何找到函數地址呢?
程序執行的時候,一定會用到堆棧。我們可以通過堆棧就可以定位程序的行為。我們把關注點放到KTHREAD的KernelStack上。涉及該成員的操作存在於線程切換中,我們來看看與堆棧操作相關的局部彙編代碼:
pushf
mov ecx, [ebx] ; ebx = KPCR
cmp [ebx+_KPCR.PrcbData.DpcRoutineActive], 0
push ecx ;KPCR.NtTib.ExceptionList
……
mov esp, [esi+_KTHREAD.KernelStack]
……
pop ecx
xor eax, eax
retn
先看看堆棧地址的地址是啥:
kd> dt _KTHREAD 0x80553740
ntdll!_KTHREAD
+0x000 Header : _DISPATCHER_HEADER
+0x010 MutantListHead : _LIST_ENTRY [ 0x80553750 - 0x80553750 ]
+0x018 InitialStack : 0x8054af00 Void
+0x01c StackLimit : 0x80547f00 Void
+0x020 Teb : (null)
+0x024 TlsArray : (null)
+0x028 KernelStack : 0x8054ac4c Void
+0x02c DebugActive : 0 ''
+0x02d State : 0x2 ''
+0x02e Alerted : [2] ""
+0x030 Iopl : 0 ''
+0x031 NpxState : 0xa ''
+0x032 Saturation : 0 ''
+0x033 Priority : 16 ''
+0x034 ApcState : _KAPC_STATE
+0x04c ContextSwitches : 0x1736
+0x050 IdleSwapBlock : 0 ''
+0x051 Spare0 : [3] ""
+0x054 WaitStatus : 0n0
+0x058 WaitIrql : 0x2 ''
+0x059 WaitMode : 0 ''
+0x05a WaitNext : 0 ''
+0x05b WaitReason : 0 ''
+0x05c WaitBlockList : 0x805537b0 _KWAIT_BLOCK
+0x060 WaitListEntry : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x060 SwapListEntry : _SINGLE_LIST_ENTRY
+0x068 WaitTime : 0x555
+0x06c BasePriority : 0 ''
+0x06d DecrementCount : 0 ''
+0x06e PriorityDecrement : 0 ''
+0x06f Quantum : -17 ''
+0x070 WaitBlock : [4] _KWAIT_BLOCK
+0x0d0 LegoData : (null)
+0x0d4 KernelApcDisable : 0
+0x0d8 UserAffinity : 0xffffffff
+0x0dc SystemAffinityActive : 0 ''
+0x0dd PowerState : 0 ''
+0x0de NpxIrql : 0 ''
+0x0df InitialNode : 0 ''
+0x0e0 ServiceTable : 0x80553fa0 Void
+0x0e4 Queue : (null)
+0x0e8 ApcQueueLock : 0
+0x0f0 Timer : _KTIMER
+0x118 QueueListEntry : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x120 SoftAffinity : 1
+0x124 Affinity : 1
+0x128 Preempted : 0 ''
+0x129 ProcessReadyQueue : 0 ''
+0x12a KernelStackResident : 0x1 ''
+0x12b NextProcessor : 0 ''
+0x12c CallbackStack : (null)
+0x130 Win32Thread : (null)
+0x134 TrapFrame : (null)
+0x138 ApcStatePointer : [2] 0x80553774 _KAPC_STATE
+0x140 PreviousMode : 0 ''
+0x141 EnableStackSwap : 0x1 ''
+0x142 LargeStack : 0 ''
+0x143 ResourceIndex : 0 ''
+0x144 KernelTime : 0x2d1a
+0x148 UserTime : 0
+0x14c SavedApcState : _KAPC_STATE
+0x164 Alertable : 0 ''
+0x165 ApcStateIndex : 0 ''
+0x166 ApcQueueable : 0x1 ''
+0x167 AutoAlignment : 0 ''
+0x168 StackBase : 0x8054af00 Void
+0x16c SuspendApc : _KAPC
+0x19c SuspendSemaphore : _KSEMAPHORE
+0x1b0 ThreadListEntry : _LIST_ENTRY [ 0x805539f0 - 0x805539f0 ]
+0x1b8 FreezeCount : 0 ''
+0x1b9 SuspendCount : 0 ''
+0x1ba IdealProcessor : 0 ''
+0x1bb DisableBoost : 0 ''
再看看堆棧長什麼樣子:
kd> dd 0x8054ac4c
8054ac4c 00000000 ffdff980 80542af0 00000000
8054ac5c 0000000e 00000000 00000000 00000000
8054ac6c 00000000 00000000 00000000 00000000
8054ac7c 00000000 00000000 00000000 00000000
8054ac8c 00000000 00000000 00000000 00000000
8054ac9c 00000000 00000000 00000000 00000000
8054acac 00000000 00000000 00000000 00000000
8054acbc 00000000 00000000 00000000 00000000
也就是說,第一個就是ExceptionList,第二個就是Eflag,第三個就是切換線程後跳轉的地址,在這裡也就是IdleThread繼續走的地址。這個地址肯定就在IdleThread當中。我們u一下:
kd> u 80542af0
ReadVirtual: 80542af0 not properly sign extended
80542af0 fb sti
ReadVirtual: 80542b00 not properly sign extended
80542af1 90 nop
ReadVirtual: 80542b01 not properly sign extended
80542af2 90 nop
ReadVirtual: 80542b02 not properly sign extended
80542af3 fa cli
ReadVirtual: 80542b03 not properly sign extended
80542af4 3b6d00 cmp ebp,dword ptr [ebp]
80542af7 740d je nt!KiIdleLoop+0x26 (80542b06)
80542af9 b102 mov cl,2
80542afb ff15a8864d80 call dword ptr [nt!_imp_HalClearSoftwareInterrupt (804d86a8)]
也就是函數是KiIdleLoop,我們通過IDA看看該函數:
; _DWORD __cdecl KiIdleLoop()
@KiIdleLoop@0 proc near ; CODE XREF: KiSystemStartup(x)+2E2↓j
lea ebp, [ebx+980h]
jmp short loc_46AAF0
; ---------------------------------------------------------------------------
loc_46AAE8: ; CODE XREF: KiIdleLoop()+2D↓j
lea ecx, [ebx+0C50h]
call dword ptr [ecx]
loc_46AAF0: ; CODE XREF: KiIdleLoop()+6↑j
; KiIdleLoop()+65↓j
sti
nop
nop
cli
cmp ebp, [ebp+0]
jz short loc_46AB06
mov cl, 2
call ds:__imp_@HalClearSoftwareInterrupt@4 ; HalClearSoftwareInterrupt(x)
call KiRetireDpcList
loc_46AB06: ; CODE XREF: KiIdleLoop()+17↑j
cmp dword ptr [ebx+128h], 0
jz short loc_46AAE8
sti
mov esi, [ebx+128h]
mov edi, [ebx+124h]
or ecx, 1
mov [ebx+124h], esi
mov byte ptr es:[esi+2Dh], 2
mov dword ptr [ebx+128h], 0
push offset loc_46AB3F
pushf
jmp loc_46A8E8
; ---------------------------------------------------------------------------
loc_46AB3F: ; DATA XREF: KiIdleLoop()+54↑o
lea ebp, [ebx+980h]
jmp short loc_46AAF0
@KiIdleLoop@0 endp
這個函數沒有任何意義,就是讓CPU別閑着,執行一波無任何意義的代碼。至此第6題解決。
至此第7題解決。
模擬線程切換與Windows的線程切換有哪些區別?真正的線程有兩個棧,一個內核0環的棧,一個是3環的棧,發生線程切換在0環;模擬線程切換沒用到FS、異常列表之類的東西,其他的區別可以自行總結。
接下來是最後一題,我們走一下時鐘中斷的流程,中斷都是在IDT表中的,首先我們跟着走一下,首先定位該表,只需g到_IDT,效果如下所示:
_IDT dd offset _KiTrap00 ; DATA XREF: KiSystemStartup(x)+1D5↑o
db 0, 8Eh
word_5B8B02 dw 8 ; DATA XREF: KiSwapIDT()↓o
dd offset _KiTrap01
dd 88E00h
dd offset _KiTrap02
dd 88E00h
dd offset _KiTrap03
dd 8EE00h
dd offset _KiTrap04
dd 8EE00h
dd offset _KiTrap05
dd 88E00h
dd offset _KiTrap06
dd 88E00h
dd offset _KiTrap07
dd 88E00h
dd offset _KiTrap08
dd 88E00h
dd offset _KiTrap09
時鐘中斷的中斷號是0x30,我們定位到這個函數,如何定位呢?看下面的圖:
然後跳轉到這個函數:
; _DWORD __stdcall KiStartUnexpectedRange()
_KiStartUnexpectedRange@0 proc near ; DATA XREF: KiGetVectorInfo(x,x)+68↑o
; INIT:005B8C7C↓o
push 30h ; '0'
jmp _KiEndUnexpectedRange@0 ; KiEndUnexpectedRange()
_KiStartUnexpectedRange@0 endp
我們看看跳轉到哪裡:
; _DWORD __stdcall KiEndUnexpectedRange()
_KiEndUnexpectedRange@0 proc near ; CODE XREF: KiStartUnexpectedRange()+5↑j
; _KiUnexpectedInterrupt1+5↑j ...
jmp cs:off_46632E
_KiEndUnexpectedRange@0 endp
; ---------------------------------------------------------------------------
off_46632E dd offset _KiUnexpectedInterruptTail
; DATA XREF: KiEndUnexpectedRange()↑r
繼續跟着,為了節約篇幅,只保留調用流程部分:
_KiUnexpectedInterruptTail proc near ; CODE XREF: KiEndUnexpectedRange()↑j
; DATA XREF: .text:off_46632E↑o
……
loc_466E7E: ; CODE XREF: Dr_kui_a+10↑j
; Dr_kui_a+7C↑j
inc dword ptr ds:0FFDFF5C4h
mov ebx, [esp+68h+var_68]
sub esp, 4
push esp
push ebx
push 1Fh
call ds:__imp__HalBeginSystemInterrupt@12 ; HalBeginSystemInterrupt(x,x,x)
or eax, eax
jnz short loc_466E9D
add esp, 8
jmp short loc_466EEC
; ---------------------------------------------------------------------------
loc_466E9D: ; CODE XREF: _KiUnexpectedInterruptTail+BF↑j
cli
call ds:__imp__HalEndSystemInterrupt@8 ; HalEndSystemInterrupt(x,x)
jmp short Kei386EoiHelper@0 ; Kei386EoiHelper()
_KiUnexpectedInterruptTail endp
public HalEndSystemInterrupt
HalEndSystemInterrupt proc near ; CODE XREF: sub_80010EF0+E8↑p
; sub_80017144+B3↓p
; DATA XREF: ...
arg_0 = byte ptr 4
movzx ecx, [esp+arg_0]
cmp byte ptr ds:0FFDFF024h, 2
jbe short loc_8001123E
mov eax, ds:dword_800176EC[ecx*4]
or eax, ds:0FFDFF030h
out 21h, al ; Interrupt controller, 8259A.
shr eax, 8
out 0A1h, al ; Interrupt Controller #2, 8259A
loc_8001123E: ; CODE XREF: HalEndSystemInterrupt+C↑j
mov ds:0FFDFF024h, cl
mov eax, ds:0FFDFF028h
mov al, ds:byte_80017784[eax]
cmp al, cl
ja short loc_80011256
retn 8
; ---------------------------------------------------------------------------
loc_80011256: ; CODE XREF: HalEndSystemInterrupt+35↑j
add esp, 0Ch
jmp ds:pKiUnexpectedInterrupt[eax*4]
HalEndSystemInterrupt endp ; sp-analysis failed
pKiUnexpectedInterrupt dd offset KiUnexpectedInterrupt
; DATA XREF: HalEndSystemInterrupt+3D↑r
; sub_80011260+3D↑r
dd offset sub_80016BDD
dd offset sub_80016A45
sub_80016A45 proc near ; CODE XREF: KfLowerIrql:loc_800110AC↑p
; KfReleaseSpinLock:loc_8001111C↑p ...
push dword ptr ds:0FFDFF024h
mov byte ptr ds:0FFDFF024h, 2
and dword ptr ds:0FFDFF028h, 0FFFFFFFBh
sti
call ds:KiDispatchInterrupt
cli
call sub_80011260
jmp ds:Kei386EoiHelper
sub_80016A45 endp
; _DWORD __stdcall KiDispatchInterrupt()
public _KiDispatchInterrupt@0
_KiDispatchInterrupt@0 proc near ; DATA XREF: .edata:off_58D2A8↓o
var_C = dword ptr -0Ch
var_8 = dword ptr -8
var_4 = dword ptr -4
mov ebx, ds:0FFDFF01Ch ; a1
lea eax, [ebx+980h]
cli
cmp eax, [eax]
jz short loc_46A85E
push ebp
push dword ptr [ebx]
mov dword ptr [ebx], 0FFFFFFFFh
mov edx, esp
mov esp, [ebx+988h]
push edx
mov ebp, eax
call KiRetireDpcList
pop esp
pop dword ptr [ebx]
pop ebp
loc_46A85E: ; CODE XREF: KiDispatchInterrupt()+F↑j
sti
cmp dword ptr [ebx+9ACh], 0
jnz short loc_46A8BE
cmp dword ptr [ebx+128h], 0
jz short locret_46A8BD
mov eax, [ebx+128h]
loc_46A877: ; CODE XREF: KiDispatchInterrupt()+9F↓j
sub esp, 0Ch
mov [esp+0Ch+var_4], esi
mov [esp+0Ch+var_8], edi
mov [esp+0Ch+var_C], ebp
mov esi, eax ; NewThread
mov edi, [ebx+124h] ; oldThread
mov dword ptr [ebx+128h], 0
mov [ebx+124h], esi
mov ecx, edi
mov byte ptr [edi+50h], 1
call @KiReadyThread@4 ; KiReadyThread(x)
mov cl, 1
call SwapContext
mov ebp, [esp+0Ch+var_C]
mov edi, [esp+0Ch+var_8]
mov esi, [esp+0Ch+var_4]
add esp, 0Ch
locret_46A8BD: ; CODE XREF: KiDispatchInterrupt()+3F↑j
retn
; ---------------------------------------------------------------------------
loc_46A8BE: ; CODE XREF: KiDispatchInterrupt()+36↑j
mov dword ptr [ebx+9ACh], 0
call _KiQuantumEnd@0 ; KiQuantumEnd()
or eax, eax
jnz short loc_46A877
retn
_KiDispatchInterrupt@0 endp
進程掛靠
在講編程的時候,我們都聽過:一個進程可以包含多個線程,一個進程至少要有一個線程。進程為線程提供資源,也就是提供Cr3的值,Cr3中存儲的是頁目錄表基址,Cr3確定了,線程能訪問的內存也就確定了。
對於這一行代碼:mov eax,dword ptr ds:[0x12345678],CPU如何解析這個地址呢?CPU解析線性地址時要通過頁目錄表來找對應的物理頁,頁目錄表基址存在Cr3寄存器中。當前的Cr3的值來源於當前的進程結構體的_KPROCESS.DirectoryTableBase當中。那麼進程掛靠又是怎麼回事呢?我們先來看個結構體:
kd> dt _KTHREAD
ntdll!_KTHREAD
……
+0x032 Saturation : Char
+0x033 Priority : Char
+0x034 ApcState : _KAPC_STATE
+0x04c ContextSwitches : Uint4B
+0x050 IdleSwapBlock : UChar
……
kd> dt _KAPC_STATE
ntdll!_KAPC_STATE
+0x000 ApcListHead : [2] _LIST_ENTRY
+0x010 Process : Ptr32 _KPROCESS
+0x014 KernelApcInProgress : UChar
+0x015 KernelApcPending : UChar
+0x016 UserApcPending : UChar
ApcState這個成員我們在逆向線程切換的時候遇到過,也就是解決我們第2題的時候,這個結構體的Process成員就是存儲的進程掛靠上的進程CR3。可以打一個比方,EPROCESS的DirectoryTableBase存儲的是親父母,而ApcState存儲的是養父母,我想要資源時從養父母來拿。正常情況下,CR3的值是由養父母提供的,但CR3的值也可以改成和當前線程毫不相干的其他進程的DirectoryTableBase。將當前CR3的值改為其他進程,稱為「進程掛靠」。
跨進程內存讀寫
跨進程內存讀寫根據之前所學肯定必須切換CR3,並且讀取內存肯定會落實到類似如下彙編:
mov eax,dword ptr ds:[0x12345678]
mov dword ptr ds:[0x00401234],eax
我們自己實現一個跨進程內存讀寫一個int還好說,如果是一個指定長度的Buffer,那咋辦呢?
我們都知道,應用程序的高2G的內核空間是共用的,也就是說,無論是哪個應用程序,高2G的內容尋址都能尋到的。那麼我把讀取進程的內存寫到高2G的空間,然後切換CR3回去,然後重新把高2G緩存的東西寫到指定Buffer中,我們就完成了。上面的讀的操作,寫得操作也是類似的。
跨進程讀
跨進程寫
我們下面來簡單分析一下Windows實現的跨進程讀內存的函數NtReadVirtualMemory和跨進程寫NtWriteVirtualMemory的函數,NtWriteVirtualMemory和NtReadVirtualMemory實現十分相似,我就只分析前者,下面的自行分析。為什麼說是淺析是因為裏面有大量的其他前置知識,比如APC和內存管理。三環怎麼進內核的我就不再贅述了,為了方便。為了縮短篇幅增加可讀性,我會儘可能使用IDA翻譯的偽代碼,你的偽代碼結果應該和我的不一樣,因為我進行了一些重命名操作。
NtReadVirtualMemory 淺析
我們先定位到NtReadVirtualMemory這個偽代碼:
NTSTATUS __stdcall NtReadVirtualMemory(HANDLE ProcessHandle, PVOID BaseAddress, PVOID Buffer, SIZE_T NumberOfBytesToRead, PSIZE_T NumberOfBytesRead)
{
_KTHREAD *v5; // edi
PSIZE_T v6; // ebx
int a7; // [esp+10h] [ebp-28h] BYREF
PRKPROCESS PROCESS; // [esp+14h] [ebp-24h] BYREF
KPROCESSOR_MODE AccessMode[4]; // [esp+18h] [ebp-20h]
NTSTATUS res; // [esp+1Ch] [ebp-1Ch]
CPPEH_RECORD ms_exc; // [esp+20h] [ebp-18h]
v5 = KeGetCurrentThread();
AccessMode[0] = v5->PreviousMode;
if ( AccessMode[0] )
{
if ( BaseAddress + NumberOfBytesToRead < BaseAddress
|| Buffer + NumberOfBytesToRead < Buffer
|| BaseAddress + NumberOfBytesToRead > MmHighestUserAddress
|| Buffer + NumberOfBytesToRead > MmHighestUserAddress )
{
return 0xC0000005;
}
v6 = NumberOfBytesRead;
if ( NumberOfBytesRead )
{
ms_exc.registration.TryLevel = 0;
if ( NumberOfBytesRead >= MmUserProbeAddress )
*MmUserProbeAddress = 0;
*NumberOfBytesRead = *NumberOfBytesRead;
ms_exc.registration.TryLevel = -1;
}
}
else
{
v6 = NumberOfBytesRead;
}
a7 = 0;
res = 0;
if ( NumberOfBytesToRead )
{
res = ObReferenceObjectByHandle(ProcessHandle, 0x10u, PsProcessType, AccessMode[0], &PROCESS, 0);
if ( !res )
{
res = MmCopyVirtualMemory(
PROCESS,
BaseAddress,
v5->ApcState.Process,
Buffer,
NumberOfBytesToRead,
AccessMode[0],
&a7);
ObfDereferenceObject(PROCESS);
}
}
if ( v6 )
{
*v6 = a7;
ms_exc.registration.TryLevel = -1;
}
return res;
}
我們可以看到,該函數實現內存拷貝是通過MmCopyVirtualMemory這個函數實現的,我們點擊去看看:
NTSTATUS __stdcall MmCopyVirtualMemory(PRKPROCESS PROCESS, PVOID BaseAddress, PRKPROCESS KPROCESS, char *buffer, SIZE_T Length, KPROCESSOR_MODE AccessMode, int *a7)
{
PRKPROCESS process; // ebx
PRKPROCESS kprocess; // ecx
NTSTATUS res; // esi
struct _EX_RUNDOWN_REF *RunRefa; // [esp+8h] [ebp+8h]
if ( !Length )
return 0;
process = PROCESS;
kprocess = PROCESS;
if ( PROCESS == KeGetCurrentThread()->ApcState.Process )
kprocess = KPROCESS;
RunRefa = &kprocess[1].ProfileListHead.Blink;
if ( !ExAcquireRundownProtection(&kprocess[1].ProfileListHead.Blink) )
return STATUS_PROCESS_IS_TERMINATING;
if ( Length <= 0x1FF )
goto LABEL_10;
res = MiDoMappedCopy(process, BaseAddress, KPROCESS, buffer, Length, AccessMode, a7);
if ( res == STATUS_WORKING_SET_QUOTA )
{
*a7 = 0;
LABEL_10:
res = MiDoPoolCopy(process, BaseAddress, KPROCESS, buffer, Length, AccessMode, a7);
}
ExReleaseRundownProtection(RunRefa);
return res;
}
你可能看到一個新奇的函數ExAcquireRundownProtection,這個函數是申請一個鎖,從網上查閱翻譯過來是停運保護(RundownProtection)鎖,名字怪怪的聽起來怪怪的。
這個不涉及我們的核心,我們繼續分析,發現它內部又是通過MiDoMappedCopy實現進程內存讀取的:
NTSTATUS __stdcall MiDoMappedCopy(PRKPROCESS PROCESS, char *src, PRKPROCESS process, char *buffer, SIZE_T Length, KPROCESSOR_MODE AccessMode, int *a7)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
v13 = 0;
src_0 = src;
buffer_1 = buffer;
v7 = 0xE000;
if ( Length <= 0xE000 )
v7 = Length;
v16 = &MemoryDescriptorList;
Length_1 = Length;
v19 = v7;
v20 = 0;
v14 = 0;
v15 = 0;
while ( Length_1 )
{
if ( Length_1 < v19 )
v19 = Length_1;
KeStackAttachProcess(PROCESS, &ApcState);
BaseAddress = 0;
v12 = 0;
v11 = 0;
ms_exc.registration.TryLevel = 0;
if ( src_0 == src && AccessMode )
{
v20 = 1;
if ( Length && (&src[Length] < src || &src[Length] > MmUserProbeAddress) )
ExRaiseAccessViolation();
v20 = 0;
}
MemoryDescriptorList.Next = 0;
MemoryDescriptorList.Size = 4 * (((src_0 & 0xFFF) + v19 + 0xFFF) >> 12) + 28;
MemoryDescriptorList.MdlFlags = 0;
MemoryDescriptorList.StartVa = (src_0 & 0xFFFFF000);
MemoryDescriptorList.ByteOffset = src_0 & 0xFFF;
MemoryDescriptorList.ByteCount = v19;
MmProbeAndLockPages(&MemoryDescriptorList, AccessMode, IoReadAccess);
v12 = 1;
BaseAddress = MmMapLockedPagesSpecifyCache(&MemoryDescriptorList, 0, MmCached, 0u, 0u, 0x20u);
if ( !BaseAddress )
{
v13 = 1;
ExRaiseStatus(STATUS_INSUFFICIENT_RESOURCES);
}
KeUnstackDetachProcess(&ApcState);
KeStackAttachProcess(process, &ApcState);
if ( src_0 == src )
{
if ( AccessMode )
{
v20 = 1;
ProbeForWrite(buffer, Length, 1u);
v20 = 0;
}
}
v11 = 1;
qmemcpy(buffer_1, BaseAddress, v19);
ms_exc.registration.TryLevel = -1;
KeUnstackDetachProcess(&ApcState);
MmUnmapLockedPages(BaseAddress, &MemoryDescriptorList);
MmUnlockPages(&MemoryDescriptorList);
Length_1 -= v19;
src_0 += v19;
buffer_1 += v19;
}
*a7 = Length;
return STATUS_SUCCESS;
}
KeStackAttachProcess和KeUnstackDetachProcess這倆函數與APC相關,在這裡你可以簡單理解就是切換CR3,實現進程掛靠和解除掛靠。我們注意一下下面的偽代碼:
MemoryDescriptorList.Next = 0;
MemoryDescriptorList.Size = 4 * (((src_0 & 0xFFF) + v19 + 0xFFF) >> 12) + 28;
MemoryDescriptorList.MdlFlags = 0;
MemoryDescriptorList.StartVa = (src_0 & 0xFFFFF000);
MemoryDescriptorList.ByteOffset = src_0 & 0xFFF;
MemoryDescriptorList.ByteCount = v19;
MmProbeAndLockPages(&MemoryDescriptorList, AccessMode, IoReadAccess);
v12 = 1;
BaseAddress = MmMapLockedPagesSpecifyCache(&MemoryDescriptorList, 0, MmCached, 0u, 0u, 0x20u);
MmMapLockedPagesSpecifyCache這個函數就是映射裏面描述的物理頁,下面是微軟對該函數的描述:
The MmMapLockedPagesSpecifyCache routine maps the physical pages that are described by an MDL to a virtual address, and enables the caller to specify the cache attribute that is used to create the mapping.
上面的操作就算鎖住物理頁,並把它重新映射到高2G地址,我們直接寫到裏面,就少了重新把高2G的內容重新寫到程序空間的步驟了。
進程創建淺析
同理上面的淺析,我同樣使用偽代碼。進程創建淺析是為了知道內核是怎樣創建進程的流程,具體細節請自行挖掘,我們定位到其內核函數NtCreateProcess:
NTSTATUS __stdcall NtCreateProcess(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, HANDLE ParentProcess, BOOLEAN InheritObjectTable, HANDLE SectionHandle, HANDLE DebugPort, HANDLE ExceptionPort)
{
ULONG v8; // eax
v8 = (SectionHandle & 1) != 0;
if ( (DebugPort & 1) != 0 )
v8 |= 2u;
if ( InheritObjectTable )
v8 |= 4u;
return NtCreateProcessEx(
ProcessHandle,
DesiredAccess,
ObjectAttributes,
ParentProcess,
v8,
SectionHandle,
DebugPort,
ExceptionPort,
0);
}
這個內核函數又會調用NtCreateProcessEx實現功能,我們點進去看看:
NTSTATUS __stdcall NtCreateProcessEx(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, HANDLE ParentProcess, ULONG Flags, HANDLE SectionHandle, HANDLE DebugPort, HANDLE ExceptionPort, BOOLEAN InJob)
{
PHANDLE v9; // ecx
NTSTATUS result; // eax
if ( KeGetCurrentThread()->PreviousMode )
{
v9 = ProcessHandle;
if ( ProcessHandle >= MmUserProbeAddress )
*MmUserProbeAddress = 0;
*ProcessHandle = *ProcessHandle;
}
else
{
v9 = ProcessHandle;
}
if ( ParentProcess )
result = PspCreateProcess(
v9,
DesiredAccess,
ObjectAttributes,
ParentProcess,
Flags,
SectionHandle,
DebugPort,
ExceptionPort,
InJob);
else
result = STATUS_INVALID_PARAMETER;
return result;
}
這個函數優惠調用PspCreateProcess實現創建進程的任務,繼續點擊去看看:
🔒 點擊查看偽代碼 🔒
NTSTATUS __stdcall PspCreateProcess(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, HANDLE ParentProcess, ULONG Flags, HANDLE SectionHandle, HANDLE DebugPort, HANDLE ExceptionPort, BOOLEAN InJob)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
v41 = KeGetCurrentThread();
AccessMode[0] = v41->PreviousMode;
process = v41->ApcState.Process;
v64 = 0;
v55[0] = 0;
v55[1] = 0;
if ( (Flags & 0xFFFFFFF0) != 0 )
return STATUS_INVALID_PARAMETER;
if ( ParentProcess )
{
result = ObReferenceObjectByHandle(ParentProcess, 0x80u, PsProcessType, AccessMode[0], &eprocess, 0);
ParentProcess_1 = eprocess;
if ( result < 0 )
return result;
if ( InJob && !eprocess[2].Affinity )
{
ObfDereferenceObject(eprocess);
return STATUS_INVALID_PARAMETER;
}
ActiveProcessors = eprocess->Affinity;
}
else
{
ParentProcess_1 = 0;
ActiveProcessors = KeActiveProcessors;
}
v48 = ActiveProcessors;
*NewIrql = PsMinimumWorkingSet;
v47 = PsMaximumWorkingSet;
v11 = ObCreateObject(AccessMode[0], PsProcessType, ObjectAttributes, *AccessMode, 0, 608, 0, 0, &Process);
if ( v11 < 0 )
goto LABEL_97;
NewProcess = Process;
memset(Process, 0, 0x260u);
NewProcess[1].ProfileListHead.Blink = 0;
*&NewProcess[1].Header.Type = 0;
NewProcess[3].ThreadListHead.Flink = &NewProcess[3].VdmTrapcHandler;
NewProcess[3].VdmTrapcHandler = &NewProcess[3].VdmTrapcHandler;
PspInheritQuota(NewProcess, ParentProcess_1);
ObInheritDeviceMap(NewProcess, ParentProcess_1);
v13 = ParentProcess_1;
if ( ParentProcess_1 )
{
*&NewProcess[3].AutoAlignment = *&ParentProcess_1[3].AutoAlignment;
NewProcess[3].Header.WaitListHead.Flink = v13[1].DirectoryTableBase[0];
}
else
{
*&NewProcess[3].AutoAlignment = 1;
NewProcess[3].Header.WaitListHead.Flink = 0;
}
if ( SectionHandle )
{
v14 = ObReferenceObjectByHandle(SectionHandle, 8u, MmSectionObjectType, AccessMode[0], &v40, 0);
v61 = v40;
v11 = v14;
if ( v14 < 0 )
goto LABEL_96;
v13 = ParentProcess_1;
}
else
{
v61 = 0;
if ( v13 != PsInitialSystemProcess )
{
if ( ExAcquireRundownProtection(&v13[1].ProfileListHead.Blink) )
{
v15 = *&v13[2].StackCount;
v61 = v15;
if ( v15 )
ObfReferenceObject(v15);
ExReleaseRundownProtection(&v13[1].ProfileListHead.Blink);
}
if ( !v61 )
{
v11 = STATUS_PROCESS_IS_TERMINATING;
goto LABEL_96;
}
}
}
*&NewProcess[2].StackCount = v61;
if ( DebugPort )
{
v11 = ObReferenceObjectByHandle(DebugPort, 2u, DbgkDebugObjectType, AccessMode[0], &v44, 0);
if ( v11 < 0 )
goto LABEL_96;
NewProcess[1].ThreadListHead.Flink = v44;
if ( (Flags & 2) != 0 )
_InterlockedOr(&NewProcess[5].Int21Descriptor.Access, 2u);
}
else if ( v13 )
{
DbgkCopyProcessDebugPort(NewProcess, v13);
}
if ( ExceptionPort )
{
v11 = ObReferenceObjectByHandle(ExceptionPort, 0, LpcPortObjectType, AccessMode[0], &v45, 0);
if ( v11 < 0 )
goto LABEL_96;
NewProcess[1].ThreadListHead.Blink = v45;
}
*&NewProcess[5].IopmOffset = 259;
v11 = PspInitializeProcessSecurity(ParentProcess_1, NewProcess);
if ( v11 < 0 )
goto LABEL_96;
v16 = ParentProcess_1;
if ( ParentProcess_1 )
{
if ( !MmCreateProcessAddressSpace(NewIrql[0], NewProcess, v55) )
goto LABEL_59;
}
else
{
NewProcess[1].ProcessLock = process[49].Count;
MmInitializeHandBuiltProcess(NewProcess, v55);
}
_InterlockedOr(&NewProcess[5].Int21Descriptor.Access, 0x40000u);
*&NewProcess[4].AutoAlignment = v47;
KeInitializeProcess(NewProcess, 8, v48, v55, NewProcess[3].AutoAlignment & 4);
NewProcess->ThreadQuantum = PspForegroundQuantum;
LOBYTE(NewProcess[5].KernelTime) = 2;
if ( v16 )
{
v17 = v16[5].KernelTime;
if ( v17 == 1 || v17 == 5 )
LOBYTE(NewProcess[5].KernelTime) = v17;
v18 = ObInitProcess((Flags & 4) != 0 ? ParentProcess_1 : 0, NewProcess);
}
else
{
v18 = MmInitializeHandBuiltProcess2(NewProcess);
}
v11 = v18;
if ( v18 < 0 )
goto LABEL_96;
v58 = 0;
if ( SectionHandle )
{
v19 = MmInitializeProcessAddressSpace(NewProcess, 0, v61, &NewProcess[4].ReadyListHead.Blink);
v11 = v19;
if ( v19 < 0 )
goto LABEL_96;
v58 = v19;
v11 = PspMapSystemDll(NewProcess, 0);
if ( v11 < 0 )
goto LABEL_96;
v64 = 1;
goto LABEL_58;
}
v20 = ParentProcess_1;
if ( !ParentProcess_1 )
goto LABEL_58;
if ( ParentProcess_1 == PsInitialSystemProcess )
{
v11 = MmInitializeProcessAddressSpace(NewProcess, 0, 0, 0);
if ( v11 >= 0 )
{
v24 = ExAllocatePoolWithTag(PagedPool, 8u, 0x61506553u);
NewProcess[4].ReadyListHead.Blink = v24;
if ( v24 )
{
v24->Flink = 0;
v24->Blink = 0;
goto LABEL_58;
}
goto LABEL_59;
}
LABEL_96:
ObfDereferenceObject(NewProcess);
goto LABEL_97;
}
*&NewProcess[2].AutoAlignment = *&ParentProcess_1[2].AutoAlignment;
v11 = MmInitializeProcessAddressSpace(NewProcess, v20, 0, 0);
v64 = 1;
if ( v11 < 0 )
goto LABEL_96;
v21 = ParentProcess_1[4].ReadyListHead.Blink;
if ( v21 )
{
v22 = HIWORD(v21->Flink) + 8;
v23 = ExAllocatePoolWithTag(PagedPool, v22, 0x61506553u);
NewProcess[4].ReadyListHead.Blink = v23;
if ( v23 )
{
qmemcpy(v23, ParentProcess_1[4].ReadyListHead.Blink, v22);
NewProcess[4].ReadyListHead.Blink->Blink = NewProcess[4].ReadyListHead.Blink + 1;
goto LABEL_58;
}
LABEL_59:
v11 = STATUS_INSUFFICIENT_RESOURCES;
goto LABEL_96;
}
LABEL_58:
v25 = (NewProcess[1].Affinity & 0xFFFFFFF8);
v26 = MmGetSessionId(NewProcess);
SeSetSessionIdToken(v25, v26);
v46[0] = NewProcess;
v46[1] = 0;
v27 = ExCreateHandle(PspCidTable, v46);
NewProcess[1].DirectoryTableBase[0] = v27;
if ( !v27 )
goto LABEL_59;
*(NewProcess[1].ProcessLock + 8) = v27;
if ( SeDetailedAuditingWithToken(0) )
SeAuditProcessCreation(NewProcess);
if ( ParentProcess_1 )
{
v28 = ParentProcess_1[2].Affinity;
if ( v28 )
{
if ( (v28[38] & 0x1000) == 0 )
{
if ( (Flags & 1) != 0 )
{
v11 = (v28[38] & 0x800) != 0 ? 0 : STATUS_ACCESS_DENIED;
}
else
{
v11 = PspGetJobFromSet(v28, InJob, &NewProcess[2].Affinity);
if ( v11 < 0 )
goto LABEL_96;
v43 = NewProcess[2].Affinity;
v11 = PspAddProcessToJob(v43, NewProcess);
v29 = v43[9].Header.SignalState;
if ( v29 )
{
v11 = SeSubProcessToken(v29, &v50, 0);
if ( v11 < 0 )
goto LABEL_96;
SeAssignPrimaryToken(NewProcess, v50);
ObfDereferenceObject(v50);
}
}
if ( v11 < 0 )
goto LABEL_96;
}
}
}
if ( ParentProcess_1 && v64 )
{
BaseAddress[0] = 0;
BaseAddress[1] = -1;
if ( SectionHandle )
{
v11 = MmCreatePeb(NewProcess, BaseAddress, &NewProcess[4]);
if ( v11 < 0 )
{
*&NewProcess[4].Header.Type = 0;
goto LABEL_96;
}
}
else
{
LOBYTE(BaseAddress[0]) = 1;
v30 = *&ParentProcess_1[4].Header.Type;
*&NewProcess[4].Header.Type = v30;
MmCopyVirtualMemory(process, BaseAddress, NewProcess, v30, 8u, 0, &a7a);
}
}
v31 = v41;
--v41->KernelApcDisable;
ExAcquireFastMutexUnsafe(&PspActiveProcessMutex);
v32 = dword_48315C;
NewProcess[1].DirectoryTableBase[1] = &PsActiveProcessHead;
*&NewProcess[1].LdtDescriptor.LimitLow = v32;
*v32 = NewProcess + 136;
dword_48315C = &NewProcess[1].DirectoryTableBase[1];
ExReleaseFastMutexUnsafe(&PspActiveProcessMutex);
v34 = (*(v31 + 212))++ == -1;
if ( v34 && *(v31 + 52) != v31 + 52 )
{
*(v31 + 73) = 1;
LOBYTE(v33) = 1;
HalRequestSoftwareInterrupt(v33);
}
if ( !ParentProcess_1 || (v35 = PsInitialSystemProcess, ParentProcess_1 != PsInitialSystemProcess) )
v35 = *(v31 + 68);
v11 = SeCreateAccessStateEx(0, v35, &PassedAccessState, v37, DesiredAccess, (PsProcessType + 26));
if ( v11 < 0 )
goto LABEL_96;
v11 = ObInsertObject(NewProcess, &PassedAccessState, DesiredAccess, 1u, 0, &Handle);
v53 = v11;
SeDeleteAccessState(&PassedAccessState);
if ( v11 >= 0 )
{
*&NewProcess[3].StackCount = 1;
PsSetProcessPriorityByClass(NewProcess, 0);
if ( !ParentProcess_1 || ParentProcess_1 == PsInitialSystemProcess )
{
*&NewProcess[3].StackCount = 2035711;
}
else
{
v11 = ObGetObjectSecurity(NewProcess, &SecurityDescriptor, MemoryAllocated);
v53 = v11;
if ( v11 < 0 )
{
ObCloseHandle(Handle, AccessMode[0]);
goto LABEL_96;
}
SubjectSecurityContext.ProcessAuditId = NewProcess;
SubjectSecurityContext.PrimaryToken = PsReferencePrimaryToken(NewProcess);
SubjectSecurityContext.ClientToken = 0;
v62 = SeAccessCheck(
SecurityDescriptor,
&SubjectSecurityContext,
0,
0x2000000u,
0,
0,
(PsProcessType + 26),
AccessMode[0],
&NewProcess[3].StackCount,
&AccessStatus);
ObFastDereferenceObject(&NewProcess[1].Affinity, SubjectSecurityContext.PrimaryToken);
ObReleaseObjectSecurity(SecurityDescriptor, MemoryAllocated[0]);
if ( !v62 )
*&NewProcess[3].StackCount = 0;
*&NewProcess[3].StackCount |= 0x1F07FBu;
}
KeQuerySystemTime(&NewProcess[1].Header.SignalState);
*ProcessHandle = Handle;
ms_exc.registration.TryLevel = -1;
if ( v58 )
v11 = v58;
goto LABEL_96;
}
LABEL_97:
if ( ParentProcess_1 )
ObfDereferenceObject(ParentProcess_1);
return v11;
}
我們都知道,創建進程的時候一定會創建一個線程,被稱之為主線程。上面都是初始化進程結構體,創建TEB等操作,線程在哪裡創建的呢?是因為這個是在3環調用的,我們來看看3環長啥樣子:
BOOL __stdcall CreateProcessW(LPCWSTR lpApplicationName, LPWSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation)
{
return CreateProcessInternalW(
0,
lpApplicationName,
lpCommandLine,
lpProcessAttributes,
lpThreadAttributes,
bInheritHandles,
dwCreationFlags,
lpEnvironment,
lpCurrentDirectory,
lpStartupInfo,
lpProcessInformation,
0);
}
這個函數又調用了CreateProcessInternalW這個函數,我們來繼續點擊去看看,創建線程的函數就在這裏面,但是為了節省篇幅,我們就給出了局部代碼:
BaseInitializeContext(&ThreadContext, v157, SectionInformation, UserStack.StackBase, 0);
v161 = BaseFormatObjectAttributes(&ObjectAttributes, v141, 0);
if ( v184 && v160 && v141 )
{
DirectoryInfo.CurDirRef = v141->nLength;
v200 = v141->lpSecurityDescriptor;
v201 = v141->bInheritHandle;
v200 = 0;
v161 = BaseFormatObjectAttributes(&ObjectAttributes, &DirectoryInfo.CurDirRef, 0);
}
v20 = NtCreateThread(
&ThreadHandle,
0x1F03FFu,
v161,
ProcessHandle,
&ClientId,
&ThreadContext,
&UserStack,
1u);
進程結束淺析
結束函數沒啥可分析了,給出如下IDA偽代碼:
NTSTATUS __stdcall NtTerminateProcess(HANDLE ProcessHandle, NTSTATUS ExitStatus)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
v2 = KeGetCurrentThread();
v3 = v2;
v8 = v2->ApcState.Process;
if ( ProcessHandle )
{
v11 = 1;
}
else
{
ProcessHandle = -1;
v11 = 0;
}
LOBYTE(AccessMode) = v2->PreviousMode;
result = ObReferenceObjectByHandle(ProcessHandle, 1u, PsProcessType, AccessMode, &AccessMode, 0);
v5 = AccessMode;
v6 = AccessMode;
if ( result >= 0 )
{
ProcessHandlea = &AccessMode[146];
if ( (AccessMode[146].Count & 0x2000) != 0 )
PspCatchCriticalBreak("Terminating critical process 0x%p (%s)\n", AccessMode, &AccessMode[93]);
RunRef = v5 + 32;
if ( ExAcquireRundownProtection(v5 + 32) )
{
if ( v11 )
_InterlockedOr(ProcessHandlea, 8u);
ProcessHandleb = 290;
v7 = PsGetNextProcessThread(v6, 0);
if ( v7 )
{
ProcessHandleb = 0;
do
{
if ( v7 != v3 )
PspTerminateThreadByPointer(v7, ExitStatus);
v7 = PsGetNextProcessThread(v6, v7);
}
while ( v7 );
}
ExReleaseRundownProtection(RunRef);
if ( v6 == v8 )
{
if ( v11 )
{
ObfDereferenceObject(v6);
PspTerminateThreadByPointer(v3, ExitStatus);
}
}
else if ( ExitStatus == DBG_TERMINATE_PROCESS )
{
DbgkClearProcessDebugObject(v6, 0);
}
if ( ProcessHandleb == 290 || v6[1].ThreadListHead.Flink && v11 )
{
ObClearProcessHandleTable(v6);
ProcessHandleb = 0;
}
ObfDereferenceObject(v6);
result = ProcessHandleb;
}
else
{
ObfDereferenceObject(v5);
result = STATUS_PROCESS_IS_TERMINATING;
}
}
return result;
}
可以說,結束進程,也就是把它的所有的線程全部幹掉,刪掉進程相關記錄,進程就被殺死了。
進程線程結構體擴展
之前介紹進程線程相關結構體的時候主要介紹關鍵的成員,但是有些成員是在說明信息的時候還是比較重要的,或者沒啥用處做個記錄的,這裡再重新補充一下,僅供了解:
KPROCESS
其結構體如下所示:
kd> dt _KPROCESS
ntdll!_KPROCESS
+0x000 Header : _DISPATCHER_HEADER
+0x010 ProfileListHead : _LIST_ENTRY
+0x018 DirectoryTableBase : [2] Uint4B
+0x020 LdtDescriptor : _KGDTENTRY
+0x028 Int21Descriptor : _KIDTENTRY
+0x030 IopmOffset : Uint2B
+0x032 Iopl : UChar
+0x033 Unused : UChar
+0x034 ActiveProcessors : Uint4B
+0x038 KernelTime : Uint4B
+0x03c UserTime : Uint4B
+0x040 ReadyListHead : _LIST_ENTRY
+0x048 SwapListEntry : _SINGLE_LIST_ENTRY
+0x04c VdmTrapcHandler : Ptr32 Void
+0x050 ThreadListHead : _LIST_ENTRY
+0x058 ProcessLock : Uint4B
+0x05c Affinity : Uint4B
+0x060 StackCount : Uint2B
+0x062 BasePriority : Char
+0x063 ThreadQuantum : Char
+0x064 AutoAlignment : UChar
+0x065 State : UChar
+0x066 ThreadSeed : UChar
+0x067 DisableBoost : UChar
+0x068 PowerState : UChar
+0x069 DisableQuantum : UChar
+0x06a IdealNode : UChar
+0x06b Flags : _KEXECUTE_OPTIONS
+0x06b ExecuteOptions : UChar
ProfileListHead
性能分析相關,一般操作系統會自動處理,如下圖所示,沒啥用處:
ActiveProcessors
當前活動的處理器,表示在哪個核運行。
ReadyListHead
該進程處於就緒狀態的所有進程鏈表。
SwapListEntry
被交換到磁盤上的進程的鏈表,如果進程被交換出去就會掛到這裡。
ThreadListHead
當前進程下的所有線程的鏈表。
ProcessLock
進程鎖,用於同步,防止被同時修改用的,給操作系統用的。
ThreadQuantum
線程默認的時間碎片。
State
表示進程交換到磁盤和內存的狀態。
ThreadSeed
指示Affinity陳述的親核性最理想親的核。
EPROCESS
其結構體如下所示:
kd> dt _EPROCESS
ntdll!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x06c ProcessLock : _EX_PUSH_LOCK
+0x070 CreateTime : _LARGE_INTEGER
+0x078 ExitTime : _LARGE_INTEGER
+0x080 RundownProtect : _EX_RUNDOWN_REF
+0x084 UniqueProcessId : Ptr32 Void
+0x088 ActiveProcessLinks : _LIST_ENTRY
+0x090 QuotaUsage : [3] Uint4B
+0x09c QuotaPeak : [3] Uint4B
+0x0a8 CommitCharge : Uint4B
+0x0ac PeakVirtualSize : Uint4B
+0x0b0 VirtualSize : Uint4B
+0x0b4 SessionProcessLinks : _LIST_ENTRY
+0x0bc DebugPort : Ptr32 Void
+0x0c0 ExceptionPort : Ptr32 Void
+0x0c4 ObjectTable : Ptr32 _HANDLE_TABLE
+0x0c8 Token : _EX_FAST_REF
+0x0cc WorkingSetLock : _FAST_MUTEX
+0x0ec WorkingSetPage : Uint4B
+0x0f0 AddressCreationLock : _FAST_MUTEX
+0x110 HyperSpaceLock : Uint4B
+0x114 ForkInProgress : Ptr32 _ETHREAD
+0x118 HardwareTrigger : Uint4B
+0x11c VadRoot : Ptr32 Void
+0x120 VadHint : Ptr32 Void
+0x124 CloneRoot : Ptr32 Void
+0x128 NumberOfPrivatePages : Uint4B
+0x12c NumberOfLockedPages : Uint4B
+0x130 Win32Process : Ptr32 Void
+0x134 Job : Ptr32 _EJOB
+0x138 SectionObject : Ptr32 Void
+0x13c SectionBaseAddress : Ptr32 Void
+0x140 QuotaBlock : Ptr32 _EPROCESS_QUOTA_BLOCK
+0x144 WorkingSetWatch : Ptr32 _PAGEFAULT_HISTORY
+0x148 Win32WindowStation : Ptr32 Void
+0x14c InheritedFromUniqueProcessId : Ptr32 Void
+0x150 LdtInformation : Ptr32 Void
+0x154 VadFreeHint : Ptr32 Void
+0x158 VdmObjects : Ptr32 Void
+0x15c DeviceMap : Ptr32 Void
+0x160 PhysicalVadList : _LIST_ENTRY
+0x168 PageDirectoryPte : _HARDWARE_PTE_X86
+0x168 Filler : Uint8B
+0x170 Session : Ptr32 Void
+0x174 ImageFileName : [16] UChar
+0x184 JobLinks : _LIST_ENTRY
+0x18c LockedPagesList : Ptr32 Void
+0x190 ThreadListHead : _LIST_ENTRY
+0x198 SecurityPort : Ptr32 Void
+0x19c PaeTop : Ptr32 Void
+0x1a0 ActiveThreads : Uint4B
+0x1a4 GrantedAccess : Uint4B
+0x1a8 DefaultHardErrorProcessing : Uint4B
+0x1ac LastThreadExitStatus : Int4B
+0x1b0 Peb : Ptr32 _PEB
+0x1b4 PrefetchTrace : _EX_FAST_REF
+0x1b8 ReadOperationCount : _LARGE_INTEGER
+0x1c0 WriteOperationCount : _LARGE_INTEGER
+0x1c8 OtherOperationCount : _LARGE_INTEGER
+0x1d0 ReadTransferCount : _LARGE_INTEGER
+0x1d8 WriteTransferCount : _LARGE_INTEGER
+0x1e0 OtherTransferCount : _LARGE_INTEGER
+0x1e8 CommitChargeLimit : Uint4B
+0x1ec CommitChargePeak : Uint4B
+0x1f0 AweInfo : Ptr32 Void
+0x1f4 SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO
+0x1f8 Vm : _MMSUPPORT
+0x238 LastFaultCount : Uint4B
+0x23c ModifiedPageCount : Uint4B
+0x240 NumberOfVads : Uint4B
+0x244 JobStatus : Uint4B
+0x248 Flags : Uint4B
+0x248 CreateReported : Pos 0, 1 Bit
+0x248 NoDebugInherit : Pos 1, 1 Bit
+0x248 ProcessExiting : Pos 2, 1 Bit
+0x248 ProcessDelete : Pos 3, 1 Bit
+0x248 Wow64SplitPages : Pos 4, 1 Bit
+0x248 VmDeleted : Pos 5, 1 Bit
+0x248 OutswapEnabled : Pos 6, 1 Bit
+0x248 Outswapped : Pos 7, 1 Bit
+0x248 ForkFailed : Pos 8, 1 Bit
+0x248 HasPhysicalVad : Pos 9, 1 Bit
+0x248 AddressSpaceInitialized : Pos 10, 2 Bits
+0x248 SetTimerResolution : Pos 12, 1 Bit
+0x248 BreakOnTermination : Pos 13, 1 Bit
+0x248 SessionCreationUnderway : Pos 14, 1 Bit
+0x248 WriteWatch : Pos 15, 1 Bit
+0x248 ProcessInSession : Pos 16, 1 Bit
+0x248 OverrideAddressSpace : Pos 17, 1 Bit
+0x248 HasAddressSpace : Pos 18, 1 Bit
+0x248 LaunchPrefetched : Pos 19, 1 Bit
+0x248 InjectInpageErrors : Pos 20, 1 Bit
+0x248 VmTopDown : Pos 21, 1 Bit
+0x248 Unused3 : Pos 22, 1 Bit
+0x248 Unused4 : Pos 23, 1 Bit
+0x248 VdmAllowed : Pos 24, 1 Bit
+0x248 Unused : Pos 25, 5 Bits
+0x248 Unused1 : Pos 30, 1 Bit
+0x248 Unused2 : Pos 31, 1 Bit
+0x24c ExitStatus : Int4B
+0x250 NextPageColor : Uint2B
+0x252 SubSystemMinorVersion : UChar
+0x253 SubSystemMajorVersion : UChar
+0x252 SubSystemVersion : Uint2B
+0x254 PriorityClass : UChar
+0x255 WorkingSetAcquiredUnsafe : UChar
+0x258 Cookie : Uint4B
RundownProtect
進程停運保護,可以防止他人殺死進程。
SessionProcessLinks
進程的會話子系統相關。
Token
該進程的令牌,與安全相關。
InheritedFromUniqueProcessId
指示被誰創建該進程的pid,這東西比較有用,可以找到父進程。
SeAuditProcessCreationInfo
通過這個可以獲取進程的全路徑,我們舉個例子:
kd> dt _EPROCESS 89a9b648
ntdll!_EPROCESS
……
+0x1f0 AweInfo : (null)
+0x1f4 SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO
+0x1f8 Vm : _MMSUPPORT
……
kd> dx -id 0,0,805539a0 -r1 (*((ntdll!_SE_AUDIT_PROCESS_CREATION_INFO *)0x89a9b83c))
(*((ntdll!_SE_AUDIT_PROCESS_CREATION_INFO *)0x89a9b83c)) [Type: _SE_AUDIT_PROCESS_CREATION_INFO]
[+0x000] ImageFileName : 0x89cc8a18 [Type: _OBJECT_NAME_INFORMATION *]
kd> dx -id 0,0,805539a0 -r1 ((ntdll!_OBJECT_NAME_INFORMATION *)0x89cc8a18)
((ntdll!_OBJECT_NAME_INFORMATION *)0x89cc8a18) : 0x89cc8a18 [Type: _OBJECT_NAME_INFORMATION *]
[+0x000] Name : "\Device\HarddiskVolume1\Program Files\PalmInput\Extensions\Guard\2.6.0.49\PalmInputGuard.exe" [Type: _UNICODE_STRING]
Flags
指示進程的狀態,比較有用。
SubSystemMinorVersion / SubSystemMajorVersion
指示支持的子系統版本,在PE文件的信息中是有描述的。
ExitStatus
進程退出狀態。
ReadOperationCount
調用ReadFile的次數。
WriteOperationCount
調用WriteFile的次數。
OtherOperationCount
調用其他與IO讀寫文件相關的API次數。
KTHREAD
其結構體如下所示:
kd> dt _KTHREAD
ntdll!_KTHREAD
+0x000 Header : _DISPATCHER_HEADER
+0x010 MutantListHead : _LIST_ENTRY
+0x018 InitialStack : Ptr32 Void
+0x01c StackLimit : Ptr32 Void
+0x020 Teb : Ptr32 Void
+0x024 TlsArray : Ptr32 Void
+0x028 KernelStack : Ptr32 Void
+0x02c DebugActive : UChar
+0x02d State : UChar
+0x02e Alerted : [2] UChar
+0x030 Iopl : UChar
+0x031 NpxState : UChar
+0x032 Saturation : Char
+0x033 Priority : Char
+0x034 ApcState : _KAPC_STATE
+0x04c ContextSwitches : Uint4B
+0x050 IdleSwapBlock : UChar
+0x051 Spare0 : [3] UChar
+0x054 WaitStatus : Int4B
+0x058 WaitIrql : UChar
+0x059 WaitMode : Char
+0x05a WaitNext : UChar
+0x05b WaitReason : UChar
+0x05c WaitBlockList : Ptr32 _KWAIT_BLOCK
+0x060 WaitListEntry : _LIST_ENTRY
+0x060 SwapListEntry : _SINGLE_LIST_ENTRY
+0x068 WaitTime : Uint4B
+0x06c BasePriority : Char
+0x06d DecrementCount : UChar
+0x06e PriorityDecrement : Char
+0x06f Quantum : Char
+0x070 WaitBlock : [4] _KWAIT_BLOCK
+0x0d0 LegoData : Ptr32 Void
+0x0d4 KernelApcDisable : Uint4B
+0x0d8 UserAffinity : Uint4B
+0x0dc SystemAffinityActive : UChar
+0x0dd PowerState : UChar
+0x0de NpxIrql : UChar
+0x0df InitialNode : UChar
+0x0e0 ServiceTable : Ptr32 Void
+0x0e4 Queue : Ptr32 _KQUEUE
+0x0e8 ApcQueueLock : Uint4B
+0x0f0 Timer : _KTIMER
+0x118 QueueListEntry : _LIST_ENTRY
+0x120 SoftAffinity : Uint4B
+0x124 Affinity : Uint4B
+0x128 Preempted : UChar
+0x129 ProcessReadyQueue : UChar
+0x12a KernelStackResident : UChar
+0x12b NextProcessor : UChar
+0x12c CallbackStack : Ptr32 Void
+0x130 Win32Thread : Ptr32 Void
+0x134 TrapFrame : Ptr32 _KTRAP_FRAME
+0x138 ApcStatePointer : [2] Ptr32 _KAPC_STATE
+0x140 PreviousMode : Char
+0x141 EnableStackSwap : UChar
+0x142 LargeStack : UChar
+0x143 ResourceIndex : UChar
+0x144 KernelTime : Uint4B
+0x148 UserTime : Uint4B
+0x14c SavedApcState : _KAPC_STATE
+0x164 Alertable : UChar
+0x165 ApcStateIndex : UChar
+0x166 ApcQueueable : UChar
+0x167 AutoAlignment : UChar
+0x168 StackBase : Ptr32 Void
+0x16c SuspendApc : _KAPC
+0x19c SuspendSemaphore : _KSEMAPHORE
+0x1b0 ThreadListEntry : _LIST_ENTRY
+0x1b8 FreezeCount : Char
+0x1b9 SuspendCount : Char
+0x1ba IdealProcessor : UChar
+0x1bb DisableBoost : UChar
MutantListHead
互斥體鏈表相關,具體細節在同步篇進行講解。
Alertable
指示線程是否被喚醒,具體細節在APC篇進行講解。
Priority
線程優先級。
EnableStackSwap
指示是否能將堆棧最為文件交換
ETHREAD
結構體如下所示:
kd> dt _ETHREAD
ntdll!_ETHREAD
+0x000 Tcb : _KTHREAD
+0x1c0 CreateTime : _LARGE_INTEGER
+0x1c0 NestedFaultCount : Pos 0, 2 Bits
+0x1c0 ApcNeeded : Pos 2, 1 Bit
+0x1c8 ExitTime : _LARGE_INTEGER
+0x1c8 LpcReplyChain : _LIST_ENTRY
+0x1c8 KeyedWaitChain : _LIST_ENTRY
+0x1d0 ExitStatus : Int4B
+0x1d0 OfsChain : Ptr32 Void
+0x1d4 PostBlockList : _LIST_ENTRY
+0x1dc TerminationPort : Ptr32 _TERMINATION_PORT
+0x1dc ReaperLink : Ptr32 _ETHREAD
+0x1dc KeyedWaitValue : Ptr32 Void
+0x1e0 ActiveTimerListLock : Uint4B
+0x1e4 ActiveTimerListHead : _LIST_ENTRY
+0x1ec Cid : _CLIENT_ID
+0x1f4 LpcReplySemaphore : _KSEMAPHORE
+0x1f4 KeyedWaitSemaphore : _KSEMAPHORE
+0x208 LpcReplyMessage : Ptr32 Void
+0x208 LpcWaitingOnPort : Ptr32 Void
+0x20c ImpersonationInfo : Ptr32 _PS_IMPERSONATION_INFORMATION
+0x210 IrpList : _LIST_ENTRY
+0x218 TopLevelIrp : Uint4B
+0x21c DeviceToVerify : Ptr32 _DEVICE_OBJECT
+0x220 ThreadsProcess : Ptr32 _EPROCESS
+0x224 StartAddress : Ptr32 Void
+0x228 Win32StartAddress : Ptr32 Void
+0x228 LpcReceivedMessageId : Uint4B
+0x22c ThreadListEntry : _LIST_ENTRY
+0x234 RundownProtect : _EX_RUNDOWN_REF
+0x238 ThreadLock : _EX_PUSH_LOCK
+0x23c LpcReplyMessageId : Uint4B
+0x240 ReadClusterSize : Uint4B
+0x244 GrantedAccess : Uint4B
+0x248 CrossThreadFlags : Uint4B
+0x248 Terminated : Pos 0, 1 Bit
+0x248 DeadThread : Pos 1, 1 Bit
+0x248 HideFromDebugger : Pos 2, 1 Bit
+0x248 ActiveImpersonationInfo : Pos 3, 1 Bit
+0x248 SystemThread : Pos 4, 1 Bit
+0x248 HardErrorsAreDisabled : Pos 5, 1 Bit
+0x248 BreakOnTermination : Pos 6, 1 Bit
+0x248 SkipCreationMsg : Pos 7, 1 Bit
+0x248 SkipTerminationMsg : Pos 8, 1 Bit
+0x24c SameThreadPassiveFlags : Uint4B
+0x24c ActiveExWorker : Pos 0, 1 Bit
+0x24c ExWorkerCanWaitUser : Pos 1, 1 Bit
+0x24c MemoryMaker : Pos 2, 1 Bit
+0x250 SameThreadApcFlags : Uint4B
+0x250 LpcReceivedMsgIdValid : Pos 0, 1 Bit
+0x250 LpcExitThreadCalled : Pos 1, 1 Bit
+0x250 AddressSpaceOwner : Pos 2, 1 Bit
+0x254 ForwardClusterOnly : UChar
+0x255 DisablePageFaultClustering : UChar
CrossThreadFlags
表示線程的狀態和身份,可以設置狀態為系統線程可以使普通權限殺不死。
線程創建淺析
之前我們分析創建進程的時候,是通過NtCreateThread這個函數進行創建主線程,我們來看看裏面有什麼:
NTSTATUS __stdcall NtCreateThread(PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, HANDLE ProcessHandle, PCLIENT_ID ClientId, PCONTEXT ThreadContext, PINITIAL_TEB UserStack, BOOLEAN CreateSuspended)
{
struct _INITIAL_TEB *v8; // eax
int *v9; // ebx
int v11; // ecx
int v12[6]; // [esp+Ch] [ebp-38h] BYREF
_KTHREAD *v13; // [esp+24h] [ebp-20h]
CPPEH_RECORD ms_exc; // [esp+2Ch] [ebp-18h]
ms_exc.registration.TryLevel = 0;
v13 = KeGetCurrentThread();
if ( v13->PreviousMode )
{
if ( ThreadHandle >= MmUserProbeAddress )
*MmUserProbeAddress = 0;
*ThreadHandle = *ThreadHandle;
if ( ClientId )
{
v12[5] = ClientId;
if ( ClientId >= MmUserProbeAddress )
*MmUserProbeAddress = 0;
if ( (ClientId & 3) != 0 )
ExRaiseDatatypeMisalignment();
LOBYTE(ClientId->UniqueProcess) = ClientId->UniqueProcess;
LOBYTE(ClientId->UniqueThread) = ClientId->UniqueThread;
}
if ( !ThreadContext )
{
ms_exc.registration.TryLevel = -1;
return STATUS_INVALID_PARAMETER;
}
if ( (ThreadContext & 3) != 0 )
ExRaiseDatatypeMisalignment();
v8 = MmUserProbeAddress;
if ( ThreadContext >= MmUserProbeAddress )
{
*MmUserProbeAddress = 0;
v8 = MmUserProbeAddress;
}
v9 = UserStack;
if ( (UserStack & 3) != 0 )
ExRaiseDatatypeMisalignment();
if ( UserStack >= v8 )
v8->PreviousStackBase = 0;
}
else
{
v9 = UserStack;
}
v12[0] = *v9;
v11 = v9[1];
v12[1] = v11;
if ( !v12[0] && !v11 )
qmemcpy(v12, v9, 0x14u);
ms_exc.registration.TryLevel = -1;
return PspCreateThread(
ThreadHandle,
DesiredAccess,
ObjectAttributes,
ProcessHandle,
0,
ClientId,
ThreadContext,
v12,
CreateSuspended,
0,
0);
}
瀏覽一遍,發現最終是通過PspCreateThread這個函數進行創建線程實現,點擊去看看:
NTSTATUS __stdcall PspCreateThread(PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, HANDLE ProcessHandle, PVOID a5, PCLIENT_ID ClientId, PCONTEXT ThreadContext, int a8, BOOLEAN CreateSuspended, int a10, int a11)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
CurrentThread = KeGetCurrentThread();
CurrentThread_1 = CurrentThread;
if ( a10 )
AccessMode[0] = 0;
else
AccessMode[0] = CurrentThread->PreviousMode;
v70 = 0;
v12 = 0;
v56 = 0;
if ( ProcessHandle )
{
result = ObReferenceObjectByHandle(ProcessHandle, 2u, PsProcessType, AccessMode[0], &Object, 0);
v12 = Object;
v56 = Object;
}
else if ( a10 )
{
v12 = a5;
ObfReferenceObject(a5);
v56 = a5;
result = 0;
}
else
{
result = STATUS_INVALID_HANDLE;
}
if ( result >= 0 )
{
if ( AccessMode[0] && v12 == PsInitialSystemProcess )
{
v14 = STATUS_INVALID_HANDLE;
LABEL_15:
ObfDereferenceObject(v12);
return v14;
}
v15 = ObCreateObject(AccessMode[0], PsThreadType, ObjectAttributes, *AccessMode, 0, 600, 0, 0, &v59);
if ( v15 < 0 )
{
v14 = v15;
goto LABEL_15;
}
thread = v59;
memset(v59, 0, 0x258u);
thread[1].WaitBlock[0].WaitListEntry.Blink = 0;
thread[1].WaitListEntry.Flink = v12;
*&thread[1].DebugActive = v12[33];
v60[0] = thread;
v60[1] = 0;
v17 = ExCreateHandle(PspCidTable, v60);
*&thread[1].Iopl = v17;
if ( !v17 )
{
v18 = STATUS_INSUFFICIENT_RESOURCES;
LABEL_38:
ObfDereferenceObject(thread);
return v18;
}
thread[1].WaitBlock[0].NextWaitBlock = MmReadClusterSize;
KeInitializeSemaphore(&thread[1].ApcState, 0, 1);
thread[1].Header.WaitListHead.Blink = &thread[1].Header.WaitListHead;
thread[1].Header.WaitListHead.Flink = &thread[1].Header.WaitListHead;
thread[1].WaitStatus = &thread[1].IdleSwapBlock;
*&thread[1].IdleSwapBlock = thread + 528;
thread[1].InitialStack = &thread[1].MutantListHead.Blink;
thread[1].MutantListHead.Blink = &thread[1].MutantListHead.Blink;
thread[1].WaitBlock[0].Thread = 0;
KeInitializeSpinLock(&thread[1].Teb);
thread[1].KernelStack = &thread[1].TlsArray;
thread[1].TlsArray = &thread[1].TlsArray;
v45 = v12 + 32;
if ( !ExAcquireRundownProtection(v12 + 32) )
{
v18 = STATUS_PROCESS_IS_TERMINATING;
goto LABEL_38;
}
if ( ThreadContext )
{
v18 = MmCreateTeb(v12, a8, &thread[1].DebugActive, &v70);
if ( v18 < 0 )
{
v19 = v45;
LABEL_37:
ExReleaseRundownProtection(v19);
goto LABEL_38;
}
ms_exc.registration.TryLevel = 0;
thread[1].WaitListEntry.Blink = ThreadContext->Eip;
thread[1].WaitTime = ThreadContext->Eax;
ms_exc.registration.TryLevel = -1;
v20 = KeInitThread(thread, 0, PspUserThreadStartup, 0, thread[1].WaitListEntry.Blink, ThreadContext, v70, v12);
}
else
{
v70 = 0;
_InterlockedOr(&thread[1].WaitBlock[1], 0x10u);
thread[1].WaitListEntry.Blink = a10;
v20 = KeInitThread(thread, 0, PspSystemThreadStartup, a10, a11, 0, 0, v12);
}
v18 = v20;
if ( v20 < 0 )
{
if ( v70 )
{
MmDeleteTeb(v12, v70);
thread->Teb = 0;
}
LABEL_36:
v19 = v12 + 32;
goto LABEL_37;
}
v21 = CurrentThread_1;
--CurrentThread_1->KernelApcDisable;
v53 = v12 + 27;
_ECX = v12 + 27;
_EDX = 2;
__asm { cmpxchg [ecx], edx }
if ( (v12[146].Count & 8) != 0 )
{
v50 = v12 + 27;
_ECX = v12 + 27;
_EDX = 0;
__asm { cmpxchg [ecx], edx }
v26 = (*(v21 + 212))++ == -1;
if ( v26 && *(v21 + 52) != v21 + 52 )
{
*(v21 + 73) = 1;
LOBYTE(_ECX) = 1;
HalRequestSoftwareInterrupt(_ECX);
}
KeUninitThread(thread);
if ( v70 )
MmDeleteTeb(v12, v70);
v18 = STATUS_PROCESS_IS_TERMINATING;
goto LABEL_36;
}
v27 = v12[104].Count;
v12[104].Count = v27 + 1;
v28 = v12[101].Count;
*&thread[1].BasePriority = v12 + 100;
thread[1].WaitBlock[0].WaitListEntry.Flink = v28;
v28->Flink = &thread[1].BasePriority;
v12[101].Count = &thread[1].BasePriority;
KeStartThread(thread);
v48 = v12 + 27;
_ECX = v12 + 27;
_EDX = 0;
__asm { cmpxchg [ecx], edx }
v31 = CurrentThread_1;
v26 = CurrentThread_1->KernelApcDisable++ == -1;
if ( v26 && *(v31 + 52) != v31 + 52 )
{
CurrentThread_1->ApcState.KernelApcPending = 1;
LOBYTE(_ECX) = 1;
HalRequestSoftwareInterrupt(_ECX);
}
ExReleaseRundownProtection(v12 + 32);
if ( !v27 )
{
WmiTraceProcess(v12);
if ( PspCreateProcessNotifyRoutineCount )
{
v58 = &PspCreateProcessNotifyRoutine;
v51 = 8;
do
{
v32 = ExReferenceCallBackBlock(v58);
v33 = v32;
if ( v32 )
{
v34 = ExGetCallBackBlockRoutine(v32);
v34(v12[83].Count, v12[33].Count, 1);
ExDereferenceCallBackBlock(v58, v33);
}
v58 += 4;
--v51;
}
while ( v51 );
}
}
v35 = v12[77].Count;
if ( v35 && *(v35 + 196) && (v12[145].Count & 5) == 0 )
{
_InterlockedOr(&v12[145], 4u);
--CurrentThread_1->KernelApcDisable;
Resource = (v35 + 32);
ExAcquireResourceSharedLite((v35 + 32), 1u);
v36 = *(v35 + 196);
if ( v36 )
IoSetIoCompletion(v36, *(v35 + 200), v12[33].Count, 0, 6, 0);
ExReleaseResourceLite(Resource);
v37 = CurrentThread_1;
v26 = CurrentThread_1->KernelApcDisable++ == -1;
if ( v26 && *(v37 + 52) != v37 + 52 )
{
*(v37 + 73) = 1;
LOBYTE(v37) = 1;
HalRequestSoftwareInterrupt(v37);
}
}
WmiTraceThread(thread, a8, 1);
if ( PspCreateThreadNotifyRoutineCount )
{
v57 = &PspCreateThreadNotifyRoutine;
v49 = 8;
do
{
v38 = ExReferenceCallBackBlock(v57);
RunRef = v38;
if ( v38 )
{
v39 = ExGetCallBackBlockRoutine(v38);
v39(*&thread[1].DebugActive, *&thread[1].Iopl, 1);
ExDereferenceCallBackBlock(v57, RunRef);
}
v57 += 4;
--v49;
}
while ( v49 );
}
ObReferenceObjectEx(thread, 2);
if ( ThreadContext )
{
P = ExAllocatePoolWithTag(NonPagedPool, 0x30u, 'aCsP');
if ( !P )
{
_InterlockedOr(&thread[1].WaitBlock[1], 2u);
LABEL_64:
v18 = STATUS_INSUFFICIENT_RESOURCES;
LABEL_77:
KeReadyThread(thread);
ObDereferenceObjectEx(thread, 2);
return v18;
}
KeInitializeApc(P, thread, 0, IopDeallocateApc, 0, dword_598B5C, 1, 0);
if ( !KeInsertQueueApc(P, BaseAddress, 0, 0) )
{
_InterlockedOr(&thread[1].WaitBlock[1], 2u);
ExFreePoolWithTag(P, 0);
goto LABEL_64;
}
}
if ( CreateSuspended )
{
ms_exc.registration.TryLevel = 1;
KeSuspendThread(thread);
ms_exc.registration.TryLevel = -1;
if ( (thread[1].WaitBlock[1].WaitListEntry.Flink & 1) != 0 )
KeForceResumeThread(thread);
}
if ( ThreadContext )
v40 = CurrentThread_1->ApcState.Process;
else
v40 = v12;
v63 = SeCreateAccessStateEx(0, v40, &PassedAccessState, v44, DesiredAccess, (PsThreadType + 26));
if ( v63 < 0 )
{
_InterlockedOr(&thread[1].WaitBlock[1], 2u);
if ( CreateSuspended )
KeResumeThread(thread);
v18 = v63;
goto LABEL_77;
}
v63 = ObInsertObject(thread, &PassedAccessState, DesiredAccess, 0, 0, &Handle);
SeDeleteAccessState(&PassedAccessState);
if ( v63 >= 0 )
{
ms_exc.registration.TryLevel = 2;
*ThreadHandle = Handle;
if ( ClientId )
*ClientId = *&thread[1].DebugActive;
ms_exc.registration.TryLevel = -1;
}
else
{
_InterlockedOr(&thread[1].WaitBlock[1], 2u);
if ( CreateSuspended )
KeResumeThread(thread);
}
KeQuerySystemTime(&CurrentTime);
v41 = CurrentTime.QuadPart >> 29;
*&thread[1].Header.Type = 8 * CurrentTime.LowPart;
thread[1].Header.SignalState = v41;
if ( (thread[1].WaitBlock[1].WaitListEntry.Flink & 2) != 0 )
{
*&thread[1].WaitBlock[0].WaitKey = 2032639;
}
else
{
v63 = ObGetObjectSecurity(thread, &SecurityDescriptor, MemoryAllocated);
if ( v63 < 0 )
{
_InterlockedOr(&thread[1].WaitBlock[1], 2u);
if ( CreateSuspended )
KeResumeThread(thread);
KeReadyThread(thread);
ObfDereferenceObject(thread);
ObCloseHandle(Handle, AccessMode[0]);
goto LABEL_95;
}
SubjectSecurityContext.ProcessAuditId = v12;
SubjectSecurityContext.PrimaryToken = PsReferencePrimaryToken(v12);
SubjectSecurityContext.ClientToken = 0;
v42 = &thread[1].WaitBlock[0].WaitKey;
v64 = SeAccessCheck(
SecurityDescriptor,
&SubjectSecurityContext,
0,
0x2000000u,
0,
0,
(PsThreadType + 26),
AccessMode[0],
&thread[1].WaitBlock[0].WaitKey,
&AccessStatus);
ObFastDereferenceObject(&v12[50], SubjectSecurityContext.PrimaryToken);
ObReleaseObjectSecurity(SecurityDescriptor, MemoryAllocated[0]);
if ( !v64 )
*v42 = 0;
*v42 |= 0x61u;
}
KeReadyThread(thread);
ObfDereferenceObject(thread);
LABEL_95:
result = v63;
}
return result;
}
流程和進程線程創建了流程套路幾乎一樣,初始化線程結構體,創建TEB,插入結構體,但裏面有APC相關的知識,具體細節就不再贅述了。
DPC
DPC是什麼?它的英文全稱為Deferred Procedure Call,即延遲過程調用。它最初作用是設計為中斷服務程序的一部分,用來解決中斷服務處理時間過長的問題。因為每次觸發中斷,都會關中斷,然後執行中斷服務例程。由於關中斷了,所以中斷服務例程必須短小精悍,不能消耗過多時間,否則會導致系統丟失大量其他中斷。但是有的中斷,其中斷服務例程要做的事情本來就很多,那怎麼辦?於是,可以在中斷服務例程中先執行最緊迫的那部分工作,然後把剩餘的相對來說不那麼重要的工作移入到DPC函數中去執行。
每當觸發一個中斷時,中斷服務例程可以在當前CPU中插入一個DPC,當執行完ISR,退出ISR後, CPU就會掃描它的DPC隊列,依次執行裏面的每個DPC,當執行完DPC後,才又回到當前線程的中斷處繼續執行。
既然提到ISR,那麼它是什麼?它的英文全稱為Interrupt Service Routines,即中斷服務處理。在Windows中如果有認為不太重要的操作會被打包成一個KDPC結構體,如下所示:
kd> dt _KDPC
ntdll!_KDPC
+0x000 Type : Int2B
+0x002 Number : UChar
+0x003 Importance : UChar
+0x004 DpcListEntry : _LIST_ENTRY
+0x00c DeferredRoutine : Ptr32 void
+0x010 DeferredContext : Ptr32 Void
+0x014 SystemArgument1 : Ptr32 Void
+0x018 SystemArgument2 : Ptr32 Void
+0x01c Lock : Ptr32 Uint4B
打包完畢後,會插入到KPCRB的DpcListHead成員中,等待觸發調用時機。KPCRB結構如下所示,只展示與DPC相關的成員:
kd> dt _KPRCB
ntdll!_KPRCB
……
+0x4b0 DpcTime : Uint4B
+0x4b4 DebugDpcTime : Uint4B
……
+0x860 DpcListHead : _LIST_ENTRY
+0x868 DpcStack : Ptr32 Void
+0x86c DpcCount : Uint4B
+0x870 DpcQueueDepth : Uint4B
+0x874 DpcRoutineActive : Uint4B
+0x878 DpcInterruptRequested : Uint4B
+0x87c DpcLastCount : Uint4B
+0x880 DpcRequestRate : Uint4B
……
+0x8a0 DpcLock : Uint4B
……
+0x8c0 CallDpc : _KDPC
……
非時鐘中斷的中斷、時鐘中斷、主動調用API,相關函數會去輪詢DPC鏈表進行回調。對於用戶或者驅動創建的DPC,還可以加定時器,到指定時間進行觸發,但不會掛到KPCR當中,會掛到時鐘任務當中。
介紹了上面的概念,我們來介紹結構體的相關成員含義:
KDPC
Type
指明該結構體的類型,值為0x13。
Number
指明屬於哪個KPCR。
Importance
優先級,取值0-2,0最低,2最高,優先級越高越優先執行,初始化默認值為1。
DpcListEntry
DPC鏈表,和進程線程鏈表一樣,掛到腰上。
DeferredRoutine
DPC的回調函數地址。
DeferredContext
回調函數上下文,非必須。
SystemArgument1 / SystemArgument2
回調函數的參數,非必須。
Lock
DPC結構體的鎖。
DPC 的初始化
學習DPC,首先我們了解一下它的初始化,我們用IDA定位到該函數:
; void __stdcall KeInitializeDpc(PRKDPC Dpc, PKDEFERRED_ROUTINE DeferredRoutine, PVOID DeferredContext)
public _KeInitializeDpc@12
_KeInitializeDpc@12 proc near ; CODE XREF: IopInitializeIrpStackProfiler()+29↑p
; VdmpDelayInterrupt(x)+26B↓p ...
Dpc = dword ptr 8
DeferredRoutine = dword ptr 0Ch
DeferredContext = dword ptr 10h
mov edi, edi
push ebp
mov ebp, esp
mov eax, [ebp+Dpc]
mov ecx, [ebp+DeferredRoutine]
and dword ptr [eax+1Ch], 0
mov [eax+0Ch], ecx
mov ecx, [ebp+DeferredContext]
mov word ptr [eax], 13h
mov byte ptr [eax+2], 0
mov byte ptr [eax+3], 1
mov [eax+10h], ecx
pop ebp
retn 0Ch
_KeInitializeDpc@12 endp
這個函數十分簡單,用偽代碼展示如下:
void __stdcall KeInitializeDpc(PRKDPC Dpc, PKDEFERRED_ROUTINE DeferredRoutine, PVOID DeferredContext)
{
Dpc->Lock = 0;
Dpc->DeferredRoutine = DeferredRoutine;
Dpc->Type = 19;
Dpc->Number = 0;
Dpc->Importance = 1;
Dpc->DeferredContext = DeferredContext;
}
DPC 的插入
然後我們來看看DPC的插入流程,由於涉及IRQL提權操作,我們先看一下枚舉:
為了增強可讀性,如下是我重命名好的IDA的偽代碼:
BOOLEAN __stdcall KeInsertQueueDpc(PRKDPC Dpc, PVOID SystemArgument1, PVOID SystemArgument2)
{
_KPRCB *kprcb; // esi
bool Importance; // zf
_LIST_ENTRY *DpcListHead; // ecx
_LIST_ENTRY *DpcListEntry; // eax
_LIST_ENTRY *v9; // edx
_LIST_ENTRY *v10; // edx
KIRQL NewIrql; // [esp+Fh] [ebp-1h]
NewIrql = KfRaiseIrql(0x1Fu);
kprcb = MEMORY[0xFFDFF020];
_ECX = &Dpc->Lock;
_EDX = MEMORY[0xFFDFF020] + 0x8A0; // DpcLock
__asm { cmpxchg [ecx], edx }
++kprcb->DpcCount;
++kprcb->DpcQueueDepth;
Importance = Dpc->Importance == 2;
Dpc->SystemArgument1 = SystemArgument1;
Dpc->SystemArgument2 = SystemArgument2;
DpcListHead = &kprcb->DpcListHead;
DpcListEntry = &Dpc->DpcListEntry;
if ( Importance )
{
v9 = DpcListHead->Flink;
DpcListEntry->Flink = DpcListHead->Flink;
Dpc->DpcListEntry.Blink = DpcListHead;
v9->Blink = DpcListEntry;
DpcListHead->Flink = DpcListEntry;
}
else
{
v10 = kprcb->DpcListHead.Blink;
DpcListEntry->Flink = DpcListHead;
Dpc->DpcListEntry.Blink = v10;
v10->Flink = DpcListEntry;
kprcb->DpcListHead.Blink = DpcListEntry;
}
if ( !kprcb->DpcRoutineActive
&& !kprcb->DpcInterruptRequested
&& (Dpc->Importance
|| kprcb->DpcQueueDepth >= kprcb->MaximumDpcQueueDepth
|| kprcb->DpcRequestRate < kprcb->MinimumDpcRate) )
{
LOBYTE(DpcListHead) = 2;
kprcb->DpcInterruptRequested = 1;
HalRequestSoftwareInterrupt(DpcListHead);
}
KfLowerIrql(NewIrql);
return 1;
}
可以看出,如果DPC的優先級高,就會插到DPC鏈表的前面,如果低就會插到末尾。在多核的情況下,其偽代碼會有所不一樣,如下所示:
BOOLEAN __stdcall KeInsertQueueDpc(PRKDPC Dpc, PVOID SystemArgument1, PVOID SystemArgument2)
{
unsigned __int8 Number; // al
_KPRCB *kpcrb; // esi
char v5; // bl
bool v6; // zf
_LIST_ENTRY *v7; // ecx
_LIST_ENTRY *v8; // eax
_LIST_ENTRY *v9; // edx
_LIST_ENTRY *v10; // edx
signed __int32 v12; // [esp+Ch] [ebp-8h]
KIRQL NewIrql; // [esp+13h] [ebp-1h]
NewIrql = KfRaiseIrql(0x1Fu);
Number = Dpc->Number;
if ( Number < 0x20u )
{
v5 = Dpc;
kpcrb = KeGetPcr()->Prcb;
}
else
{
kpcrb = KiProcessorBlock[Number];
v5 = Number - 32;
}
KiAcquireSpinLock();
v12 = _InterlockedCompareExchange(&Dpc->Lock, &kpcrb->DpcLock, 0);
if ( !v12 )
{
++kpcrb->DpcCount;
++kpcrb->DpcQueueDepth;
v6 = Dpc->Importance == 2;
Dpc->SystemArgument1 = SystemArgument1;
Dpc->SystemArgument2 = SystemArgument2;
v7 = &kpcrb->DpcListHead;
v8 = &Dpc->DpcListEntry;
if ( v6 )
{
v9 = v7->Flink;
v8->Flink = v7->Flink;
Dpc->DpcListEntry.Blink = v7;
v9->Blink = v8;
v7->Flink = v8;
}
else
{
v10 = kpcrb->DpcListHead.Blink;
v8->Flink = v7;
Dpc->DpcListEntry.Blink = v10;
v10->Flink = v8;
kpcrb->DpcListHead.Blink = v8;
}
if ( !kpcrb->DpcRoutineActive && !kpcrb->DpcInterruptRequested )
{
if ( kpcrb == KeGetPcr()->Prcb )
{
if ( Dpc->Importance
|| kpcrb->DpcQueueDepth >= kpcrb->MaximumDpcQueueDepth
|| kpcrb->DpcRequestRate < kpcrb->MinimumDpcRate )
{
LOBYTE(v7) = 2;
kpcrb->DpcInterruptRequested = 1;
HalRequestSoftwareInterrupt(v7); //處理 DPC
}
}
else if ( Dpc->Importance == 2 || kpcrb->DpcQueueDepth >= kpcrb->MaximumDpcQueueDepth )
{
kpcrb->DpcInterruptRequested = 1;
KiIpiSend(1 << v5, 2u);
}
}
}
KiReleaseSpinLock(&kpcrb->DpcLock);
KfLowerIrql(NewIrql);
return v12 == 0;
}
這裡提一句,想要獲取多核調試環境極其內核文件,必須把虛擬機的配置配置成多核再重新安裝操作系統。但是我給的符號是單核的,多核的我也踏破鐵鞋也找不到,上面的偽代碼一些結構體我是通過單核的生成的,至於函數是參考資料命名的。如果您有多核的符號,希望您能夠提供,感謝支持。
對於多核的情況,具體細節將會在同步篇進行講解。含有SpinLock函數的這個東西成為自旋鎖。發現這份代碼和單核的會多一些判斷和一個函數KiIpiSend,這個函數作用是通知另一個KPCR執行DPC,了解即可。
DPC 的移除
對於移除DPC代碼很簡單,如下是其偽代碼:
BOOLEAN __stdcall KeRemoveQueueDpc(PRKDPC Dpc)
{
unsigned int *Lock; // esi
_disable(); // cli
Lock = Dpc->Lock;
if ( Lock )
{
--*(Lock - 12);
RemoveEntryList(&Dpc->DpcListEntry);
Dpc->Lock = 0;
}
_enable(); // sti
return Lock != 0;
}
對於多核下,代碼如下,和單核區別不大,只是加入了自旋鎖:
BOOLEAN __stdcall KeRemoveQueueDpc(PRKDPC Dpc)
{
unsigned int *Lock; // edi
_disable();
Lock = Dpc->Lock;
if ( Lock )
{
KiAcquireSpinLock(Dpc->Lock);
if ( Lock == Dpc->Lock )
{
--*(Lock - 12);
RemoveEntryList(&Dpc->DpcListEntry);
Dpc->Lock = 0;
}
KiReleaseSpinLock(Lock);
}
_enable();
return Lock != 0;
}
DPC 的執行
執行DPC是通過KiRetireDpcList執行的,而調用該函數的是通過KiDispatchInterrupt和KiIdleLoop執行的,我們通過偽代碼進行簡單了解:
int __usercall KiRetireDpcList@<eax>(_KPCR *kpcr@<ebx>, _LIST_ENTRY *DPCListHead@<ebp>)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
v13 = 0;
if ( PPerfGlobalGroupMask && (*(PPerfGlobalGroupMask + 4) & 0x80) != 0 )
v13 = 1;
do
{
MEMORY[0xFFDFFBC4] = &v11; // 這裡有錯誤,原彙編:
// mov ds:0FFDFF994h, esp
// 可能是插件的 Bug
// 指向:DpcRoutineActive
do
{
dpc = DPCListHead->Flink;
v3 = DPCListHead->Flink->Flink;
DPCListHead->Flink = v3;
v3->Blink = DPCListHead;
dpc = (dpc - 4); // 掛到腰上了,把指針指到頭部
DeferredRoutine = dpc->DeferredRoutine;
SystemArgument1 = *&dpc->SystemArgument1;
DeferredContext = dpc->DeferredContext;
dpc_1 = dpc;
dpc->Lock = 0;
--kpcr->PrcbData.DpcQueueDepth;
_enable();
if ( v13 ) // 日誌性能分析,忽略
{
v6 = WmiGetCpuClock(DeferredRoutine);
DeferredRoutine = v7;
v11 = v6;
v12 = v7;
}
result = (DeferredRoutine)(dpc_1, DeferredContext, SystemArgument1, HIDWORD(SystemArgument1));// 執行 DPC
if ( v13 )
result = PerfInfoLogDpc(v12, v11, SHIDWORD(v11));
_disable();
}
while ( !IsListEmpty(DPCListHead) );
kpcr->PrcbData.DpcRoutineActive = 0;
kpcr->PrcbData.DpcInterruptRequested = 0;
}
while ( !IsListEmpty(DPCListHead) );
return result;
}
DPC 的使用
既然學習了DPC,沒有應用肯定不行,我們做幾個實驗體驗一下DPC。我們新建一個驅動項目,怎麼配置我就不說了,代碼如下:
#include <ntifs.h>
#include <ntddk.h>
KDPC dpc = { 0 };
VOID DPCRoutine(_In_ struct _KDPC* Dpc, _In_opt_ PVOID DeferredContext,
_In_opt_ PVOID SystemArgument1, _In_opt_ PVOID SystemArgument2)
{
DbgPrint("DPC Running……\n");
}
NTSTATUS UnloadDriver(PDRIVER_OBJECT DriverObject)
{
DbgPrint("Unloaded Successfully!");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
DbgPrint("Loaded Successfully!");
DriverObject->DriverUnload = UnloadDriver;
KeInitializeDpc(&dpc, DPCRoutine, NULL);
KeInsertQueueDpc(&dpc, NULL, NULL);
return STATUS_SUCCESS;
}
編譯然後加載後,就會出現如下結果:
那麼繼續搞得高級點,不是驅動或者用戶創建的DPC支持定時器嗎?我們來搞一下:
#include <ntifs.h>
#include <ntddk.h>
KDPC dpc = { 0 };
KTIMER timer = { 0 };
LARGE_INTEGER duringtime = { 0 };
VOID DPCRoutine(_In_ struct _KDPC* Dpc, _In_opt_ PVOID DeferredContext,
_In_opt_ PVOID SystemArgument1, _In_opt_ PVOID SystemArgument2)
{
DbgPrint("DPC Running……\n");
KeSetTimer(&timer, duringtime, &dpc);
}
VOID UnloadDriver(PDRIVER_OBJECT DriverObject)
{
KeCancelTimer(&timer);
DbgPrint("Unloaded Successfully!");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
DbgPrint("Loaded Successfully!");
DriverObject->DriverUnload = UnloadDriver;
KeInitializeTimer(&timer);
KeInitializeDpc(&dpc, DPCRoutine, NULL);
duringtime.QuadPart = -30 * 1000 * 1000; //負號表示相對時間,此處間隔為3秒
KeSetTimer(&timer, duringtime, &dpc);
return STATUS_SUCCESS;
}
實現的效果如下:
上面實現的就是DPC定時器。
Windows 架構
學習系統內核,最好了解一些該系統的系統架構,示意圖如下,來自潘愛民的《Windows內核原理與實現》:
如有興趣了解其細節,請自行翻找,注意該書是基於WRK的,裏面的結構體相關或者代碼相關的請以逆向結果為準。


