Friday, March 6, 2009

學習Windbg系列:定位Kernel32.dll的方法 (2)

相關知識:Windows Knowledge, PE Format, Windbg command, Intel x86 knowledge

     上次我們討論過如何利用PEB定位出Kernel32.dll,這次要討論怎樣使用TEB定位的方式。本篇的內容適用於XP,其他平台的原理相通。首先打開計算機,然後使用Windbg選擇File->Attach to a Process "附身"到calc.exe。所謂發文不附圖,此風不可長:
AttachToProcess
       建議勾選底下的"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的開頭。這裡利用兩個運作法則:

  1. 因為DLL在記憶體中的alignment是64k,所以開頭位置最後一定是四個0。
  2. 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
       登登!找到了!我們來確認一下對不對,將結果與!peb做個比較:
   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所存放的資訊:

  1. fs:[00]放的是TEB,因為TEB的第一個欄位是_NT_TIB,所以這裡也可以用_NT_TIB來解釋記憶體裡面的資料。fs所指的位置可以用dg指令查詢。
  2. fs:[18]放的是TEB在memory的位置,也就是fs所指的位置,這個值會與dg所得出的值相同。
  3. fs:[30]放的是PEB在memory的位置,也就是TEB加上offset 30的欄位。

下一篇是"定位Kernel32.dll"的最後一集。預計會仔細討論SEH的結構,以及如何利用SEH list找到Kernel32.dll的開頭。(待續)

No comments: