1. 程式人生 > >【舊文章搬運】Windows控制代碼表分配演算法分析(實驗部分)

【舊文章搬運】Windows控制代碼表分配演算法分析(實驗部分)

原文發表於百度空間,2009-03-31
==========================================================================

理論結合實踐,這是我一貫的學習方法~~
實驗目的:以實驗的方式觀察PspCidTable的變化,從中瞭解Windows控制代碼表的分配過程.
實驗器材:Windbg,RunIt(一個可控的不斷建立執行緒的程式),DebugView
知識回顧:



如圖所示,控制代碼表的結構根據TableLevel來確定:
TableLevel為0時,CapturedTable是一級表(藍箭頭)
TableLevel為1時,CapturedTable是二級表,CapturedTable[i]是一級表(綠箭頭)
TableLevel為2時,CapturedTable是三級表,CapturedTable[i]是二級表,CapturedTable[i][j]是一級表(紅箭頭)
三級表的內容是二級表指標,二級表的內容是一級表指標,一級表中放的才是物件及訪問屬性(HANDLE_TABLE_ETNRY結構)

準備工作:獲取PspCidTable的基本資訊

lkd> dd PspCidTable l1
8055a360 e1001810 //獲取PspCidTable的地址
lkd> dt _HANDLE_TABLE e1001810
nt!_HANDLE_TABLE
   +0x000 TableCode        : 0xe1003000 //表基址為0xe1003000,一級表
   +0x004 QuotaProcess     : (null) 
   +0x008 UniqueProcessId : (null) 
   +0x00c HandleTableLock : [4] _EX_PUSH_LOCK
   +0x01c HandleTableList : _LIST_ENTRY [ 0xe100182c
- 0xe100182c ] +0x024 HandleContentionEvent : _EX_PUSH_LOCK +0x028 DebugInfo : (null) +0x02c ExtraInfoPages : 0 +0x030 FirstFree : 0x308 +0x034 LastFree : 0x34c +0x038 NextHandleNeedingPool : 0x800 //當前的控制代碼上限 +0x03c HandleCount : 329 +0x040 Flags : 1 +0x040
StrictFIFO : 0y1

此時可以觀察到PspCidTable=e1001810,當前TableCode為0xe1003000,低兩位表明是一級表,表地址為0xe1003000

lkd> dd 0xe1003000
e1003000 00000000 fffffffe 821bb661 00000000
e1003010 821bb3e9 00000000 821ba021 00000000
e1003020 821bad21 00000000 821baaa9 00000000
e1003030 821ba831 00000000 821ba5b9 00000000
e1003040 821ba341 00000000 821b9021 00000000
e1003050 821b9da9 00000000 821b9b31 00000000
e1003060 821b98b9 00000000 821b9641 00000000
e1003070 821b93c9 00000000 821b8021 00000000

這時可以看到一級表存放的進執行緒物件了

實驗一:觀察控制代碼表的升級
由於二級表升級為三級表需要極大的控制代碼容量,因此我們通常只能觀察到控制代碼表由一級表升為二級表的過程
執行RunIt.exe,按回車不斷建立執行緒,直至新執行緒的ThreadId大於當前控制代碼表的上限0x800.

此時再觀察PspCidTable:

lkd> dt _HANDLE_TABLE e1001810
nt!_HANDLE_TABLE
   +0x000 TableCode        : 0xe11a4001 //這時已經為二級表了
   +0x004 QuotaProcess     : (null) 
   +0x008 UniqueProcessId : (null) 
   +0x00c HandleTableLock : [4] _EX_PUSH_LOCK
   +0x01c HandleTableList : _LIST_ENTRY [ 0xe100182c - 0xe100182c ]
   +0x024 HandleContentionEvent : _EX_PUSH_LOCK
   +0x028 DebugInfo        : (null) 
   +0x02c ExtraInfoPages   : 0
   +0x030 FirstFree        : 0x860
   +0x034 LastFree         : 0x38c
   +0x038 NextHandleNeedingPool : 0x1000 //控制代碼上限達到了0x800*2=0x1000
   +0x03c HandleCount      : 529
   +0x040 Flags            : 1
   +0x040 StrictFIFO       : 0y1

這時的TableCode低兩位表時現在是二級表,掩去低兩位就是二級表的地址0xe11a4000了

lkd> dd 0xe11a4000 //觀察二級表的內容
e11a4000 e1003000 e11b5000 00000000 00000000
e11a4010 00000000 00000000 00000000 00000000
e11a4020 00000000 00000000 00000000 00000000
e11a4030 00000000 00000000 00000000 00000000
e11a4040 00000000 00000000 00000000 00000000
e11a4050 00000000 00000000 00000000 00000000
e11a4060 00000000 00000000 00000000 00000000
e11a4070 00000000 00000000 00000000 00000000

可以看到,原來的一級表e1003000已經成為了二級表中的第一個元素.同時新分配了一個一級表為e11b5000.這樣,控制代碼表的升級就完成了

實驗二:觀察新分配的控制代碼表是如何填充的
前面已經分析過,新分配的控制代碼表被填充成一個有序的FreeHandle序列.
觀察新分配的這個二級表:

lkd> dd e11b5000
e11b5000 00000000 fffffffe 81f008b9 00000000
e11b5010 81f00641 00000000 81f003c9 00000000
e11b5020 81f5d021 00000000 81f5dda9 00000000
e11b5030 81f5db31 00000000 81f5d8b9 00000000
e11b5040 81f5d641 00000000 81f5d3c9 00000000
e11b5050 81eff021 00000000 81effda9 00000000
e11b5060 81effb31 00000000 81eff8b9 00000000
e11b5070 81eff641 00000000 81eff3c9 00000000 //RunIt建立的最後一個執行緒的ETHREAD在e11b5078處
lkd> dd
e11b5080 82012921 00000000 00000000 00000220 //這裡的一部分控制代碼也被使用過了,因為可能別的程序也建立了執行緒
e11b5090 00000000 00000000 00000000 00000478
e11b50a0 00000000 0000038c 81f5cda9 00000000
e11b50b0 00000000 00000850 00000000 0000084c
e11b50c0 00000000 00000864 00000000 00000868
e11b50d0 00000000 0000086c 00000000 00000870
e11b50e0 00000000 00000874 00000000 00000878
e11b50f0 00000000 0000087c 00000000 00000880
lkd> 
e11b5100 00000000 00000884 00000000 00000888
e11b5110 00000000 0000088c 00000000 00000890
e11b5120 00000000 00000894 00000000 00000898
e11b5130 00000000 0000089c 00000000 000008a0
e11b5140 00000000 000008a4 00000000 000008a8
e11b5150 00000000 000008ac 00000000 000008b0
e11b5160 00000000 000008b4 00000000 000008b8
e11b5170 00000000 000008bc 00000000 000008c0

由圖可知,最後一個ThreadId=0x83c,那麼它在第二個表中的偏移是e11b5000+(0x83c-0x800)*2=e11b5078

從e11b5080到e11b50c0這部分的內容表明該範圍內的部分控制代碼已經被使用過且又釋放了(如果想避免該問題,你可以使用livekd的方式進行本實驗,這樣中斷到偵錯程式時就不會有其它動作來干擾我們的觀察),但是尚未影響到e11b50c0之後的部分.
來觀察這裡:

e11b50c0 00000000 00000864 00000000 00000868
e11b50d0 00000000 0000086c 00000000 00000870
e11b50e0 00000000 00000874 00000000 00000878
e11b50f0 00000000 0000087c 00000000 00000880

e11b50c0作為二級表中的第二個一級表,它所對應的控制代碼為:

(e11b50c0-e11b5000)/2+0x800*(2-1)=0x860 //如果瞭解了控制代碼表的基本結構,這個計算很容易理解
而它的NextFreeHadleTableEntry則指向它緊挨著的下一個HANDLE_TABLE_ENTRY的所對應的控制代碼0x864
而且很容易看出0x864,0x868,0x86c...構成了一個等差數列.
這個結果可以與前面對ExpAllocateLowLevelTable函式的分析對比,兩者是完全一致的.
附Runit程式的原始碼:

// RunIt.cpp : Defines the entry point for the console application.
//
#include <windows.h>
#include <stdio.h>

DWORD WINAPI ThreadProc(LPVOID lpParameter ); HANDLE hEvent = NULL;
int main(int argc, char* argv[]) { int i=0; DWORD tid=0,oldtid=0; printf("MyPID=%d 0x%x\n",GetCurrentProcessId(),GetCurrentProcessId()); printf("每按一次回車鍵將產生一個新執行緒.\n"); hEvent=CreateEvent(NULL,FALSE,TRUE,"TEST"); for (i=0;i<2000;i++) { getchar(); CreateThread(NULL,0,ThreadProc,NULL,NULL,&tid); printf("Runing %d...\tTID=%4d\t0x%x",i,tid,tid); if (tid==oldtid)//已經達到當前程序所能建立執行緒數量的上限 { break; } oldtid=tid; //Sleep(20); } while (1) { Sleep(100);//停在這裡 } return 0; } DWORD WINAPI ThreadProc(LPVOID lpParameter ) { WaitForSingleObject(hEvent,INFINITE); return 0; }