相關知識:Windows Knowledge, PE Format, Windbg command, Intel x86 knowledge
上次我們討論過如何利用PEB定位出Kernel32.dll,這次要討論怎樣使用TEB定位的方式。本篇的內容適用於XP,其他平台的原理相通。首先打開計算機,然後使用Windbg選擇File->Attach to a Process "附身"到calc.exe。所謂發文不附圖,此風不可長:
建議勾選底下的"Noninvasive"。如果沒有勾選,被debug的程式將會多出一個執行緒(你是誰派來的?我是debugger派來的! )。同時Windbg結束時,calc.exe也會隨之結束。
在前一篇,我們利用了PEB裡面的LDR資訊找出裝載的DLL,本篇將利用TEB的資訊找出Kernel32.dll的基底位置。TEB裡頭記錄了許多與此thread相關的資訊,譬如stack起始位置以及大小、PEB的位置、例外處理者的資訊、TLS...etc。其中有一塊資訊叫做Thread Information Block,簡稱TIB,symbol name是_NT_TIB。它的歷史可以追朔到OS/2時期,所儲存的資訊也相當實用。(TIB的內容可以在ntpsapi.h找到)
1: 0:001> dt _TEB
2: ntdll!_TEB
3: +0x000 NtTib : _NT_TIB
4: +0x01c EnvironmentPointer : Ptr32 Void
5: +0x020 ClientId : _CLIENT_ID
6: +0x028 ActiveRpcHandle : Ptr32 Void
7: +0x02c ThreadLocalStoragePointer : Ptr32 Void
8: +0x030 ProcessEnvironmentBlock : Ptr32 _PEB
9: ...
10: 0:001> dt _NT_TIB
11: ntdll!_NT_TIB
12: +0x000 ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD
13: +0x004 StackBase : Ptr32 Void
14: +0x008 StackLimit : Ptr32 Void
15: ...
TEB的位置儲存在fs:[00],而_NT_TIB剛好是TEB的第一個欄位,所以fs:[00]也可以用_NT_TIB來解釋。那麼如何計算出fs指向那裡呢?方法一是使用dg(display selector)指令:
1: 0:000> r
2: eax=0048e710 ebx=00000000 ecx=00480000 edx=26010e71 esi=0007fee8 edi=01014018
3: eip=7c90e4f4 esp=0007fde0 ebp=0007fdfc iopl=0 nv up ei pl zr na pe nc
4: cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
5: ntdll!KiFastSystemCallRet:
6: 7c90e4f4 c3 ret
7: 0:000> dg 003b
8: P Si Gr Pr Lo
9: Sel Base Limit Type l ze an es ng Flags
10: ---- -------- -------- ---------- - -- -- -- -- --------
11: 003B 7ffdf000 00000fff Data RW Ac 3 Bg By P Nl 000004f3
方法二是取用fs:[18]的值,TEB在這個位置存放了開頭的virtual address。
1: 0:000> dd fs:[18] L1
2: 003b:00000018 7ffdf000
接著我們要來找出stack的開頭,這個資料存放在TIB裡面。如果觀察整個堆疊,會發現程式的主執行緒起始函式是Kernel32.dll的BaseProcessStart,接著才是main。其他的執行緒,起始函式則是Kernel32.dll的BaseThreadStart,接下來才是user指定的thread函式。
1: 0:000> k
2: ChildEBP RetAddr
3: 0007fddc 7e4191be ntdll!KiFastSystemCallRet
4: 0007fdfc 010021b0 USER32!NtUserGetMessage+0xc
5: 0007ff1c 010125e9 calc!WinMain+0x25f
6: 0007ffc0 7c817067 calc!WinMainCRTStartup+0x174
7: 0007fff0 00000000 kernel32!BaseProcessStart+0x23
所以我們可以利用_NT_TIB之中stack起始位置的資訊,進而找尋BaseProcessStart在堆疊上留下的痕跡,得到一個位於Kernel32.dll之內的位置:
1: 0:000> dt _NT_TIB 7ffdf000
2: ntdll!_NT_TIB
3: +0x000 ExceptionList : 0x0007ff10 _EXCEPTION_REGISTRATION_RECORD
4: +0x004 StackBase : 0x00080000
5: +0x008 StackLimit : 0x0007d000
6: +0x00c SubSystemTib : (null)
7: +0x010 FiberData : 0x00001e00
8: +0x010 Version : 0x1e00
9: +0x014 ArbitraryUserPointer : (null)
10: +0x018 Self : 0x7ffdf000 _NT_TIB
11: 0:000> dds 0x00080000-0x70 L20
12: 0007ff90 00000000
13: 0007ff94 00000001
14: 0007ff98 003d3f60
15: 0007ff9c 003d2b18
16: 0007ffa0 00000000
17: 0007ffa4 00000000
18: 0007ffa8 0007ff34
19: 0007ffac a6dc2d08
20: 0007ffb0 0007ffe0
21: 0007ffb4 010128e2 calc!_except_handler3
22: 0007ffb8 010015e0 calc!`string'+0x4
23: 0007ffbc 00000000
24: 0007ffc0 0007fff0
25: 0007ffc4 7c817067 kernel32!BaseProcessStart+0x23
26: 0007ffc8 00370036
27: 0007ffcc 00350034
28: 0007ffd0 7ffd4000
29: 0007ffd4 8054b6ed
30: 0007ffd8 0007ffc8
31: 0007ffdc 8a0eb5b8
32: 0007ffe0 ffffffff
33: 0007ffe4 7c839ac0 kernel32!_except_handler3
34: 0007ffe8 7c817070 kernel32!`string'+0x98
35: 0007ffec 00000000
36: 0007fff0 00000000
37: 0007fff4 00000000
38: 0007fff8 01012475 calc!WinMainCRTStartup
39: 0007fffc 00000000
40: 00080000 78746341
請看第33、34行,這邊Kernel32.dll放了一些東西在stack上面,然後於25行Push了return address並呼叫calc的WinMain。Kernel32.dll到底放了什麼在Stack上面?答案是SEH的結構。Windows Internals 4th P115 有一段BaseProcessStart的pseudo code,大致如下:
void Pseudo_StartProcess(LPTHREAD_START_ROUTINE lpStartAddr,
LPVOID lpParm){
__TRY{
Dword dwExitCode = lpStartAddr(lpParm);
ExitThread(dwExitCode);
}_except(UnhandleExceptionFilter(GetExceptionInformation()))
{
ExitProcess(GetExceptionCode());
}
}
BaseProcessStart是一個很單純的函式,它最主要的工作就是放置一個例外處理在整個thread的最外層。如果發生了沒有人處理的例外,BaseProcessStart會攔住並且最終呼叫ExitProcess。
回到主題,我們現在有了7c817070 / 7c839ac0這兩個位置,就可以推回Kernel32.dll的開頭。這裡利用兩個運作法則:
- 因為DLL在記憶體中的alignment是64k,所以開頭位置最後一定是四個0。
- PE file的開頭一定是 "MZ",也就是5A4D。
因為7c817070比較7c839ac0小,所以我們由7c810000開始找尋:
1: 0:000> dw 7c810000 L1
2: 7c810000 488d
3: 0:000> dw 7c800000 L1
4: 7c800000 5a4d
1: 0:000> !peb
2: PEB at 7ffd4000
3: InheritedAddressSpace: No
4: ReadImageFileExecOptions: No
5: BeingDebugged: No
6: ImageBaseAddress: 01000000
7: Ldr 001a1e90
8: Ldr.Initialized: Yes
9: Ldr.InInitializationOrderModuleList: 001a1f28 . 001a2ef0
10: Ldr.InLoadOrderModuleList: 001a1ec0 . 001a2ee0
11: Ldr.InMemoryOrderModuleList: 001a1ec8 . 001a2ee8
12: Base TimeStamp Module
13: 1000000 3b7d8410 Aug 18 04:52:32 2001 C:\WINDOWS\system32\calc.exe
14: 7c900000 4802a12c Apr 14 08:11:24 2008 C:\WINDOWS\system32\ntdll.dll
15: 7c800000 4802a12c Apr 14 08:11:24 2008 C:\WINDOWS\system32\kernel32.dll
本篇結束之前,小小的整理一下fs所存放的資訊:
- fs:[00]放的是TEB,因為TEB的第一個欄位是_NT_TIB,所以這裡也可以用_NT_TIB來解釋記憶體裡面的資料。fs所指的位置可以用dg指令查詢。
- fs:[18]放的是TEB在memory的位置,也就是fs所指的位置,這個值會與dg所得出的值相同。
- fs:[30]放的是PEB在memory的位置,也就是TEB加上offset 30的欄位。
下一篇是"定位Kernel32.dll"的最後一集。預計會仔細討論SEH的結構,以及如何利用SEH list找到Kernel32.dll的開頭。(待續)
No comments:
Post a Comment