本文共 9587 字,大约阅读时间需要 31 分钟。
导读:
作者:scz 主页:http://www.nsfocus.net日期:2003-09-03这只是我一篇巨长无比的笔记中的一小节,因此只列了本节所引参考资源,可我还是很乐意向大家推荐[8]、[10]。如果对你有用,是否也可以给我订份小礼物,呵,小玩笑啦。☆ 通过TEB/PEB枚举当前进程空间中用户模块列表实在没精力找出最早介绍该项技术的文章,尽管很想知道答案。29A杂志中大量使用该技术([8]),并辅以文字说明,推荐阅读。下面操作是在windbg中进行的,提前在注册表中设置好AeDebug,执行vulnerable_0.exe,进入windbg调试状态。也可通过其它办法进入windbg调试状态。段选择子FS所对应的段即当前线程TEB(Thread Environment Block),即FS:0指向TEB。> dg @fsSelector Base Limit Type DPL Size Gran Pres-------- -------- -------- ---------- --- ------- ---- ---- 0038 7ffde000 00000fff Data RW Ac 3 Big Byte P> r $teb$teb=7ffde000TEB[0x30]是一个指向当前进程PEB(Process Environment Block)的指针。大家都这么说,可他们如何知道这个偏移的,你该上哪去找TEB的数据结构定义,不同系统上该结构定义一致吗。有这么多疑问,可绝大多数文章没有告诉你捞鱼的办法,只给你鱼,你一定有种意尤未尽的郁闷。呵,了解,下面就是捞鱼的办法,windbg的dt命令:> dt ntdll!*teb* (列出匹配通配符的结构名) ntdll!_TEB ... ...> dt -v -r ntdll!_TEBstruct _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 ... ...偏移、名称、类型、大小等等一应俱全地列举在此。万事不求人是不对的,指望别人老来求自己更是不对的,藏着掖着是要一起挨米国鬼子打的。无论你用什么系统,去下载相应版本的windbg,然后用上述办法自己核实偏移。MS一贯擅长保持向后兼容性,这是它巨大成功的基础,类似0x30这样的偏移,被改动的可能性并不大。号称新版Platform SDK的ntpsapi.h文件中直接给出了TEB的数据结构,我的不够新,没找着这个文件。反正dt命令够我用了,懒得深究。顺便提个事。肯定有人见过这样的代码,之后eax将指向PEB: mov eax,fs:[18h] mov eax,[eax+30h]看完本文"新"的介绍,你肯定在疑惑中,这样的代码居然也能成功获取PEB地址,惟一的解释就是fs:[18h]与fs:[0h]指向同一处。TEB结构第一成员是NT_TIB结构,后者的Self成员指向自身这个NT_TIB结构,"碰巧"这个NT_TIB结构是TEB结构第一成员,于是可以认为Self指向TEB。Self的偏移是0x18,就这么简单。> dt -v _NT_TIB $tebstruct _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]这两个不同的逻辑地址对应同一个线性地址。参看<>([12])的3.1小节,下面是Intel的标准定义:逻辑地址 段选择子(16-bits):段内偏移(32-bits) 也叫远指针。程序中寻址时只能使用逻辑地址。没有办法禁用段机制,但有办法 禁用分页机制。线性地址 逻辑地址经GDT、LDT转换后得到线性地址。只需一条代码即可获取PEB地址: mov eax,fs:[30h]> dd @fs:30 L10038: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 ... ...> !tebTEB at 7ffde000 ExceptionList: 0012ffb0 ... ... PEB Address: 7ffdf000 ... ...> r $peb$peb=7ffdf000> dd $teb+30 L1 (当前基是16进制)7ffde030 7ffdf000无数种办法可以得到指向PEB的指针0x7ffdf000。Inside 2K([9])<>中直接将TEB、PEB定位在0x7ffde000、0x7ffdf000。一般来说是这样的,可是不建议依赖硬编码地址。这就看当前最紧要的是广泛兼容性还是压缩代码空间。注意,讨论前提是编写exploit、shellcode、virus,最终可能要汇编化的,而非常规编程。PEB[0x0c]指向PEB_LDR_DATA结构,该结构有三个成员均可用于枚举当前进程空间中的模块列表,区别在于加载顺序、内存顺序、初始化顺序。这是三个双向循环链表,遍历时留神。> dt -v -r ntdll!_PEBstruct _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_DATAstruct _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_ENTRYstruct _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 L17ffdf00c 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形成循环链表)00241f28 00241fd0 00241eac00241fd0 00242118 00241f2800242118 00242258 00241fd000242258 002423a0 00242118002423a0 00242300 0024225800242300 002424e0 002423a0002424e0 00242440 0024230000242440 00242770 002424e000242770 002428a8 00242440002428a8 00242808 0024277000242808 002429e8 002428a8002429e8 00241eac 0024280800241eac 00241f28 002429e8> dlb 2429e8 ffff 2 (注意Blink形成循环链表)002429e8 00241eac 0024280800242808 002429e8 002428a8002428a8 00242808 0024277000242770 002428a8 0024244000242440 00242770 002424e0002424e0 00242440 0024230000242300 002424e0 002423a0002423a0 00242300 0024225800242258 002423a0 0024211800242118 00242258 00241fd000241fd0 00242118 00241f2800241f28 00241fd0 00241eac00241eac 00241f28 002429e8单从windbg所给出的信息,只能看出三个双向循环链表的存在,每个链表结点上只有前向指针、后向指针,如何获取最终的模块信息呢。其实这是很关键的问题,遗憾的是某些文章不知出于什么动机刻意回避该问题。Reactos与Tomasz Nowak提供了未公开的LDR_MODULE数据结构,我把偏移标出来了,方便计算:typedef struct _LDR_MODULE{ LIST_ENTRY InLoadOrderModuleList; // +0x00 LIST_ENTRY InMemoryOrderModuleList; // +0x08 LIST_ENTRY InInitializationOrderModuleList; // +0x10 PVOID BaseAddress; // +0x18 PVOID EntryPoint; // +0x1c ULONG SizeOfImage; // +0x20 UNICODE_STRING FullDllName; // +0x24 UNICODE_STRING BaseDllName; // +0x2c ULONG Flags; // +0x34 SHORT LoadCount; // +0x38 SHORT TlsIndex; // +0x3a LIST_ENTRY HashTableEntry; // +0x3c ULONG TimeDateStamp; // +0x44 // +0x48} LDR_MODULE, *PLDR_MODULE;郑重推荐<>,逆反心理促使我一定要让大家都知道这个数据结构引自何处([10])。说点题外话,Google是你的朋友。Windows底层编程在中国不知因为什么而显得资料罕见,结果以前给我一个错觉,Windows底层编程举步维艰。后来因工作原因Google时向Windows做了点偏移,发现国外此类编程资料并非如想像的那般罕见,有些人共享出完整源代码后只是说如果感觉有帮助能否在网上订份小礼物给他,我是没境外信用卡,否则一定订份小礼物给他。我还发现一件有意思的事,有人注明"原创"的时候居然没有任何参考资源,不巧的是我看过的英文文章实在有点烂多,当创意、技巧、代码片段重合得太多时,就有种无奈的感概。有鉴于此,己所不欲、勿施与人,写此类型文章时一概不敢宣称是原创、翻译,撑死了是笔记,还生怕漏了引自何处。也可能是我水平低,才如此。水平一高,就不便引他人之文了,谁知道呢。呵,很小的时候听爹说过一句话,吃饭的永远不要责怪做饭的,确实如此,所以就不多评价了。建议大家写文章时认真负责地给参考资源,有URL的都注明URL,不费什么事。扯远了,回到LDR_MODULE结构上来,三个双向循环链表的结点正是该结构。按加载顺序遍历:!list -t _LIST_ENTRY.Flink -x "!ustr" -a "+24" 241ec0!list -t _LIST_ENTRY.Flink -x "!ustr" -a "+2c" 241ec0按内存顺序遍历:!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" 241f28String(58,520) at 00241f3c: D:/WINDOWS/System32/ntdll.dllString(64,66) at 00241fe4: D:/WINDOWS/system32/kernel32.dll... ...> !list -t _LIST_ENTRY.Flink -x "!ustr" -a "+1c" 241f28String(18,20) at 00241f44: ntdll.dllString(24,26) at 00241fec: kernel32.dll... ...一般前向遍历链表时,第一个节点对应ntdll.dll,第二个结点对应kernel32.dll,我们不太关心其它模块。编程枚举用户模块列表时,重点显示BaseAddress、FullDllName成员。总结一下全过程:a. 从fs:[30h]获取PEB地址b. 从PEB[0x0c]获取Ldr地址c. 从Ldr[0x0c]获取InLoadOrderModuleList.Flinkd. 从InLoadOrderModuleList.Flink开始前向遍历循环链表e. 显示LDR_MODULE结构的BaseAddress、FullDllName成员。下面是完整的C语言演示程序,汇编化留到编写完整shellcode时进行。--------------------------------------------------------------------------/** -----------------------------------------------------------------------* 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 : * -----------------------------------------------------------------------*//** 按加载顺序遍历双向循环链表*/#include #include #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 */--------------------------------------------------------------------------执行效果如下。可以看出,尽管是循环链表,也可通过判断BaseAddress是否为NULL来结束遍历。> EnumModulePEB = 0x7FFDF000Ldr = 0x00241E90Flink = 0x00241EC0p = 0x00241EC0 0x00400000 X:/EnumModule.exep = 0x00241F18 0x77F50000 X:/XP/System32/ntdll.dllp = 0x00241FC0 0x77E60000 X:/XP/system32/kernel32.dllp = 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] <> - 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本文转自转载地址:http://jgnob.baihongyu.com/