寫在前面
此係列是本人一個字一個字碼出來的,包括示例和實驗截圖。由於系統核心的複雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閒錢,可以打賞支援我的創作。如想轉載,請把我的轉載資訊附在文章後面,並宣告我的個人資訊和本人部落格地址即可,但必須事先通知我。
你如果是從中間插過來看的,請仔細閱讀 羽夏看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題解決。
KiFindReadyThread
分析這塊涉及演算法,經查閱是通過二分法進行查詢的。演算法實現原理是我的知識盲區,我僅把流程說一下:首先該函式會解析KiReadySummary
,找到從左起第一個為1的位數,再用該位獲取從KiDispatchReadListHead
中的第一個_KTHREAD
執行緒,將其從連結串列中摘除再判斷如果摘除後該連結串列為空,則找到相應的KiReadySummary
位將其置0,然後將對應找到的執行緒結構體返回。至於其中的詳細細節,可以參考這位博友的分析:KiFindReadyThread分析 - 查詢下一個就緒執行緒 。如下是IDA
的偽C程式碼,僅供參考:
PETHREAD __fastcall KiFindReadyThread(ULONG ProcessorNumber, KPRIORITY LowPriority)
{
int v2; // ecx
unsigned int v3; // eax
unsigned int v4; // edx
int v5; // edx
int v6; // eax
_LIST_ENTRY *v7; // esi
PETHREAD ReadyThread; // eax
_LIST_ENTRY *v9; // ecx
_LIST_ENTRY *v10; // edi
v2 = 16;
v3 = KiReadySummary & ~((1 << LowPriority) - 1);
v4 = HIWORD(v3);
if ( !HIWORD(v3) )
{
v2 = 0;
v4 = v3;
}
if ( (v4 & 0xFFFFFF00) != 0 )
v2 += 8;
v5 = v2 + KiFindFirstSetLeft[v3 >> v2];
v6 = v3 << (31 - v5);
v7 = &KiDispatcherReadyListHead[2 * v5];
if ( !v6 )
return 0;
while ( v6 >= 0 )
{
LOBYTE(v5) = v5 - 1;
--v7;
v6 *= 2;
if ( !v6 )
return 0;
}
v9 = v7->Flink->Flink;
ReadyThread = &v7->Flink[-12];
v10 = ReadyThread->WaitListEntry.Blink;
v10->Flink = v9;
v9->Blink = v10;
if ( IsListEmpty(v7) )
KiReadySummary &= ~(1 << v5);
return ReadyThread;
}
至此第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
的,裡面的結構體相關或者程式碼相關的請以逆向結果為準。