首頁 | 安全文章 | 安全工具 | Exploits | 本站原創 | 關于我們 | 網站地圖 | 安全論壇
  當前位置:主頁>安全文章>文章資料>網絡安全>文章內容
NT下動態切換進程分析筆記
來源:http://www.whitecell.org 作者:sinister 發布時間:2005-11-28  

NT 下動態切換進程分析筆記

Author: sinister
Email: [email protected]
Homepage:http://www.whitecell.org
Date: 2005-11-16


2005-2-22

在應用層想要動態嵌入其他進程內存空間,我們可以調用 CreateRemoteThread()
等相關函數來實現。那么在內核態想要動態嵌入其他進程內存空間又如何做呢?這時
我們需要調用一個未公開的內核函數 KeAttachProcess(),利用這個函數我們可以在
內核態實現將代碼插入到其他進程地址空間中。下面是一段演示代碼。


NTSTATUS PsLookupProcessByProcessId( IN ULONG ulProcId, OUT PEPROCESS * pEProcess);
VOID KeAttachProcess ( IN PEPROCESS pEProcess );
VOID KeDetachProcess ( VOID );


NTSTATUS KeSwitchProcess(ULONG uPID)
{
PEPROCESS EProcess;
LPTSTR lpCurProc;
DWORD dwCR3;
NTSTATUS ntStatus;

ntStatus = PsLookupProcessByProcessId(uPID, &EProcess);

if (!NT_SUCCESS( ntStatus ))
{

DbgPrint("PsLookupProcessByProcessId()\n");
return ntStatus;
}

KeAttachProcess(EProcess);
__asm{
mov eax,cr3
mov dwCR3,eax
}

lpCurProc = (LPTSTR)EProcess;
lpCurProc = lpCurProc + ProcessNameOffset;
DbgPrint("CR3 = %x, PROCESS = PROCESS NAME: %s, PROCESS ID: %d, PROCESS ADDRESS %x:\n",
dwCR3,
lpCurProc,
uPID,
EProcess
);

KeDetachProcess();

return STATUS_SUCCESS;

}

NTSTATUS KeEnumProcess(VOID)
{
ULONG nextoffset;
NTSTATUS rc;
ULONG ReturnLength = 0;
SYSTEM_PROCESSES *curr = NULL;
PVOID SystemInformationBuffer = ExAllocatePool(PagedPool, 255*sizeof(SYSTEM_PROCESSES));

rc = ZwQuerySystemInformation(5, SystemInformationBuffer, 255*sizeof(SYSTEM_PROCESSES), &ReturnLength );

if(NT_SUCCESS(rc))
{
curr = (SYSTEM_PROCESSES *)SystemInformationBuffer;

while(curr)
{

rc = SwitchProcess(curr->ProcessId);
if (!NT_SUCCESS( rc ))
{
DbgPrint("AttachProcess() to failed!\n");
}

nextoffset = curr->NextEntryDelta;
if (nextoffset) {
curr = (SYSTEM_PROCESSES *)((PCHAR)curr + nextoffset);
}
else curr = NULL;
}
}

ExFreePool(SystemInformationBuffer);
return rc;
}


為了調試方便,我枚舉系統中所有進程,并依次調用 KeAttachProcess(),KeDetachP
rocess() 函數動態嵌入和退出,通過打印 CR3 的值我們可以看到確實是實現了動態嵌
入的目的,在這里我們并不想介紹嵌入函數的用法,而是想要了解動態切換進程內存空
間的實現方法。首先我們來分析一下 KeAttachProcess() 函數的流程,當調用此函數時,
首先從 KPCR 結構中得到當前線程 ETHREAD 結構,并提升當前 IRQL 為 DISPATCH_LEVEL
(防止在進程切換中被其他軟件列程所中斷),并從形參中取出要切換進程的 EPROCESS
結構與當前進程 EPROCESS 結構進行比較,如果是當前進程的話,則降低當前 IRQL 為
初始值,函數返回。如果要進行切換的進程不是當前進程的話,先要判斷當前線程是否
處于 APC 與 DPC 活動狀態(ETHREAD->ApcStateIndex,KPCR->DpcRoutineActive),
如果是則不允許動態切換進程,跳轉到藍屏處,系統崩潰。(微軟規定在進行動態切換
時不允許 APC ,DPC 列程運行,否則將系統崩潰,這應該不是必須的)如果不是則從形
參中取當前線程(ETHREAD)、要切換的進程(EPROCESS)初始的 IRQL 值和當前線程的
APC 保存狀態 (ETHREAD->SavedApcState) 做為參數,來調用 KiAttachProcess()函
數完成具體的切換。


從上面可以看出 KeAttachProcess() 函數只是進行了一些相關參數的設置和系統環
境的判斷后簡單的調用了 KiAttachProcess() 函數,那么我們需要對 KiAttachProcess()
流程做一下分析。當調用此函數時先把要切換的進程的內核堆棧數加一(EPROCESS->Stack
Count),并從形參中取得當前線程保存 APC 狀態的位置,(ETHREAD->SaveApcState)
得到當前線程 APC 狀態(ETHREAD->ApcState),來調用 KiMoveApcState() 函數,將當
前線程APC 狀態 (ETHREAD->ApcState) 復制到當前線程 SavedApcState 處保存。然后
將當前線程內核 APC 的 Progress 狀態、內核模式 APC 的 Pending 狀態、用戶模式
APC 的 Pending 態均設置為 FALSE(ETHREAD->KernelApcInProgress、ETHREAD->Kernel
ApcPending、ETHREAD->UserApcPending),并初始化當前線程內核模式與用戶模式 APC
狀態鏈(ETHREAD->>ApcState.ApcListHead[KernelMode]、ETHREAD->ApcState.ApcList
Head[UserMode]),然后將當前線程所在進程(ETHREAD->ApcState->EPROCESS)設置為
要切換的進程(EPROCESS),比較當前線程保存 APC 狀態(ETHREAD->SavedApcState)
是否與形參中(SavedApcState)要輸出的保存 APC 態相等,如相等則需要將當前線程
APC 狀態與保存 APC 狀態(ETHREAD->ApcState、ETHREAD->SavedApcState)分別賦與
當前線程的 ApcStatePointer[0] 與 ApcStatePointer[1],(ApcStatePointer 是
KAPC_STATE 結構數組而 KAPC_STATE 又是由 LIST_ENTRY 鏈表描述的)然后在設置當前
線程 APC 狀態索引為 1(ETHREAD->ApcStateIndex)。這里需要重點講解一下,上述基
本都是處理與 APC 相關的操作,(APC 即“異步過程調用” )為什么要做這些操作呢?
在進程調度中怎么沒有看到相關的操作?那是因為進行強行切換時需要中斷當前線程運
行切換到新的進程中,這種切換又跟時鐘中斷所產生的進程調度不一樣,時鐘中斷產生
的進程調度會根據時間片與線程優先級等讓被中斷的線程 APC 重新得以運行,而這種強
行進程切換如果不調用退出函數,是不會讓原線程 APC 運行的,所以首先要保留當前線
程的 APC 狀態,狀態保存后為了不讓當前 APC 狀態影響要切換的新進程,則需要把相關
APC 位清0。為了更加明確,我這里配合 KeDetachProcess() 函數深入講解一下,當調用
KeDetachProcess() 函數退出時,首先得到當前線程所指象的進程(ETHREAD->ApcState-
>EPROCESS),這里要注意,當前運行環境是已被嵌入的新進程,所以得到的是被嵌入的
進程,退出函數的下一步就是調用KiMoveApcState 來恢復 SavedApcState 到當前線程
APC 狀態(ETHREAD->ApcState),
這時的 ETHREAD->ApcState->EPROCESS 是原始進程和剛才第一次調用得到的被嵌入的新
進程是兩個 EPROCESS 結構地址了,待做一些相關操作后調用 KiAttachProcess() 來切
換回到原始進程。在段落開頭時講過 KiAttachProcess() 調用會先判斷要進行嵌入的是
否為同一進程,如果是則退出,經過上述對線程相關的 APC 操作后,現在是完全不同的
兩個進程,所以可以順利的切換回原始進程。想象一下我們經常通過一個線程(ETHREAD)
的偏移 0x44 處得到它所屬的進程(EPROCESS)而這個進程正是在當前線程的 APC 狀態
中的(ETHREAD->ApcState->EPROCESS)通過上面的分析,可以看出微軟把對 APC 的處
理與ETHREAD,EPROCESS 結構之間的連接安排的非常巧妙,充分考慮了各種環境應用。
好了,當相關 APC 設置完成后,我們需要比較要切換的進程是否在內存中?(EPROCESS
->State)(因為有可能進程在等待或掛起時相關頁面已交換到硬盤頁面文件中)如果不
存在則不能馬上進行切換,要將當前線程等待鏈(ETHREAD->WaitListEntry)插入到要
切換進程的就緒鏈中(EPROCESS->ReadyListHead),并設置當前線程為就緒狀態(ETHR
EAD->State)和當前線程鏈所指向的進程就緒隊列(ETHREAD->ProcessReadyQueue)為
TRUE。然后判斷要切換的進程狀態(EPROCESS->State)是否已經交換出當前內存,如果
為其他狀態則直接調用 KiSwapThread() 函數來完成切換。如果已交換出當前內存狀態
則將要切換的進程狀態(EPROCESS->State)設置為 Transition 并將要切換進程的交換
鏈(EPROCESS->SwapListEntry)插入到全局進程輸入交換鏈 KiProcessInSwapListHead
中繼續設置全局交換事件狀態(KiSwapEvent.Header.SignalState),判斷全局交換事
件等待鏈頭(KiSwapEvent.Header.WaitListHead)是否為空如果不為空則需要調用 KiW
aitTest() 函數來測試全局交換事件 (KiSwapEvent)余額。最后調用 KiSwapThread()
來完成最終切換并跳轉到函數結束處。如果存在當前內存中,則遍歷要切換進程的就緒
鏈(EPROCESS->ReadyListHead),如果要切換進程的下一節點沒有就緒,則從線程等待
鏈里得到一個線程(ETHREAD)并移除剛剛得到的要切換進程的節點,設置得到的線程鏈
所指向的進程就緒隊列(ETHREAD->ProcessReadyQueue)為 FALSE,然后調用 KiReady
Thread() 來就緒得到的線程(ETHREAD),并得到要切換進程就緒鏈的下一節點,重復
上述步驟直到要切換進程的下一節點就緒,這時調用 KiSwapProcess() 函數來完成最終
的切換工作,切換完成后從形參中得到初始 IRQL 值,調用 KfLowerIrql() 來降低當前
IRQL 為初始值,函數結束。這里可能會有疑問,既然當前進程在內存中直接調用 KiSw
apProcess() 來切換不就行了?為什么還需要做那么多的設置?那是因為必須是有一個
線程在你要切換進程的就緒隊列中,這個時候才可以實現強行切換。


這里對最終切換進行一個分析,上面提到了兩種切換方式,一種是 KiSwapThread(),
在這份筆記里我不打算記錄它,我想在下一份筆記里將線程優先級分析等結合在一起來
講。這份筆記里僅僅記錄對 KiSwapProcess() 的分析,其實 KiSwapProcess()和《NT
內核的進程調度分析筆記》中寫到的 SwapContext() 函數大致一樣,而且還比他簡單了
許多,因為要被嵌入的進程許多環境和參數都已設置好了。函數實現就是,先得到要切換
進程的 LDT 描述符(EPROCESS->LdtDescriptor)與頁目錄表(EPROCESS->DirectoryTa
bleBase),判斷要切換的進程 LDT 不為空,如果不為空則從 KPCR 中取得 KGDT 的位
置,并從 KGDT 中索引到 LDT,把要切換的進程(EPROCESS->LdtDescriptor)中的 LDT
賦與 KPCR 中的LDT。再得到 KPCR 中 IDT的位置,把當前進程(EPROCESS->Int21Descr
iptor)中的 INT 21中斷賦與 KPCR 中 KIDT中的相應位置,使當前進程可以調用 INT
21。最后跳轉到調用 LLDT 使當前所有設置生效。(按理說 NT 內核中 32 位應用程序
是不使用 LDT 的但為什么在線程切換中會有設置LDT的部分呢?這是為了向下兼容 16
位的應用程序,當調度到一個 16 位的應用程時則會特意為它分配 LDT 并且使 IDT 中的
INT 21有效,玩過 DOS 的人都知道 INT 21 是 DOS 下的系統調用,可以試著運行一個
16 位的 DOS 程序,然后觀察下 IDT 表就會發現,原來沒有用到的 INT 21 會被設置成
一個 16 位的 TrapGate),否則用要切換進程的頁目錄來刷新 KTSS 中的 CR3 值與當前
CR3 值,并用要切換進程的 IOPM 來填充 KTSS 中的 IOPM。完成切換,函數返回。


:u KeAttachProcess l 200
ntoskrnl!KeAttachProcess
0008:8042BE1A PUSH EBP
0008:8042BE1B MOV EBP,ESP
0008:8042BE1D PUSH ECX
0008:8042BE1E PUSH ESI
0008:8042BE1F MOV EAX,FS:[00000124]
0008:8042BE25 MOV ESI,EAX 注釋:ESI = 當前線程(ETHREAD)
0008:8042BE27 CALL [HAL!KeRaiseIrqlToDpcLevel]
0008:8042BE2D MOV ECX,[EBP+08] 注釋:ECX = 要切換進程的(EPROCESS)
0008:8042BE30 MOV [EBP-04],AL 注釋:保存初始 IRQL 值到臨時變量
0008:8042BE33 CMP [ESI+44],ECX
0008:8042BE36 JNZ 8042BE46
0008:8042BE38 MOV CL,AL
0008:8042BE3A CALL 80403FD0 注釋:降低當前 IRQL(CL=初始IRQL值)
0008:8042BE3F POP ESI
0008:8042BE40 LEAVE
0008:8042BE41 RET 0004 注釋:KeAttachProcess() 函數調用完畢,返回。

從 KPCR 結構中得到當前線程 ETHREAD 結構,并提升當前 IRQL 為 DISPATCH_LEVEL 并
從形參中取出要切換進程的 EPROCESS 結構與當前進程 EPROCESS 結構進行比較,如果
要進行切換的進程不是當前進程的話,則跳轉到具體切換地址進行處理。如果是當前進
程的話,則降低當前 IRQL 為初始值,函數返回。

0008:8042BE44 JMP 8042BE3F

跳轉函數完成地址。

0008:8042BE46 CMP BYTE PTR [ESI+00000159],00
0008:8042BE4D JNZ 8042BE6C

判斷如果當前線程(ETHREAD->ApcStateIndex)處于 APC 運行狀態,則不允許動態切換
進程,跳轉到藍屏處,系統崩潰。

0008:8042BE4F MOV EAX,FS:[0000080C]
0008:8042BE55 TEST EAX,EAX
0008:8042BE57 JNZ 8042BE6C

判斷如果當前 有 DCP 列程運行 (KPCR->DpcRoutineActive) 則不允許動態切換進程,
跳轉到藍屏處,系統崩潰。

0008:8042BE59 LEA EAX,[ESI+00000140] 注釋:當前線程的 APC 狀態(ETHREAD->SavedApcState)
0008:8042BE5F PUSH EAX
0008:8042BE60 PUSH DWORD PTR [EBP-04] 注釋:初始的 IRQL 值
0008:8042BE63 PUSH ECX 注釋:要切換的進程(EPROCESS)
0008:8042BE64 PUSH ESI 注釋:當前線程(ETHREAD)
0008:8042BE65 CALL 8042C2F2 注釋:調用 KiAttachProcess() 函數

如果當前沒有 APC,DPC 等調用處理,則取當前線程(ETHREAD)、要切換的進程(EPROC
ESS)初始的 IRQL 值和當前線程的 APC 保存狀態 (ETHREAD->SavedApcState) 做為參
數,來調用KiAttachProcess() 函數完成具體的切換。

0008:8042BE6A JMP 8042BE3F

進程切換完成后,直接跳轉函數完成地址。

0008:8042BE6C MOV EAX,FS:[0000080C]
0008:8042BE72 PUSH EAX
0008:8042BE73 MOVZX EAX,BYTE PTR [ESI+00000159]
0008:8042BE7A PUSH EAX
0008:8042BE7B PUSH DWORD PTR [ESI+44]
0008:8042BE7E PUSH ECX
0008:8042BE7F PUSH 05
0008:8042BE81 CALL ntoskrnl!KeBugCheckEx

調用 KeBugCheckEx() 藍屏,系統崩潰,并顯示錯誤信息(當前進程,要切換到的目標進
程、APC,DPC 狀態等)


_______________________________________________________________________________________

下面是 KiAttachProcess() 實現細節

:u 8042c2f2 l 200

0008:8042C2F2 PUSH EBP
0008:8042C2F3 MOV EBP,ESP
0008:8042C2F5 PUSH EBX
0008:8042C2F6 PUSH ESI
0008:8042C2F7 MOV ESI,[EBP+08] 注釋:ESI = 當前線程(ETHREAD)
0008:8042C2FA PUSH EDI
0008:8042C2FB PUSH DWORD PTR [EBP+14] 注釋:形參 SavedApcState
0008:8042C2FE MOV EDI,[EBP+0C] 注釋:EDI = 要切換進程的(EPROCESS)
0008:8042C301 LEA EBX,[ESI+34] 注釋:EBX = 當前線程APC狀態(ETHREAD->ApcState)

0008:8042C304 INC WORD PTR [EDI+60]
0008:8042C308 PUSH EBX
0008:8042C309 CALL 8042C3FC 注釋:調用 KiMoveApcState() 函數

把要切換的進程(EPROCESS->StackCount)的內核堆棧數加一,并從形參中取得當前線程
(ETHREAD->SaveApcState)保存APC狀態的位置,得到當前線程(ETHREAD->ApcState)
APC 狀態,來調用 KiMoveApcState() 函數。


0008:8042C30E AND BYTE PTR [ESI+48],00
0008:8042C312 AND BYTE PTR [ESI+49],00
0008:8042C316 AND BYTE PTR [ESI+4A],00
0008:8042C31A LEA EAX,[ESI+3C]
0008:8042C31D MOV [ESI+40],EAX
0008:8042C320 PUSH 01
0008:8042C322 MOV [EAX],EAX
0008:8042C324 LEA EAX,[ESI+00000140] 注釋:EAX = 當前線程(ETHREAD->SavedApcState)保存 ACP 狀態
0008:8042C32A CMP [EBP+14],EAX 注釋:[EBP+14] = 形參 SavedApcState。
0008:8042C32D MOV [ESI+38],EBX
0008:8042C330 MOV [EBX],EBX
0008:8042C332 MOV [ESI+44],EDI
0008:8042C335 POP EDX
0008:8042C336 JNZ 8042C34A

將當前線程(ETHREAD->KernelApcInProgress、ETHREAD->KernelApcPending、ETHREAD->
UserApcPending)內核 APC 的 Progress 狀態、內核模式 APC 的 Pending 狀態、用戶
模式 APC 的 Pending 態均設置為 FALSE,并初始化當前線程ETHREAD->>ApcState.ApcL
istHead[KernelMode] ETHREAD->>ApcState.ApcListHead[UserMode])內核模式與用戶模
式 APC 狀態鏈,然后將當前線程(ETHREAD->ApcState->EPROCESS)所在進程設置為要切
換的進程(EPROCESS),比較當前線程(ETHREAD->SavedApcState)保存 APC 狀態是否
與形參中(SavedApcState)要輸出的保存 APC 態相等,如果不相等則直接跳轉到比較
當前進程(EPROCESS->State)狀態處。

0008:8042C338 MOV [ESI+0000012C],EAX
0008:8042C33E MOV [ESI+00000130],EBX
0008:8042C344 MOV [ESI+00000159],DL 注釋:EDX = 1

如果當前線程(ETHREAD->SavedApcState)保存 APC 狀態與形參中(SavedApcState)要
輸出的保存 APC 狀態相等則將當前線程(ETHREAD->ApcState、ETHREAD->SavedApcState)
APC 狀態與保存 APC 狀態分別賦與當前線程的 ApcStatePointer[0] 與 ApcStatePointer
[1],(ApcStatePointer 是 KAPC_STATE 結構數組而 KAPC_STATE 又是由 LIST_ENTRY 鏈
表描述的)然后在設置當前線程(ETHREAD->ApcStateIndex)APC 狀態索引為 1。

0008:8042C34A CMP BYTE PTR [EDI+65],00 注釋:要切換的進程(EPROCESS->State)狀態
0008:8042C34E JNZ 8042C391

比較要切換的進程(EPROCESS->State)是否在內存中?如果不存在則直接跳轉到設置當前
線程(ETHREAD->State)狀態與切換線程處執行。

0008:8042C350 LEA ESI,[EDI+40] 注釋:ESI = 要切換進程(EPROCESS->ReadyListHead)的就緒鏈表
0008:8042C353 MOV EAX,[ESI]
0008:8042C355 CMP EAX,ESI
0008:8042C357 JZ 8042C374
0008:8042C359 MOV EDX,[EAX]
0008:8042C35B LEA ECX,[EAX-5C] 注釋:ECX = 等待線程(ETHREAD)鏈
0008:8042C35E MOV EAX,[EAX+04]
0008:8042C361 MOV [EAX],EDX
0008:8042C363 MOV [EDX+04],EAX
0008:8042C366 AND BYTE PTR [ECX+0000011D],00 注釋:線程鏈所指向的進程就緒隊列(ETHREAD->ProcessReadyQueue)
0008:8042C36D CALL 8043150B 注釋: 調用 KiReadyThread() 函數
0008:8042C372 JMP 8042C353
0008:8042C374 MOV EAX,[EBP+14] 注釋:EAX = 形參 SavedApcState
0008:8042C377 PUSH DWORD PTR [EAX+10] 注釋:當前線程(ETHREAD->SavedApcState->EPROCESS)所在進程
0008:8042C37A PUSH EDI 注釋:要切換的進程
0008:8042C37B CALL 8040438C 注釋:調用 KiSwapProcess() 函數
0008:8042C380 MOV CL,[EBP+10]
0008:8042C383 CALL 80403FD0 注釋:調用 KfLowerIrql() 函數


如果要切換的進程(EPROCESS)在內存中則遍歷要切換進程(EPROCESS->ReadyListHead)
的就緒鏈,(ReadyListHead 是 LIST_ENTRY 結構),如果要切換進程的下一節點沒有就
緒,則從線程等待鏈里得到一個線程(ETHREAD)并移除剛剛得到的要切換進程的節點,
設置得到的線程鏈所指向的進程就緒隊列(ETHREAD->ProcessReadyQueue)為 FALSE,
然后調用 KiReadyThread() 來就緒得到的線程(ETHREAD),并得到要切換進程就緒鏈的
下一節點,重復上述步驟直到要切換進程的下一節點就緒,這時調用 KiSwapProcess()
函數來完成最終的切換工作,切換完成后從形參中得到初始 IRQL 值,調用 KfLowerIr
ql() 來降低當前 IRQL 為初始值。


0008:8042C388 POP EDI
0008:8042C389 POP ESI
0008:8042C38A POP EBX
0008:8042C38B POP EBP
0008:8042C38C RET 0010

函數結束,返回。

0008:8042C38F JMP 8042C388

跳轉到函數結束。

0008:8042C391 LEA EAX,[EDI+40] 注釋:EAX = 要切換進程(EPROCESS->ReadyListHead)的就緒鏈表
0008:8042C394 MOV [ESI+2D],DL 注釋:[ESI+2D] = 當前線程(ETHREAD->State)狀態
0008:8042C397 MOV [ESI+0000011D],DL 注釋:當前線程(ETHREAD->ProcessReadyQueue)鏈所指向的進程就緒隊列
0008:8042C39D LEA ECX,[ESI+5C] 注釋:ECX = 當前線程(ETHREAD->WaitListEntry)等待鏈
0008:8042C3A0 MOV EBX,[EAX+04]
0008:8042C3A3 MOV [ECX],EAX
0008:8042C3A5 MOV [ESI+60],EBX
0008:8042C3A8 MOV [EBX],ECX
0008:8042C3AA MOV [EAX+04],ECX
0008:8042C3AD CMP [EDI+65],DL 注釋:要切換的進程(EPROCESS->State)狀態
0008:8042C3B0 JNZ 8042C3EE
0008:8042C3B2 MOV BYTE PTR [EDI+65],02
0008:8042C3B6 MOV ECX,[80482714]
0008:8042C3BC LEA EAX,[EDI+48] 注釋:EAX = 要切換進程(EPROCESS->SwapListEntry)的交換鏈
0008:8042C3BF MOV [EDI+4C],ECX
0008:8042C3C2 MOV DWORD PTR [EAX],80482710 注釋:80482710 = 全局進程輸入交換鏈(KiProcessInSwapListHead)
0008:8042C3C8 MOV [ECX],EAX
0008:8042C3CA CMP DWORD PTR [80482C98],80482C98 注釋:80482C98 = 全局交換事件等待鏈(KiSwapEvent.Header.WaitListHead)
0008:8042C3D4 MOV [80482714],EAX
0008:8042C3D9 MOV [80482C94],EDX 注釋:[80482C94] 全局交換事件狀態(KiSwapEvent.Header.SignalState)
0008:8042C3DF JZ 8042C3EE
0008:8042C3E1 PUSH 0A
0008:8042C3E3 MOV ECX,80482C90 注釋:80482C90 = 全局交換事件(KiSwapEvent)
0008:8042C3E8 POP EDX
0008:8042C3E9 CALL 80432BF1 注釋:調用 KiWaitTest() 函數
0008:8042C3EE MOV AL,[EBP+10]
0008:8042C3F1 MOV [ESI+54],AL 注釋:[ESI+54] = 當前線程(ETHREAD->WaitIrql)等待狀態的 IRQL
0008:8042C3F4 CALL 80404080 注釋:調用 KiSwapThread() 函數
0008:8042C3F9 JMP 8042C388
0008:8042C3FB INT 3

如果要切換的進程(EPROCESS)不在內存中則將當前線程(ETHREAD->WaitListEntry)等
待鏈插入到要切換進程(EPROCESS->ReadyListHead)的就緒鏈表中,并設置當前線程
(ETHREAD->State)為就緒狀態和當前線程(ETHREAD->ProcessReadyQueue)鏈所指向的
進程就緒隊列為 TRUE。然后判斷要切換的進程(EPROCESS->State)狀態是否已經交換出
當前內存,如果為其他狀態則直接跳轉到 KiSwapThread()函數來切換當前線程。如果已
交換出當前內存狀態則將要切換的進程(EPROCESS->State)狀態設置為Transition 并將
要切換進程(EPROCESS->SwapListEntry)的交換鏈(SwapListEntry 是 LIST_ENTRY 結
構)插入到全局進程輸入交換鏈 KiProcessInSwapListHead 中 (KiProcessInSwapList
Head 是 LIST_ENTRY 結構),繼續設置全局交換事件狀態(KiSwapEvent.Header.Signal
State),判斷全局交換事件等待鏈頭(KiSwapEvent.Header.WaitListHead)是否為空如
果不為空則需要調用 KiWaitTest() 函數來測試全局交換事件 (KiSwapEvent)余額。最
后調用 KiSwapThread() 來完成線程切換并跳轉到函數結束處。


_______________________________________________________________________________________

下面是 KiMoveApcState() 函數實現細節


0008:8042C3FC MOV EDX,[ESP+04]
0008:8042C400 MOV EAX,[ESP+08]
0008:8042C404 PUSH ESI
0008:8042C405 PUSH EDI
0008:8042C406 PUSH 06
0008:8042C408 MOV ESI,EDX 注釋:EDX,ESI = 當前線程(ETHREAD->ApcState)
0008:8042C40A POP ECX
0008:8042C40B MOV EDI,EAX 注釋:EAX,EDI = 當前線程(ETHREAD->SavedApcState)
0008:8042C40D REPZ MOVSD

當前線程的 ETHREAD->SavedApcState 與 ETHREAD->ApcState 都是 KAPC_STATE 結構,而
KAPC_STATE 結構又是一個雙項鏈表 LIST_ENTRY 結構(LIST_ENTRY 結構在 DDK 頭文件中
有定義)構成的數組 ApcListHead[2] ,數組織分別標識處了內核模式(ApcListHead[KernelMode]
)與用戶模式(ApcListHead[UserMode])的 APC 鏈。以上代碼是將 ETHREAD->ApcState 復制
到 ETHREAD->SavedApcState 中去。

0008:8042C40F MOV ESI,[EDX]
0008:8042C411 CMP ESI,EDX

0008:8042C413 JNZ 8042C431

判斷當前線程(ETHREAD->ApcState[KernelMode])內核模式的 APC 鏈是否為空,如果不為
空的話則跳轉到保存內核模式 APC 雙項鏈 ApcState[KernelMode] 到 SavedApcState[KernelMode]
中。

0008:8042C415 MOV [EAX+04],EAX
0008:8042C418 MOV [EAX],EAX

如果當前線程(ETHREAD->ApcState[KernelMode])內核模式的 APC 鏈為空,則初始化當
前線程(ETHREAD->SavedApcState[KernelMode])的 SavedApcState 鏈表保存內核模式的
APC 狀態。

0008:8042C41A MOV ESI,[EDX+08]
0008:8042C41D LEA ECX,[EDX+08]
0008:8042C420 CMP ESI,ECX
0008:8042C422 JNZ 8042C440

繼續比較當前線程(ETHREAD->ApcState[UserMode])用戶模式的 APC 鏈是否為空,如果
不為空的話則跳轉到保存用戶模式 APC 雙項鏈 ApcState[UserMode] 到 SavedApcState
[UserMode]中。

0008:8042C424 LEA ECX,[EAX+08]
0008:8042C427 MOV [EAX+0C],ECX
0008:8042C42A MOV [ECX],ECX

如果當前線程(ETHREAD->ApcState[UserMode])用戶模式的 APC 鏈為空,則初始化當前
線程(ETHREAD->SavedApcState[UserMode])的 SavedApcState 鏈表保存用戶模式的 APC 狀態。

0008:8042C42C POP EDI
0008:8042C42D POP ESI
0008:8042C42E RET 0008

函數結束,返回。

0008:8042C431 MOV ECX,[EDX+04]
0008:8042C434 MOV [EAX],ESI
0008:8042C436 MOV [EAX+04],ECX
0008:8042C439 MOV [ESI+04],EAX
0008:8042C43C MOV [ECX],EAX
0008:8042C43E JMP 8042C41A

設置內核模式 APC 雙項鏈 ApcState[KernelMode].FLink 與 ApcState[KernelMode].BLink
到 SavedApcState[KernelMode].FLink 和 SavedApcState[KernelMode].BLink 中。然后
跳轉到繼續比較當前線程(ETHREAD->ApcState[UserMode])用戶模式的 APC 鏈是否為空
處。


0008:8042C440 MOV EDX,[EDX+0C]
0008:8042C443 LEA ECX,[EAX+08]
0008:8042C446 MOV [EAX+0C],EDX
0008:8042C449 MOV [ECX],ESI
0008:8042C44B MOV [ESI+04],ECX
0008:8042C44E MOV [EDX],ECX
0008:8042C450 JMP 8042C42C

設置用戶模式 APC 雙項鏈 ApcState[UserMode].FLink 與 ApcState[UserMode].BLink
到 SavedApcState[UserMode].FLink 和 SavedApcState[UserMode].BLink 中。然后跳轉
到函數結束處。

_______________________________________________________________________________________

下面是 KiSwapProcess 函數實現細節

:u 8040438c l 100

0008:8040438C MOV EDX,[ESP+04]
0008:80404390 TEST WORD PTR [EDX+20],FFFF 注釋: EPROCESS->LdtDescriptor
0008:80404396 JNZ 804043BC
0008:80404398 XOR EAX,EAX
0008:8040439A LLDT AX
0008:8040439D MOV ECX,[FFDFF040] 注釋: ECX = KPCR->KTSS
0008:804043A3 XOR EAX,EAX
0008:804043A5 MOV GS,AX
0008:804043A8 MOV EAX,[EDX+18] 注釋:EAX = EPROCESS->DirectoryTableBase
0008:804043AB MOV [ECX+1C],EAX
0008:804043AE MOV CR3,EAX
0008:804043B1 MOV AX,[EDX+30]
0008:804043B5 MOV [ECX+66],AX
0008:804043B9 RET 0008

得到要切換進程的 LDT 描述符(EPROCESS->LdtDescriptor)與頁目錄表(EPROCESS->
DirectoryTableBase),判斷要切換的進程 LDT 不為空,則跳轉到設置 INT 21 處繼續
運行,否則用要切換進程的頁目錄來刷新 KTSS 中的 CR3 值與當前 CR3 值,并用要切
換進程的 IOPM 來填充 KTSS 中的 IOPM。完成切換,函數返回。


0008:804043BC MOV ECX,[FFDFF03C] 注釋:ECX = KPCR->KGDT
0008:804043C2 MOV EAX,[EDX+20]
0008:804043C5 MOV [ECX+48],EAX
0008:804043C8 MOV EAX,[EDX+24]
0008:804043CB MOV [ECX+4C],EAX
0008:804043CE MOV ECX,[FFDFF038] 注釋:ECX = KPCR->KIDT
0008:804043D4 MOV EAX,[EDX+28]
0008:804043D7 MOV [ECX+00000108],EAX
0008:804043DD MOV EAX,[EDX+2C]
0008:804043E0 MOV [ECX+0000010C],EAX
0008:804043E6 MOV EAX,00000048
0008:804043EB JMP 8040439A

從 KPCR 中取得 KGDT 的位置,并從 KGDT 中索引到 LDT,把當前進程(EPROCESS)中的
LDT 賦與 KPCR 中的 LDT。再得到 KPCR 中 KIDT 的位置,把當前進程(EPROCESS)中的
INT 21 中斷賦與 KPCR 中 KIDT 中的相應位置,并跳轉到設置 LDT 處使當前進程可以使
用 INT 21 。


筆記是今年春節利用放假時間寫的,當時分析到一半才發現原來 WIN2K 源代碼中已
經包含了此部分,無奈已經把匯編進行了簡單的注釋,索性就這樣寫下去。錯誤之處再
所難免,還望得到您的指正。


參考資源: Windows 2000 源代碼
感謝 FlashSky,SoBeIt 與我探討。



WSS(Whitecell Security Systems),一個非營利性民間技術組織,致力于各種系統安全技術的研究。堅持傳統的hacker精神,追求技術的精純。
WSS 主頁:http://www.whitecell.org/
WSS 論壇:http://www.whitecell.org/forums/



 
[推薦] [評論(0條)] [返回頂部] [打印本頁] [關閉窗口]  
匿名評論
評論內容:(不能超過250字,需審核后才會公布,請自覺遵守互聯網相關政策法規。
 §最新評論:
  熱點文章
·一句話木馬
·samcrypt.lib簡介
·教你輕松查看QQ空間加密后的好友
·web sniffer 在線嗅探/online ht
·SPIKE與Peach Fuzzer相關知識
·Cisco PIX525 配置備忘
·用Iptables+Fedora做ADSL 路由器
·檢查 Web 應用安全的幾款開源免
·asp,php,aspx一句話集合
·Md5(base64)加密與解密實戰
·風險評估中的滲透測試
·QQ2009正式版SP4文本信息和文件
  相關文章
·NT內核的進程調度分析筆記
·基于DoS攻擊的隨機數據包標記源
·kernel inline hook 繞過vice檢
·AIX內核的虛擬文件系統框架
·使用omniORBpy開發簡單CORBA程序
·ASP安全配置不完全手冊
·誰更安全?黑客眼中的 防火墻與
·從漏洞及攻擊分析到NIDS規則設計
·安全成交換機的基本功能
·PHP漏洞中的戰爭
·簡評中小企業網絡安全市場
·從后臺得到webshell技巧大匯總
  推薦廣告
CopyRight © 2002-2020 VFocuS.Net All Rights Reserved
期本期特码