☆ 通過TEB/PEB枚舉當(dāng)前進(jìn)程空間中用戶模塊列表
http://www.sczgroup.org/windows/200311231555.txt
實(shí)在沒精力找出最早介紹該項(xiàng)技術(shù)的文章,盡管很想知道答案。29A雜志中大量使用
該技術(shù)([8]),并輔以文字說明,推薦閱讀。下面操作是在windbg中進(jìn)行的,提前在
注冊(cè)表中設(shè)置好AeDebug,執(zhí)行vulnerable_0.exe,進(jìn)入windbg調(diào)試狀態(tài)。也可通過
其它辦法進(jìn)入windbg調(diào)試狀態(tài)。
段選擇子FS所對(duì)應(yīng)的段即當(dāng)前線程TEB(Thread Environment Block),即FS:0指向TEB。
> dg @fs
Selector Base Limit Type DPL Size Gran Pres
-------- -------- -------- ---------- --- ------- ---- ----
0038 7ffde000 00000fff Data RW Ac 3 Big Byte P
> r $teb
$teb=7ffde000
TEB[0x30]是一個(gè)指向當(dāng)前進(jìn)程PEB(Process Environment Block)的指針。大家都這
么說,可他們?nèi)绾沃肋@個(gè)偏移的,你該上哪去找TEB的數(shù)據(jù)結(jié)構(gòu)定義,不同系統(tǒng)上
該結(jié)構(gòu)定義一致嗎。有這么多疑問,可絕大多數(shù)文章沒有告訴你撈魚的辦法,只給你
魚,你一定有種意尤未盡的郁悶。呵,了解,下面就是撈魚的辦法,windbg的dt命令:
> dt ntdll!*teb* (列出匹配通配符的結(jié)構(gòu)名)
ntdll!_TEB
... ...
> dt -v -r ntdll!_TEB
struct _TEB, 64 elements, 0xfb4 bytes
+0x000 NtTib : struct _NT_TIB, 8 elements, 0x1c bytes
+0x01c EnvironmentPointer : Ptr32 to Void
+0x020 ClientId : struct _CLIENT_ID, 2 elements, 0x8 bytes
+0x028 ActiveRpcHandle : Ptr32 to Void
+0x02c ThreadLocalStoragePointer : Ptr32 to Void
+0x030 ProcessEnvironmentBlock : Ptr32 to struct _PEB, 66 elements, 0x210 bytes
... ...
偏移、名稱、類型、大小等等一應(yīng)俱全地列舉在此。萬事不求人是不對(duì)的,指望別人
老來求自己更是不對(duì)的,藏著掖著是要一起挨米國(guó)鬼子打的。無論你用什么系統(tǒng),去
下載相應(yīng)版本的windbg,然后用上述辦法自己核實(shí)偏移。MS一貫擅長(zhǎng)保持向后兼容性,
這是它巨大成功的基礎(chǔ),類似0x30這樣的偏移,被改動(dòng)的可能性并不大。
號(hào)稱新版Platform SDK的ntpsapi.h文件中直接給出了TEB的數(shù)據(jù)結(jié)構(gòu),我的不夠新,
沒找著這個(gè)文件。反正dt命令夠我用了,懶得深究。
順便提個(gè)事??隙ㄓ腥艘娺^這樣的代碼,之后eax將指向PEB:
mov eax,fs:[18h]
mov eax,[eax+30h]
看完本文"新"的介紹,你肯定在疑惑中,這樣的代碼居然也能成功獲取PEB地址,惟
一的解釋就是fs:[18h]與fs:0h指向同一處(注意方括號(hào)的使用)。TEB結(jié)構(gòu)第一成員是
NT_TIB結(jié)構(gòu),后者的Self成員指向自身這個(gè)NT_TIB結(jié)構(gòu),"碰巧"這個(gè)NT_TIB結(jié)構(gòu)是
TEB結(jié)構(gòu)第一成員,于是可以認(rèn)為Self指向TEB。Self的偏移是0x18,就這么簡(jiǎn)單。
> dt -v _NT_TIB $teb
struct _NT_TIB, 8 elements, 0x1c bytes
+0x000 ExceptionList : 0x0012ffb0 struct _EXCEPTION_REGISTRATION_RECORD, 2 elements, 0x8 bytes
+0x004 StackBase : 0x00130000
+0x008 StackLimit : 0x0012b000
+0x00c SubSystemTib : (null)
+0x010 FiberData : 0x00001e00
+0x010 Version : 0x1e00
+0x014 ArbitraryUserPointer : (null)
+0x018 Self : 0x7ffde000 struct _NT_TIB, 8 elements, 0x1c bytes
換句話說,ds:7ffde000h與fs:0h這兩個(gè)不同的邏輯地址對(duì)應(yīng)同一個(gè)線性地址。參看
<<IA-32 Software Developer's Manual. Volume 3>>([12])的3.1小節(jié),下面是
Intel的標(biāo)準(zhǔn)定義:
邏輯地址
段選擇子(16-bits):段內(nèi)偏移(32-bits)
也叫遠(yuǎn)指針。程序中尋址時(shí)只能使用邏輯地址。沒有辦法禁用段機(jī)制,但有辦法
禁用分頁機(jī)制。
線性地址
邏輯地址經(jīng)GDT、LDT轉(zhuǎn)換后得到線性地址。
只需一條代碼即可獲取PEB地址:
mov eax,fs:[30h]
> dd @fs:30 L1
0038:00000030 7ffdf000
> dt ntdll!_TEB 7ffde000
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : (null)
+0x020 ClientId : _CLIENT_ID
+0x028 ActiveRpcHandle : (null)
+0x02c ThreadLocalStoragePointer : (null)
+0x030 ProcessEnvironmentBlock : 0x7ffdf000
... ...
> !teb
TEB at 7ffde000
ExceptionList: 0012ffb0
... ...
PEB Address: 7ffdf000
... ...
> r $peb
$peb=7ffdf000
> dd $teb+30 L1 (當(dāng)前基是16進(jìn)制)
7ffde030 7ffdf000
無數(shù)種辦法可以得到指向PEB的指針0x7ffdf000。
Inside 2K([9])<<Table 7-6 Windows 2000 User Process Address Space Layout>>
中直接將TEB、PEB定位在0x7FFDE000、0x7FFDF000。一般來說是這樣的,可是不建議
依賴硬編碼地址。這就看當(dāng)前最緊要的是廣泛兼容性還是壓縮代碼空間。注意,討論
前提是編寫exploit、shellcode、virus,最終可能要匯編化的,而非常規(guī)編程。
PEB[0x0c]指向PEB_LDR_DATA結(jié)構(gòu),該結(jié)構(gòu)有三個(gè)成員均可用于枚舉當(dāng)前進(jìn)程空間中
的模塊列表,區(qū)別在于加載順序、內(nèi)存順序、初始化順序。這是三個(gè)雙向循環(huán)鏈表,
遍歷時(shí)留神。
> dt -v -r ntdll!_PEB
struct _PEB, 66 elements, 0x210 bytes
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar
+0x003 SpareBool : UChar
+0x004 Mutant : Ptr32 to Void
+0x008 ImageBaseAddress : Ptr32 to Void
+0x00c Ldr : Ptr32 to struct _PEB_LDR_DATA, 7 elements, 0x28 bytes
... ...
> dt -v -r ntdll!_PEB_LDR_DATA
struct _PEB_LDR_DATA, 7 elements, 0x28 bytes
+0x000 Length : Uint4B
+0x004 Initialized : UChar
+0x008 SsHandle : Ptr32 to Void
+0x00c InLoadOrderModuleList : struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x014 InMemoryOrderModuleList : struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x01c InInitializationOrderModuleList : struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x024 EntryInProgress : Ptr32 to Void
> dt -v -r ntdll!_LIST_ENTRY
struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x000 Flink : Ptr32 to struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x004 Blink : Ptr32 to struct _LIST_ENTRY, 2 elements, 0x8 bytes
> dd $peb+c L1
7ffdf00c 00241e90
> dt ntdll!_PEB_LDR_DATA 00241e90
+0x000 Length : 0x28
+0x004 Initialized : 0x1 ''
+0x008 SsHandle : (null)
+0x00c InLoadOrderModuleList : _LIST_ENTRY [ 0x241ec0 - 0x2429d8 ]
+0x014 InMemoryOrderModuleList : _LIST_ENTRY [ 0x241ec8 - 0x2429e0 ]
+0x01c InInitializationOrderModuleList : _LIST_ENTRY [ 0x241f28 - 0x2429e8 ]
+0x024 EntryInProgress : (null)
> dl 241f28 ffff 2 (注意Flink形成循環(huán)鏈表)
00241f28 00241fd0 00241eac
00241fd0 00242118 00241f28
00242118 00242258 00241fd0
00242258 002423a0 00242118
002423a0 00242300 00242258
00242300 002424e0 002423a0
002424e0 00242440 00242300
00242440 00242770 002424e0
00242770 002428a8 00242440
002428a8 00242808 00242770
00242808 002429e8 002428a8
002429e8 00241eac 00242808
00241eac 00241f28 002429e8
> dlb 2429e8 ffff 2 (注意Blink形成循環(huán)鏈表)
002429e8 00241eac 00242808
00242808 002429e8 002428a8
002428a8 00242808 00242770
00242770 002428a8 00242440
00242440 00242770 002424e0
002424e0 00242440 00242300
00242300 002424e0 002423a0
002423a0 00242300 00242258
00242258 002423a0 00242118
00242118 00242258 00241fd0
00241fd0 00242118 00241f28
00241f28 00241fd0 00241eac
00241eac 00241f28 002429e8
單從windbg所給出的信息,只能看出三個(gè)雙向循環(huán)鏈表的存在,每個(gè)鏈表結(jié)點(diǎn)上只有
前向指針、后向指針,如何獲取最終的模塊信息呢。其實(shí)這是很關(guān)鍵的問題,遺憾的
是某些文章不知出于什么動(dòng)機(jī)刻意回避該問題。
Reactos與Tomasz Nowak提供了未公開的LDR_MODULE數(shù)據(jù)結(jié)構(gòu),我把偏移標(biāo)出來了,
方便計(jì)算:
typedef struct _LDR_MODULE
{
LIST_ENTRY InLoadOrderModuleList; // +0x000
LIST_ENTRY InMemoryOrderModuleList; // +0x008
LIST_ENTRY InInitializationOrderModuleList; // +0x010
PVOID BaseAddress; // +0x018
PVOID EntryPoint; // +0x01C
ULONG SizeOfImage; // +0x020
UNICODE_STRING FullDllName; // +0x024
UNICODE_STRING BaseDllName; // +0x02C
ULONG Flags; // +0x034
SHORT LoadCount; // +0x038
SHORT TlsIndex; // +0x03A
LIST_ENTRY HashTableEntry; // +0x03C
ULONG TimeDateStamp; // +0x044
// +0x048
} LDR_MODULE, *PLDR_MODULE;
鄭重推薦<<The Undocumented Functions For Microsoft Windows NT/2000>>,逆反
心理促使我一定要讓大家都知道這個(gè)數(shù)據(jù)結(jié)構(gòu)引自何處([10])。
說點(diǎn)題外話,Google是你的朋友。Windows底層編程在中國(guó)不知因?yàn)槭裁炊@得資料
罕見,結(jié)果以前給我一個(gè)錯(cuò)覺,Windows底層編程舉步維艱。后來因工作原因Google
時(shí)向Windows做了點(diǎn)偏移,發(fā)現(xiàn)國(guó)外此類編程資料并非如想像的那般罕見,有些人共
享出完整源代碼后只是說如果感覺有幫助能否在網(wǎng)上訂份小禮物給他,我是沒境外信
用卡,否則一定訂份小禮物給他。我還發(fā)現(xiàn)一件有意思的事,有人注明"原創(chuàng)"的時(shí)候
居然沒有任何參考資源,不巧的是我看過的英文文章實(shí)在有點(diǎn)爛多,當(dāng)創(chuàng)意、技巧、
代碼片段重合得太多時(shí),就有種無奈的感概。有鑒于此,己所不欲、勿施與人,寫此
類型文章時(shí)一概不敢宣稱是原創(chuàng)、翻譯,撐死了是筆記,還生怕漏了引自何處。也可
能是我水平低,才如此。水平一高,就不便引他人之文了,誰知道呢。呵,很小的時(shí)
候聽爹說過一句話,吃飯的永遠(yuǎn)不要責(zé)怪做飯的,確實(shí)如此,所以就不多評(píng)價(jià)了。
建議大家寫文章時(shí)認(rèn)真負(fù)責(zé)地給參考資源,有URL的都注明URL,不費(fèi)什么事。
扯遠(yuǎn)了,回到LDR_MODULE結(jié)構(gòu)上來,三個(gè)雙向循環(huán)鏈表的結(jié)點(diǎn)正是該結(jié)構(gòu)。
按加載順序遍歷:
!list -t _LIST_ENTRY.Flink -x "!ustr" -a "+24" 241ec0
!list -t _LIST_ENTRY.Flink -x "!ustr" -a "+2c" 241ec0
按內(nèi)存順序遍歷:
!list -t _LIST_ENTRY.Flink -x "!ustr" -a "+1c" 241ec8
!list -t _LIST_ENTRY.Flink -x "!ustr" -a "+24" 241ec8
按初始化順序遍歷:
> !list -t _LIST_ENTRY.Flink -x "!ustr" -a "+14" 241f28
String(58,520) at 00241f3c: D:\WINDOWS\System32\ntdll.dll
String(64,66) at 00241fe4: D:\WINDOWS\system32\kernel32.dll
... ...
> !list -t _LIST_ENTRY.Flink -x "!ustr" -a "+1c" 241f28
String(18,20) at 00241f44: ntdll.dll
String(24,26) at 00241fec: kernel32.dll
... ...
一般"按初始化順序"前向遍歷鏈表時(shí),第一個(gè)節(jié)點(diǎn)對(duì)應(yīng)ntdll.dll,第二個(gè)結(jié)點(diǎn)對(duì)應(yīng)
kernel32.dll,我們不太關(guān)心其它模塊。如果按加載順序前向遍歷,第一個(gè)節(jié)點(diǎn)對(duì)應(yīng)
EXE文件本身,第二個(gè)節(jié)點(diǎn)才對(duì)應(yīng)ntdll.dll。
編程枚舉用戶模塊列表時(shí),重點(diǎn)顯示BaseAddress、FullDllName成員??偨Y(jié)一下全過
程:
a. 從fs:[30h]獲取PEB地址
b. 從PEB[0x0c]獲取Ldr地址
c. 從Ldr[0x0c]獲取InLoadOrderModuleList.Flink
d. 從InLoadOrderModuleList.Flink開始前向遍歷循環(huán)鏈表
e. 顯示LDR_MODULE結(jié)構(gòu)的BaseAddress、FullDllName成員。
下面是完整的C語言演示程序,匯編化留到編寫完整shellcode時(shí)進(jìn)行。
--------------------------------------------------------------------------
/*
* -----------------------------------------------------------------------
* Compile : For x86/EWindows XP SP1 & VC 7
* : cl EnumModule.c /nologo /Os /G6 /W3 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /link /RELEASE
* :
* Create : 2003-08-12 11:36
* Modify :
* -----------------------------------------------------------------------
*/
/*
* 按加載順序遍歷雙向循環(huán)鏈表
*/
#include <stdio.h>
#include <stdlib.h>
#pragma comment( linker, "/INCREMENTAL:NO" )
#pragma comment( linker, "/subsystem:console" )
int __cdecl main ( int argc, char * argv[] )
{
void *PEB = NULL,
*Ldr = NULL,
*Flink = NULL,
*p = NULL,
*BaseAddress = NULL,
*FullDllName = NULL;
__asm
{
mov eax,fs:[0x30]
mov PEB,eax
}
printf( "PEB = 0x%08X\n", PEB );
Ldr = *( ( void ** )( ( unsigned char * )PEB + 0x0c ) );
printf( "Ldr = 0x%08X\n", Ldr );
Flink = *( ( void ** )( ( unsigned char * )Ldr + 0x0c ) );
printf( "Flink = 0x%08X\n", Flink );
p = Flink;
do
{
BaseAddress = *( ( void ** )( ( unsigned char * )p + 0x18 ) );
FullDllName = *( ( void ** )( ( unsigned char * )p + 0x28 ) );
printf( "p = 0x%08X 0x%08X ", p, BaseAddress );
wprintf( L"%s\n", FullDllName );
p = *( ( void ** )p );
}
while ( Flink != p );
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------
執(zhí)行效果如下??梢钥闯觯M管是循環(huán)鏈表,也可通過判斷BaseAddress是否為NULL
來結(jié)束遍歷。
> EnumModule
PEB = 0x7FFDF000
Ldr = 0x00241E90
Flink = 0x00241EC0
p = 0x00241EC0 0x00400000 X:\EnumModule.exe
p = 0x00241F18 0x77F50000 X:\XP\System32\ntdll.dll
p = 0x00241FC0 0x77E60000 X:\XP\system32\kernel32.dll
p = 0x00241E9C 0x00000000
>
☆ 參考資源
[ 8] http://vx.netlux.org/vx.php?id=z001 (29A雜志)
Virus Writing Guide 1.00 for Win32 - Billy Belceb
29A-4.202
A guide to the latest methods to retrieve API's in a Win32 environment - LethalMind
29A-4.227
Gaining important datas from PEB under NT boxes - Ratter
29A-6.024
[ 9] <<Inside Microsoft Windows 2000 Third Edition>> - David A. Solomon, Mark E. Russinovich
[10] The Undocumented Functions For Microsoft Windows NT/2000
http://undocumented.ntinternals.net/ntundoc.chm
[12] Intel Architecture Software Developer's Manual. Volume 1
ftp://download.intel.com/design/PentiumII/manuals/24319002.pdf
Intel Architecture Software Developer's Manual. Volume 2
ftp://download.intel.com/design/PentiumII/manuals/24319102.pdf
Intel Architecture Software Developer's Manual. Volume 3
ftp://download.intel.com/design/PentiumII/manuals/24319202.pdf