?? 03 reing.txt
字號:
內核反匯編技術
===============================
Усыпляющее шептание алых просторов
Сон зовет меня и мои мечты освобождены
Оставленная мной действительность
не важно, если я не проснусь
(c) by Anathema
Windows NT主要是由C寫成的,所以總的來說進程本身的反匯編不是很復雜。通常對局部變量和參數的使用是通過地址和用EBP形成的stack frame來進行的。例如:
PAGE:801932D4 mov eax, large fs:0
PAGE:801932DA push ebp
PAGE:801932DB mov ebp, esp ; 堆棧中的frame
PAGE:801932DD push 0FFFFFFFFh
PAGE:801932DF push offset $T13371
PAGE:801932E4 push offset __except_handler3
PAGE:801932E9 push eax
PAGE:801932EA xor eax, eax
PAGE:801932EC mov large fs:0, esp
PAGE:801932F3 sub esp, 64h ; 局部變量在堆棧中的位置
PAGE:801932F6 mov [ebp+Flag], al ; 使用局部變量
有時,編譯器會生成更為優化的代碼,直接使用ESP來引用堆棧。
.text:80124320 sub esp, 8
.text:80124323 test byte ptr ds:_NtGlobalFlag+2, 8
.text:8012432A push ebx
.text:8012432B push esi
.text:8012432C push edi
.text:8012432D push ebp
.text:8012432E jnz loc_FFFFF000_80124443
.text:80124334 mov esi, [esp+18h+arg_0] ; 引用第一個參數
IDA PRO這個特別的反匯編器可以跟蹤堆棧的創建,所以建立了下面例子中的相應的常量。在調用函數時,參數以相反的順序放在堆棧中。調用函數自己要負責清理堆棧(編譯器就是這樣為C函數生成代碼的)。
PAGE:80193288 push [ebp+ExceptPort]
PAGE:8019328B push [ebp+DebugPort]
PAGE:8019328E push [ebp+SectionHandle]
PAGE:80193291 push [ebp+bInheritHandle]
PAGE:80193294 push edx ; ffffffff
PAGE:80193295 push [ebp+ObjectAttributes] ;(參數3)
PAGE:80193298 push [ebp+Access] ; (參數2)
PAGE:8019329B push ecx ; Handle (參數1)
PAGE:8019329C call _PspCreateProcess@32
; PspCreateProcess (Handle,Access,
; ObjectAttributes,-1,bIbjeritHandle.SectionHandle,DebugPort,ExceptPort);
PAGE:801932A1
PAGE:801932A1 NtCreateProcessExit: ; CODE XREF: _NtCreateProcess@32+71 j
PAGE:801932A1 ; _NtCreateProcess@32+80 j
PAGE:801932A1 mov ecx, [ebp+var_10]
PAGE:801932A4 pop edi
PAGE:801932A5 mov large fs:0, ecx
PAGE:801932AC pop esi
PAGE:801932AD pop ebx
PAGE:801932AE mov esp, ebp
PAGE:801932B0 pop ebp
PAGE:801932B1 retn 20h ; 清理堆棧(返回后ESP加上0x20)
為了提高速度,有時在OS內核中會使用fastcall的函數,參數通過堆棧傳遞。例如:
PAGE:8018CC9D mov ecx, [ebp+pObject] ; 第一個參數(下一個參數在edx中)
PAGE:8018CCA0 call @ObfDereferenceObject@4 ; fastcall函數
在這個例子中使用了內核中內部的非導出函數。在下一個例子中,我們使用HAL.DLL中的未公開的導出的fastcall函數:
.text:801335E2 mov ecx, eax ; OldIrql
.text:801335E4 call ds:__imp_@KfLowerIrql@4
fastcall函數在其名字中都有字符‘f’。
Microsoft提供了符號信息,這些符號信息可以用來調試程序。這些信息可以確定內部函數(非導出的)和全局變量的真實名稱。這就簡化了辨別函數和變量名稱的工作,而且除此之外,通過后綴@N可以確定函數參數的數量。
調試信息是以NT 4.0的.DBG文件和Windows 2K的.PDB文件的形式提供的。SoftICE和IDA都通曉PDB和DBG文件(IDA使用插件來加載ntoskrnl.pdb)。
內核中的函數、變量和結構體的名稱本身都能表達一些信息。前綴通常有兩種意思,描述函數的特征或是用于子系統的數據。例如:Mm - 子系統內存,Cc - 緩存,Ob - 對象管理器,Ps - 進程管理,Se - 內存管理器,Ke - 內核其它的結構體,Ex - 執行體系統。如果函數是初始化用的(或其可能會轉入此類函數),則在前綴的第一個字母后加一個字符‘i’。例如Ki、Mi。使用Fastcall的函數在前綴后加‘f’。系統調用使用前綴Nt。這些函數不是內核導出的函數,它們的地址記錄在service table里。調用服務要通過軟中斷0x2e。內核導出了Zw函數,這些函數是對中斷的封裝。
.text:8011A49C _ZwCreateFile@44 proc near ; CODE XREF: _FsRtlpOpenDev@8+4D p
.text:8011A49C arg_0 = byte ptr 4
.text:8011A49C mov eax, 17h
.text:8011A4A1 lea edx, [esp+arg_0]
.text:8011A4A5 int 2Eh ; 中斷處理程序調用NtCreateFile
.text:8011A4A7 retn 2Ch
.text:8011A4A7 _ZwCreateFile@44 endp
我不知道字符Zw是什么意思(好像只有內核的設計者知道)。可能是Zero Wheel(或是零環)的意思,因為Zw函數是從內核模式調用的(在DDK中描述了一些)。從用戶模式下,系統服務是通過NTDLL(實現于用戶模式)調用的,NTDLL導出了NtXXX的封裝函數ZwXXX。
內核中的名字主要要遵循一種描述規則。當然,名字本身承載著意義。名字的內容通常是行為及其對象,即對對象進行某種行為。如:ObCreateObject。
許多的操作系統函數都僅僅是對內核內部函數的封裝。例如,NtCreateSection調用了MmCreateSection,用的參數也都相同。現在,如果統計一下的話,許多Nt函數的原型都是Windows NT內核研究者所熟知的,許多內部函數的原型也就可以不用通過逆向工程而獲得。有了C語言函數的原型再學習其結構和思想就輕松多了。
理論上講,取得關于內核的信息的更簡單的方法不是反匯編內核的映象,而是其它的代碼。例如,使用WinDbg的kernel-mode extensions的代碼。WinDbg的擴展中有額外的命令,擴展的調試命令集。其中有明顯使用內核內部結構的命令,或是能減輕分析內核內部結構工作的命令。例如命令!ca、!tokenfields、!processfields等等。反匯編kdextx86.dll和kdex2x86.dll的代碼可以得到某些結構的信息。
內核調試擴展是個.DLL。導出擴展的命令所用的名字與在WinDbg調試器中遇到的名字是相同的。例如,processfields。擴展的DLL導出了函數WinDbgExtensionDllInit,這個函數是在加載擴展后從WinDbg調試器中調用的。函數的原型如下:
VOID WinDbgExtensionDllInit(PWINDBG_EXTENSION_APIS lpExtensionApis,
USHORT MajorVersion,
USHORT MinorVersion)
第一個參數是指向在.DLL中使用的API的指針。WINDBG_EXTENSION_APIS結構體包含以下成員,這些成員定義了訪問擴展函數集:
lpOutputRoutine - 在控制臺輸出字符串
lpGetExpressionRoutine - 計算表達式的值
lpGetSymbolRoutine - 取得符號在內存中的地址
lpDisasmRoutine - 反匯編內存
lpCheckControlCRoutine - 檢查是否按下CTRL-C (未實現)
lpReadProcessMemoryRoutine - 讀進程內存,帶有對GPF的保護。
lpWriteProcessMemoryRoutine - 寫內存
lpGetThreadContextRoutine - 取得進程寄存器的值
lpSetThreadContextRoutine - 設置寄存器
lpIoctlRoutine - 未實現
lpStackTraceRoutine - 跟蹤堆棧
這樣.DLL導出了對應于擴展命令的函數,并可以用于與調試器的有限而熟悉的函數集的協同工作。進一步給出實現擴展命令的函數的原型。
#define DECLARE_API32(s) \
CPPMOD VOID \
s( \
HANDLE hCurrentProcess, \
HANDLE hCurrentThread, \
ULONG dwCurrentPc, \
ULONG dwProcessor, \
PCSTR args \
)
有趣的是參數args,它指向WinDbg中命令的字符串。借助反匯編可以取得足夠的信息以研究擴展命令的工作邏輯。在首要的研究中可以選出直接操縱內核結構體和能辨別結構體成員的命令。例如,命令!ca的代碼說明了內核結構體control area和segment。這個命令的邏輯并不復雜:辨別命令行,從內核內存中讀取所要的結構體,輸出域中的內容。
但是,擴展命令經常并不會列出內核結構體的所有內容。并且,從名字中并不總是能明確的推斷出域的含義,但是反匯編這條命令可以簡化對內核內部函數的分析工作。在任何情況下都會有機會對信息做對比,從各種各樣的線索中取得信息。
---------------------------------------------------------------------------
(c)Gloomy aka Peter Kosyh, Melancholy Coding'2001
http://gloomy.cjb.net
mailto:gl00my@mail.ru
董巖 譯
http://greatdong.blog.edu.cn
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -