|
作者:Hacker_Mr.P,发布于52破解*本文较为基础而且简单,可看作PEB的简介,本文数要是俺这两天的学习成果,当然有需要的同学也可以来看看~,如需详情,请咨询:谷歌、百度* 前言:通过本文所述原理和方法可以实现一个完全没有导入表的EXE可执行程序(主要是C语言和少量汇编写成,然而最后的效果只是简单地谈一个信息框….),实现的主要原理是通过FS:[0x30]所指向的PEB结构体入手,得到NTDLL.DLL或KERNEL32.DLL的基址,然后暴力搜索其导出的动态库加载和卸载、函数地址查找函数,最后通过搜索到的函数加载、卸载自己指定的动态库,并执行相关代码,虽然相关的文档已经有很多了,但是俺就是要写一篇自己的文章。*本文所用到的工具有:Detect It Easy,Lord PE,OllyDbg,Visual Studio 2013,PC Hunter,俺在64位的WIN7系统上写的代码,由于同时在32位的XP和64位的WIN7上做测试(建议只在32位XP SP3上进行试验),所以某些细节会有点出入,但是不影响程序正常运行。* 正文:OD之所以强大是有很多原因的,其中之一就是它的代码解析能力,通过它我们能方便地下API断点。OD其中一项代码解析就是API调用解析,OD通过解析程序所加载的可执行模块来遍历API或者用户函数,所以要是我们能和谐掉这个导入表,那么要想通过OD下API断点就要绕一个弯子了,最主要的是如果我们把导入表和谐掉的话,别人第一眼是看不出我们具体导入了哪些DLL及其函数的。那好,我们就来试试把导入表完全和谐掉吧,先来看看带有导入表的程序。 VS2013默认工程所生成的控制台程序代码:
Release编译后查看其导入表(图片所示软件为DetectIt Easy):
导入了两个DLL,第一个是VC运行库,第二个是KERNEL32.DLL,运行库的DLL是可以去掉的,通过VS的工程设置面板设置:运行库设置为:多线程(/MT),意思是将运行库代码静态编译进程序中,从而摆脱DLL运行库。
再次编译后查其看导入表:嘿嘿,少了一个DLL导入。 然后呢?KERNEL32.DLL怎么和谐掉呢?这个程序是C标准程序,所以要用到C函数库,然而C函数库的底层实现还是绕不开调用系统提供的API,所以他终归是要导入KERNEL32.DLL来调用所需的系统API。所以呢,没办法,只能写非C标准的程序了,也就是不使用C函数库(意思不是说跳出C语言、语法标准的圈子),那要怎么写呢,很简单,只要自定义入口函数并且不再include或者使用任何的C标准库,这样Release编译后的程序就不再导入任何的DLL了。我们重新新建一个空白工程,手动添加源文件,然后进入设置面板设置入口函数。
源代码(是的就这三行,只使用内置的C类型,没有输入参数,也没有返回值):
工程设置(顺带一提的是最好在把系统栏的子系统设置为:窗口(/SUBSYSTEM:WINDOWS),这样就没有那个黑框框了):在这里呢,我把入口点设成Entry函数,设成什么看自己喜好啦。设置完成,Release编译后查看导入表,发现……嘿嘿,导入表空了。
把生成的程序拖进OD(64位WIN7):只生成了一个RETN指令。
再来看看OD识别到的可执行模块(64位WIN7):识别到了三个DLL,第一个是NTDLL.DLL,第二个是KERNEL32.DLL,最后一个是KERNELBASE.DLL。
(对比)开头的默认工程所生成的控制台程序可执行模块(64位WIN7):看来这三个DLL是执行可执行文件所必需的。 好了,导入表是和谐掉了,但是由于没有使用库,于是我们调用API就变得困难,现在我们甚至连一个printf都调用不了。为了解决这个问题,我们可以自己造轮子,自己实现一切所需的功能…这个不大现实,毕竟要想实现这一切,我们必须是(lao)大(si)牛(ji)….,这样的话我们还有另一种办法,首先我们是知道的,要想加载DLL,我们就要用到KERNEL32.DLL导出的LoadLibrary(A/W),我们跟入LoadLibrary会发现它会调用LoadLibraryEx(A/W),然后LoadLibraryEx又会调用一个叫LdrLoadDll的函数,这个函数在NTDLL.DLL中被导出,再继续跟就是具体的实现代码了,当然我们可以通过ReactOS的源码一窥这个函数的大致实现原理,LdrLoadDll在经过一系列的初始化后会调用LdrpLoadDll来加载DLL(详情见参考)。既然我们程序的地址空间内默认有KERNEL32.DLL和NTDLL.DLL那我们就可以选择其中一个来实现我们要加载DLL的目标。在这里呢我选了NTDLL.DLL(总是要玩点不一样嘛),如果选KERNEL32.DLL会更简单些,因为不用考虑UNICODE的问题。那么要得到LdrLoadDll地址的办法是什么呢?我在这里用的办法是先得到NTDLL.DLL的地址,然后通过解析其PE结构中的导出表来获取LdrLoadDll的地址。要怎么得到NTDLL.DLL的地址呢?通过解析PEB结构体就行了(当然还有其他办法,不过那不在本文的讨论范围),这个结构体全称叫做Process Environment Block(进程环境块),还有一个叫TEB(ThreadEnvironment Block【线程环境块】)的,那么PEB在哪呢?PEB就在FS:[0x30]所指处,FS又装了什么呢?
向OD拖入一个EXE,查看其FS段寄存器(32位XP系统):我们可以发现FS的值为0x3B,也就是选择段子为0x3B(有系统差异),换算成二进制就是:0000 0000 0011 1011(16位),我们再来看一张图,这张图显示了段选择子的结构。
段选择子结构图:0x3B的低两位为11十进制就是3,RPL也就是R3,第三位为0,也就是选择了GDT表,把所有高位0省略,就还剩111,也就是十六进制位7,那好我们现在知道要从GDT表中读取第0x7了。打开PC Hunter切换到内核->GDT项。
PC Hunter(32位XP系统):我们发现0x0007项的基址为0x7FFDD000(有系统差异),而我们从OD中看到的是0x7FFDF000,这又是为什么呢?在这里呢俺找到一篇博文或许可以解决这个问题(详见参考- Windows中FS段寄存器V2)。通过这篇文章我们可以知道FS在内核层和用户层分别指向不同的内存空间,还有随机映射的问题,所以在这里我们只用关心用户层也就是OD所显示的0x7FFDF000,至于随机映射之类的我们不用关心(内核其实是俺的短板…..)。回到OD,如果我们在数据窗口跟随0x7FFDF000这个地址我们会发现这样一个东西
OD跟随(32位XP系统):这其实是个结构体,名字就是前面说的TEB(此图没有截全,因为这个结构体较大)。 TEB的C定义形式:typedef struct _TEB { NT_TIB Tib; PVOID EnvironmentPointer; CLIENT_ID Cid; PVOID ActiveRpcInfo; PVOID ThreadLocalStoragePointer; PPEB Peb; ULONG LastErrorValue; ULONG CountOfOwnedCriticalSections; PVOID CsrClientThread; PVOID Win32ThreadInfo; ULONG Win32ClientInfo[0x1F]; ……………….(此处省略N项) PVOID ReservedForOle; ULONG WaitingOnLoaderLock; PVOID StackCommit; PVOID StackCommitMax; PVOID StackReserved;} TEB, *PTEB;TEB的第一项又是一个叫NT_TIB的结构体,我们在来看看NT_TIB的定义。 TIB的C定义形式:typedef struct _NT_TIB{ struct_EXCEPTION_REGISTRATION_RECORD * ExceptionList; PVOIDStackBase; PVOIDStackLimit; PVOIDSubSystemTib; union { PVOIDFiberData; ULONGVersion; }; PVOIDArbitraryUserPointer; struct_NT_TIB * Self;} NT_TIB, *PNT_TIB;现在跟OD数据窗口内的内容对上了,NT_TIB第一项是指向了SHE链表,最后一项指向了自身。
NT_TIB其实也叫TIB,全称ThreadInformation Block(线程信息块),我们回到TEB,继续往下看会发现PEB的声明出现了(32位XP系统)。PEB的C定义形式:typedef struct _PEB { BOOLEAN InheritedAddressSpace; BOOLEAN ReadImageFileExecOptions; BOOLEAN BeingDebugged; BOOLEAN Spare; HANDLE Mutant; PVOID ImageBaseAddress; PPEB_LDR_DATA LoaderData; PRTL_USER_PROCESS_PARAMETERSProcessParameters; PVOID SubSystemData; PVOID ProcessHeap; PVOID FastPebLock; …………….(省略N项) ULONG OSPlatformId; ULONG ImageSubSystem; ULONG ImageSubSystemMajorVersion; ULONG ImageSubSystemMinorVersion; ULONG GdiHandleBuffer[0x22]; ULONG PostProcessInitRoutine; ULONG TlsExpansionBitmap; BYTE TlsExpansionBitmapBits[0x80]; ULONG SessionId;} PEB, *PPEB; TEB,TIB,PEB他们之间的关系又是什么呢?,他们分别做什么用的呢?TEB,就是程序线程的描述结构体,每一个线程对应一个TEB,它存储了当前线程的各种信息,TIB则包含线程的SHE链表指针和线程本地存储(Thread Local Storage, TLS)的指针,PEB包含进程相关的信息,每一个进程对应一个PEB结构体。TEB及其相关结构体在程序被加载时会被PE加载器填充。
三者简要关系图:关于这三个结构体的成员详细用途请自行查找~,我们现在再回到PEB的问题,仔细看看一看PEB的结构,会发现没有直接跟NTDLL.DLL基址相关的成员,不要急,我们慢慢来说。在PEB中有一个PPEB_LDR_DATA类型的成员叫做LoaderData(指向一个PEB_LDR_DATA结构体)。 PEB_LDR_DATA结构体的C定义形式:typedef struct _PEB_LDR_DATA { ULONG Length; BOOL Initialized; PVOID SsHandle; LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList;} PEB_LDR_DATA, *PPEB_LDR_DATA; 前三个成员不用管,最重要的是后三个成员,后三个成员本质上是一样的都是LIST_ENTRY类型。 LIST_ENTRY结构体的C定义形式:typedef struct _LIST_ENTRY { struct_LIST_ENTRY *Flink; struct_LIST_ENTRY *Blink;} LIST_ENTRY, *PLIST_ENTRY,*RESTRICTED_POINTER; 再继续讲下去之前我要先说一下像是NTDLL.DLL这样的可执行模块信息是如何被储存的,每一个模块信息都被存储在一个LDR_DATA_TABLE_ENTRY类型的结构体中。 LDR_DATA_TABLE_ENTRY结构的C定义形式:typedef struct _LDR_DATA_TABLE_ENTRY{ LIST_ENTRYInLoadOrderLinks; LIST_ENTRYInMemoryOrderModuleList; LIST_ENTRYInInitializationOrderModuleList; PVOIDDllBase; PVOIDEntryPoint; ULONGSizeOfImage; UNICODE_STRINGFullDllName; UNICODE_STRINGBaseDllName; ULONGFlags; USHORTLoadCount; USHORTTlsIndex; union { LIST_ENTRYHashLinks; struct { PVOID SectionPointer; ULONGCheckSum; }; }; union { ULONGTimeDateStamp; PVOIDLoadedImports; }; PVOIDEntryPointActivationContext; PVOIDPatchInformation;} LDR_DATA_TABLE_ENTRY,*PLDR_DATA_TABLE_ENTRY; 看其成员,发现头三个又是LIST_ENTRY类型的结构体,接下来就是我们翘首以盼的基址,然后是入口地址,大小,路径,名称…等等。那么这个结构体和PEB_LDR_DATA的LIST_ENTRY有什么关系呢,我们看看微软对于PEB_LDR_DATA中的InLoadOrderModuleList成员是怎么说的,“The head of a doubly-linked list that contains the loaded modulesfor the process. Each item in the list is a pointer to an LDR_DATA_TABLE_ENTRYstructure. For more information, see Remarks.”,简单说就是PEB_LDR_DATA中的InLoadOrderModuleList是一个双向链表的头,InLoadOrderModuleList(LIST_ENTRY)结构体有两个成员,Flink和Blink,Flink指向前一个LDR_DATA_TABLE_ENTRY,Blink指向后一个LDR_DATA_TABLE_ENTRY,然后每个LDR_DATA_TABLE_ENTRY又指向另两个LDR_DATA_TABLE_ENTRY从而形成双向链表,以下是MSDN对LDR_DATA_TABLE_ENTRY的定义: typedef struct _LDR_DATA_TABLE_ENTRY { PVOID Reserved1[2]; LIST_ENTRY InMemoryOrderLinks; PVOID Reserved2[2]; PVOID DllBase; PVOID EntryPoint; PVOID Reserved3; UNICODE_STRING FullDllName; BYTE Reserved4[8]; PVOID Reserved5[3]; union { ULONG CheckSum; PVOID Reserved6; }; ULONG TimeDateStamp;} LDR_DATA_TABLE_ENTRY,*PLDR_DATA_TABLE_ENTRY;我们会发现这不就是LDR_MODULE的简化版嘛,是的没错这就是其简化版,微软公开的结构体隐藏了部分内容,我们的LDR_MODULE是完全版,不单是这一个结构体,包括前面的PEB,TEB等结构体,微软对他们的公开都是半遮半掩,其实微软有很多没有公开或者没有完全公开的函数和结构体,微软不想我们去用他们是因为这些底层的结构体和函数随时可能改变,我们当然也要小心使用这些Undocumented函数/结构体,毕竟程序的稳定性和安全性第一。我们继续结构体的问题。现在我们知道了PEB包含PEB_LDR_DATA,而PEB_LDR_DATA又包含LIST_ENTRY,LIST_ENTRY又指向了双向链表,这个双向链表又是由LDR_DATA_TABLE_ENTRY组成,而我们要的可执行模块的信息就存储在这个链表中,我现在画一张图来理清一下这些结构体的关系。
以上结构体及其中的关系:我们现在回到PEB_LDR_DATA结构中,会发现它包含三个LIST_ENTRY结构体,及InLoadOrderModuleList,InMemoryOrderModuleList,和InInitializationOrderModuleList,这三个结构体的区别就是他们各自指向不同的双向链表,但是这些链表所包含的结构体和内容是完全一样的,唯一的区别就是他们的排序不一样。 FS寄存器,TEB,TIB,PEB结构讲完了,那么现在假设我们有了NTDLL.DLL的基址,我们又如何取得LdrLoadDll的地址呢?暴力搜索现在就派上用场了,通过解析NTDLL.DLL的PE结构,我们可以得到它的导出表,通过导出表我们就可以得到LdrLoadDll的地址了(PE结构我不打算讲了,那玩意儿太长了)。 原理算说完了(′_ゝ`),那我们开始写代码(好吧,然而俺已经写好了,就放到附件里面吧,源码解析啥的就看看注释吧)。这里要注意的两点是,1、由于我们的程序是没有导入库的,所以我们要在编译前去掉优化(在工程设置->C/C++->优化->禁用),否则生成的程序只会有一个RETN指令,2、还有就是头文件的问题,所有定义都需要从Windows SDK扒出来(除了TEB,TIB,PEB等结构体,只有从其他地方获得)。
我们Release(注意优化问题)我们的程序,直接拖入Detect It Easy中查看导入表:发现竟然导入了KERNEL32.DLL,这又是为什么呢?拖入OD分析看看(64位WIN7):竟然链接了一个叫__security_check_cookie的函数,而这个函数在之后的过程中会调用KERNEL32.DLL中的API,经测试发现只要开启优化就不会导入这个函数,那现在没办法啊,开了优化啥都没了。我们走一个偏门,那就是直接硬改掉导入表,怎么改呢,Lord PE或者Detect It Easy 也行。
Lord PE修改导入表:着两个域都改成0就行了,点击保存->确定退出,再次拖入Detect It Easy,导入表也经没有了,改了之后也不会影响我们程序的运行。那么怎么对我们的程序下API断点呢(俺给的源码调用了MessageBoxA)?刚开始我们可能在OD里面看不到USER32.DLL的身影,因为我们还有加载它,但是一旦我们加载USER32.DLL到我们的地址空间内,OD马上就能识别到这时候下断点就可以了。 写在最后:关于TEB,TIB,PEB的应用是远不止这么一点的,更多的应用还要自己发掘,我也通过写这篇文章找到了一些学习PEB时候的错误认知,啊哈,俺又离内核更进一步了…..虽然离着内核还是很远。如果这篇文章由什么错误需要纠正请私信,要是找不到我,那就直接把这篇文章拿去改吧,改了重新发布也好,免得误人子弟。 参考:http://doxygen.reactos.org/d7/d55/ldrapi_8c_a7671bda932dbb5096570f431ff83474c.html#a7671bda932dbb5096570f431ff83474c //ReactOS的LdrLoadDll实现http://doxygen.reactos.org/dd/d83/ntdllp_8h_a297208bd9a920c295937d755afc40387.html#a297208bd9a920c295937d755afc40387 //ReactOS的LdrpLoadDll实现http://blog.csdn.net/waveradio/article/details/2681346 //简单地枚举可执行模块http://bbs.pediy.com/showthread.php?t=175833//用户层的FS简介http://bbs.pediy.com/showthread.php?t=159935//内核层的FS简介http://bbs.nyasama.com/forum.php?mod=viewthread&tid=585//还是用户层的FS简介http://blog.chinaunix.net/uid-24807808-id-3098815.html//还是内核层FS的大致作用http://blog.csdn.net/jiangtongcn/article/details/6713143// FS TIB TEB PEBhttp://blog.csdn.net/misterliwei/article/details/4391580// Windows中FS段寄存器V2https://msdn.microsoft.com/en-us/library/windows/desktop/aa813708(v=vs.85).aspx//MSDN PEB_LDR_DATA structure
附件【本文原文,和源文件】s.rar
来源:http://www.12558.net
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
|