天天看点

利用Ldr获得加载的DLL的基址

利用链如下:

fs寄存器 -> TEB -> PEB -> PEB_LDR_DATA -> LIST_ENTRY -> LDR_DATA_TABLE_ENTRY -> BaseOfDll

利用Ldr获得加载的DLL的基址

windbg 用r命令查看fs寄存器的值

根据fs段寄存器,我们找到它的GDTR,如下

利用Ldr获得加载的DLL的基址

找到了GDT的索引为6

利用Ldr获得加载的DLL的基址

对应的值gdt[6] = ffc093df`f0000001,索引到的基址为0xffdff000

也可以用dg命令

利用Ldr获得加载的DLL的基址

看看TEB结构

kd> dt _teb
ntdll!_TEB
   +0x000 NtTib            : _NT_TIB
   +0x01c EnvironmentPointer : Ptr32 Void
   +0x020 ClientId         : _CLIENT_ID
   +0x028 ActiveRpcHandle  : Ptr32 Void
   +0x02c ThreadLocalStoragePointer : Ptr32 Void
   +0x030 ProcessEnvironmentBlock : Ptr32 _PEB
   +0x034 LastErrorValue   : Uint4B
   +0x038 CountOfOwnedCriticalSections : Uint4B
   +0x03c CsrClientThread  : Ptr32 Void
   +0x040 Win32ThreadInfo  : Ptr32 Void
   +0x044 User32Reserved   : [26] Uint4B
   +0x0ac UserReserved     : [5] Uint4B
   +0x0c0 WOW32Reserved    : Ptr32 Void
   +0x0c4 CurrentLocale    : Uint4B
   +0x0c8 FpSoftwareStatusRegister : Uint4B
   +0x0cc SystemReserved1  : [54] Ptr32 Void
   +0x1a4 ExceptionCode    : Int4B
   +0x1a8 ActivationContextStack : _ACTIVATION_CONTEXT_STACK
   +0x1bc SpareBytes1      : [24] UChar
   +0x1d4 GdiTebBatch      : _GDI_TEB_BATCH
   +0x6b4 RealClientId     : _CLIENT_ID
   +0x6bc GdiCachedProcessHandle : Ptr32 Void
   +0x6c0 GdiClientPID     : Uint4B
   +0x6c4 GdiClientTID     : Uint4B
   +0x6c8 GdiThreadLocalInfo : Ptr32 Void
   +0x6cc Win32ClientInfo  : [62] Uint4B
   +0x7c4 glDispatchTable  : [233] Ptr32 Void
   +0xb68 glReserved1      : [29] Uint4B
   +0xbdc glReserved2      : Ptr32 Void
   +0xbe0 glSectionInfo    : Ptr32 Void
   +0xbe4 glSection        : Ptr32 Void
   +0xbe8 glTable          : Ptr32 Void
   +0xbec glCurrentRC      : Ptr32 Void
   +0xbf0 glContext        : Ptr32 Void
   +0xbf4 LastStatusValue  : Uint4B
   +0xbf8 StaticUnicodeString : _UNICODE_STRING
   +0xc00 StaticUnicodeBuffer : [261] Uint2B
   +0xe0c DeallocationStack : Ptr32 Void
   +0xe10 TlsSlots         : [64] Ptr32 Void
   +0xf10 TlsLinks         : _LIST_ENTRY
   +0xf18 Vdm              : Ptr32 Void
   +0xf1c ReservedForNtRpc : Ptr32 Void
   +0xf20 DbgSsReserved    : [2] Ptr32 Void
   +0xf28 HardErrorsAreDisabled : Uint4B
   +0xf2c Instrumentation  : [16] Ptr32 Void
   +0xf6c WinSockData      : Ptr32 Void
   +0xf70 GdiBatchCount    : Uint4B
   +0xf74 InDbgPrint       : UChar
   +0xf75 FreeStackOnTermination : UChar
   +0xf76 HasFiberData     : UChar
   +0xf77 IdealProcessor   : UChar
   +0xf78 Spare3           : Uint4B
   +0xf7c ReservedForPerf  : Ptr32 Void
   +0xf80 ReservedForOle   : Ptr32 Void
   +0xf84 WaitingOnLoaderLock : Uint4B
   +0xf88 Wx86Thread       : _Wx86ThreadState
   +0xf94 TlsExpansionSlots : Ptr32 Ptr32 Void
   +0xf98 ImpersonationLocale : Uint4B
   +0xf9c IsImpersonating  : Uint4B
   +0xfa0 NlsCache         : Ptr32 Void
   +0xfa4 pShimData        : Ptr32 Void
   +0xfa8 HeapVirtualAffinity : Uint4B
   +0xfac CurrentTransactionHandle : Ptr32 Void
   +0xfb0 ActiveFrame      : Ptr32 _TEB_ACTIVE_FRAME
   +0xfb4 SafeThunkCall    : UChar
   +0xfb5 BooleanSpare     : [3] UChar

           

TEB offset为0的地方是一个TIB,结构如下

kd> dt _NT_TIB
ntdll!_NT_TIB
   +0x000 ExceptionList    : Ptr32 _EXCEPTION_REGISTRATION_RECORD
   +0x004 StackBase        : Ptr32 Void
   +0x008 StackLimit       : Ptr32 Void
   +0x00c SubSystemTib     : Ptr32 Void
   +0x010 FiberData        : Ptr32 Void
   +0x010 Version          : Uint4B
   +0x014 ArbitraryUserPointer : Ptr32 Void
   +0x018 Self             : Ptr32 _NT_TIB

           

TIB offset为0的地方 ExceptionList 是一个异常注册链的表头

StackBase 是该线程的栈地址

StackLimit 是该线程栈大小

Self是指向TEB的指针所以 fs:[0x18]也就是拿到了TEB的地址

继续来看TEB,

EnvironmentPointer是一个环境表指针,main函数的第三个参数就是环境表的地址,

+0x020 ClientId 这个结构我们也看一下,UniqueProcess 进程的PID,UniqueThread是该线程的TID

kd> dt _CLIENT_ID
ntdll!_CLIENT_ID
   +0x000 UniqueProcess    : Ptr32 Void
   +0x004 UniqueThread     : Ptr32 Void

           

+0x030 ProcessEnvironmentBlock 所说的fs:[0x30]也就是取PEB的地址

+0x034 LastErrorValue 如果出现错误,最后的错误返回值是多少,GetLastError就是从这取得值,不同的错误返回不同的错误码,如果为0,说明程序运行正常

Windbg可以通过 !gle(get last error)显示最后的错误值,跟GetLastError等同的效果!!!

例如:

0:000> !gle
LastErrorValue: (Win32) 0 (0) - The operation completed successfully.
LastStatusValue: (NTSTATUS) 0 - STATUS_WAIT_0
           

接着我们到PEB里继续探索,PEB结构如下:

kd> dt _peb
ntdll!_PEB
   +0x000 InheritedAddressSpace : UChar
   +0x001 ReadImageFileExecOptions : UChar
   +0x002 BeingDebugged    : UChar
   +0x003 SpareBool        : UChar
   +0x004 Mutant           : Ptr32 Void
   +0x008 ImageBaseAddress : Ptr32 Void
   +0x00c Ldr              : Ptr32 _PEB_LDR_DATA
   +0x010 ProcessParameters : Ptr32 _RTL_USER_PROCESS_PARAMETERS
   +0x014 SubSystemData    : Ptr32 Void
   +0x018 ProcessHeap      : Ptr32 Void
   +0x01c FastPebLock      : Ptr32 _RTL_CRITICAL_SECTION
   +0x020 FastPebLockRoutine : Ptr32 Void
   +0x024 FastPebUnlockRoutine : Ptr32 Void
   +0x028 EnvironmentUpdateCount : Uint4B
   +0x02c KernelCallbackTable : Ptr32 Void
   +0x030 SystemReserved   : [1] Uint4B
   +0x034 AtlThunkSListPtr32 : Uint4B
   +0x038 FreeList         : Ptr32 _PEB_FREE_BLOCK
   +0x03c TlsExpansionCounter : Uint4B
   +0x040 TlsBitmap        : Ptr32 Void
   +0x044 TlsBitmapBits    : [2] Uint4B
   +0x04c ReadOnlySharedMemoryBase : Ptr32 Void
   +0x050 ReadOnlySharedMemoryHeap : Ptr32 Void
   +0x054 ReadOnlyStaticServerData : Ptr32 Ptr32 Void
   +0x058 AnsiCodePageData : Ptr32 Void
   +0x05c OemCodePageData  : Ptr32 Void
   +0x060 UnicodeCaseTableData : Ptr32 Void
   +0x064 NumberOfProcessors : Uint4B
   +0x068 NtGlobalFlag     : Uint4B
   +0x070 CriticalSectionTimeout : _LARGE_INTEGER
   +0x078 HeapSegmentReserve : Uint4B
   +0x07c HeapSegmentCommit : Uint4B
   +0x080 HeapDeCommitTotalFreeThreshold : Uint4B
   +0x084 HeapDeCommitFreeBlockThreshold : Uint4B
   +0x088 NumberOfHeaps    : Uint4B
   +0x08c MaximumNumberOfHeaps : Uint4B
   +0x090 ProcessHeaps     : Ptr32 Ptr32 Void
   +0x094 GdiSharedHandleTable : Ptr32 Void
   +0x098 ProcessStarterHelper : Ptr32 Void
   +0x09c GdiDCAttributeList : Uint4B
   +0x0a0 LoaderLock       : Ptr32 Void
   +0x0a4 OSMajorVersion   : Uint4B
   +0x0a8 OSMinorVersion   : Uint4B
   +0x0ac OSBuildNumber    : Uint2B
   +0x0ae OSCSDVersion     : Uint2B
   +0x0b0 OSPlatformId     : Uint4B
   +0x0b4 ImageSubsystem   : Uint4B
   +0x0b8 ImageSubsystemMajorVersion : Uint4B
   +0x0bc ImageSubsystemMinorVersion : Uint4B
   +0x0c0 ImageProcessAffinityMask : Uint4B
   +0x0c4 GdiHandleBuffer  : [34] Uint4B
   +0x14c PostProcessInitRoutine : Ptr32     void 
   +0x150 TlsExpansionBitmap : Ptr32 Void
   +0x154 TlsExpansionBitmapBits : [32] Uint4B
   +0x1d4 SessionId        : Uint4B
   +0x1d8 AppCompatFlags   : _ULARGE_INTEGER
   +0x1e0 AppCompatFlagsUser : _ULARGE_INTEGER
   +0x1e8 pShimData        : Ptr32 Void
   +0x1ec AppCompatInfo    : Ptr32 Void
   +0x1f0 CSDVersion       : _UNICODE_STRING
   +0x1f8 ActivationContextData : Ptr32 Void
   +0x1fc ProcessAssemblyStorageMap : Ptr32 Void
   +0x200 SystemDefaultActivationContextData : Ptr32 Void
   +0x204 SystemAssemblyStorageMap : Ptr32 Void
   +0x208 MinimumStackCommit : Uint4B

           

有个BeingDebugged成员,检测本身是否处于调试状态下

ProcessHeap && HeapSegmentReserve && HeapSegmentCommit && NumberOfHeap && MaximumNumberOfHeaps等等这几个跟堆有很大的关系

了解下Windows下创建堆的过程:

Windows创建堆会调用RtlCreateHeap(),句柄保存在ProcessHeap里,HeapSegmentCommit就是堆提交的大小(PE的可选头里也有相应的答案),HeapSegmentReserve是堆保留的大小。

+0x00c Ldr这是我们讨论的重点。看下它的结构

微软给出的PEB_LDR_DATA含义

Contains information about the loaded modules for the process.
           
kd> dt _PEB_LDR_DATA
ntdll!_PEB_LDR_DATA
   +0x000 Length           : Uint4B
   +0x004 Initialized      : UChar
   +0x008 SsHandle         : Ptr32 Void
   +0x00c InLoadOrderModuleList : _LIST_ENTRY
   +0x014 InMemoryOrderModuleList : _LIST_ENTRY
   +0x01c InInitializationOrderModuleList : _LIST_ENTRY
   +0x024 EntryInProgress  : Ptr32 Void

           

后几个成员是非常重要的,含义如下

InLoadOrderModuleList;                //模块加载顺序
InMemoryOrderModuleList;              //模块在内存中的顺序
InInitializationOrderModuleList;      //模块初始化装载顺序
           

值得注意的是,他们本身是一个LIST_ENTRY的结构,这是一个链表结构

kd> dt _LIST_ENTRY
ntdll!_LIST_ENTRY
   +0x000 Flink            : Ptr32 _LIST_ENTRY
   +0x004 Blink            : Ptr32 _LIST_ENTRY

           

显然是一个双向链表结构,并且这些指针又指向了LDR_DATA_TABLE_ENTRY 这个结构体,注意这个结构体的第四个字段也就是DLL的加载基址

typedef struct _LDR_DATA_TABLE_ENTRY
{
     LIST_ENTRY InLoadOrderLinks;//模块加载顺序
     LIST_ENTRY InMemoryOrderLinks;//模块在内存中的顺序
     LIST_ENTRY InInitializationOrderLinks;//模块初始化装载顺序
     PVOID DllBase;	//DLL在内存的地址
     PVOID EntryPoint;//入口点
     ULONG SizeOfImage;//进程在内存中的大小
     UNICODE_STRING FullDllName;//全路径字符串
     UNICODE_STRING BaseDllName;//模块名字符串
     ULONG Flags;
     WORD LoadCount;
     WORD TlsIndex;
     union
     {
          LIST_ENTRY HashLinks;
          struct
          {
               PVOID SectionPointer;
               ULONG CheckSum;
          };
     };
     union
     {
          ULONG TimeDateStamp;
          PVOID LoadedImports;
     };
     _ACTIVATION_CONTEXT * EntryPointActivationContext;
     PVOID PatchInformation;
     LIST_ENTRY ForwarderLinks;
     LIST_ENTRY ServiceTagLinks;
     LIST_ENTRY StaticLinks;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
           

下面实战分析下:

先得到程序的peb

输入!peb可以显示一个程序的peb结构

PEB at 7ffdf000
    InheritedAddressSpace:    No
    ReadImageFileExecOptions: No
    BeingDebugged:            No
    ImageBaseAddress:         00400000
    NtGlobalFlag:             0
    NtGlobalFlag2:            0
    Ldr                       00251e90
    Ldr.Initialized:          Yes
    Ldr.InInitializationOrderModuleList: 00251f28 . 00252180
    Ldr.InLoadOrderModuleList:           00251ec0 . 00252170
    Ldr.InMemoryOrderModuleList:         00251ec8 . 00252178
            Base TimeStamp                     Module
          400000 5ccbca25 May 03 12:57:09 2019 C:\Documents and Settings\ljq\桌面\Test_PEB.exe
        7c920000 4d00f280 Dec 09 23:15:12 2010 C:\WINDOWS\system32\ntdll.dll
        7c800000 53203b8a Mar 12 18:48:42 2014 C:\WINDOWS\system32\kernel32.dll
        10000000 524f7ce5 Oct 05 10:43:49 2013 C:\Documents and Settings\ljq\桌面\MSVCR120D.dll
        76d70000 4802bd96 Apr 14 10:12:38 2008 C:\WINDOWS\system32\Apphelp.dll

           

我们关注InLoadOrderModuleList这个成员,这个成员指的是模块加载顺序,找到这个值00251ec0

跳到那里,看一下:

利用Ldr获得加载的DLL的基址

此时指向的是一个**_LDR_DATA_TABLE_ENTRY**结构。前三项是连续的三个LIST_ENTRY结构,每个结构是8个字节,我们关注第二项成员和第四项成员

利用Ldr获得加载的DLL的基址
利用Ldr获得加载的DLL的基址

该dll的加载基址是0x400000,即是该程序的模块基址

看下一项,是EntryPoint,用LordPE看一下,果真是0x411113,ImageBase也一样

利用Ldr获得加载的DLL的基址

LDR_DATA_TABLE_ENTRY的下一项是SizeOfImage,值是0x1c000,与在pe分析器的结果一致

利用Ldr获得加载的DLL的基址

下面两个是UNICODE_STRING类型的数据,FullDllName,BaseDllName。

typedef struct _LSA_UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;} LSA_UNICODE_STRING, *PLSA_UNICODE_STRING, UNICODE_STRING, *PUNICODE_STRING;
           

前两个length我们不管,重点关注最后一个,是指向字符串的指针

利用Ldr获得加载的DLL的基址

去看一下,显示出了全路径信息

利用Ldr获得加载的DLL的基址

然后看一下BaseDllName

利用Ldr获得加载的DLL的基址
利用Ldr获得加载的DLL的基址

这次只显示模块名称。不显示全路径的信息了。

其他就没好显示的了,进入第一项成员的Flink里,即是下一个模块,这里就是真正的DLL加载模块了

利用Ldr获得加载的DLL的基址

进入后,第一项的Flink是下一个的LDR_DATA_TABLE_ENTRY,可以看到该模块的加载基址是0x7c920000,DllName的全路径地址是0x7c9a0048,模块名是地址是0x7c94040c

利用Ldr获得加载的DLL的基址
利用Ldr获得加载的DLL的基址

这样一直找就可以遍历所有模块了,这里就不演示了,直接上代码:

#include <stdio.h>
#include <stdlib.h>
#include "infor.h"


void InLoadOrderModuleTraverse(LDR_MODULE *pLdrModule, PPEB_LDR_DATA pPebLdrData)//按模块加载顺序遍历 这样传入的就是第一个模块
{
	LDR_MODULE *pFirstModule = (LDR_MODULE *) ((char *)pPebLdrData + 0xc);//记录第一个模块地址,用于遍历链表 这里的offset为0xc是InLoadOrderModuleList的地址
	printf("--------------------\n");
	printf("InLoadOrderModuleTraverse:\n");

	do {//在这里输出一些信息
		printf("NextLdrDataTable : %8X\n", pLdrModule->InLoadOrderModuleList.Flink);
		printf("DllBase : %8X\n", pLdrModule->BaseAddress);
		wprintf(L"FullDllName : %ls\n", pLdrModule->FullDllName.Buffer);
		wprintf(L"BaseDllName : %ls\n\n", pLdrModule->BaseDllName.Buffer);
		pLdrModule = (LDR_MODULE *)pLdrModule->InLoadOrderModuleList.Flink;
	} while ((LDR_MODULE *)pLdrModule != pFirstModule);

}

int main()
{
	PEB *pPeb = NULL;
	TEB *pTeb = NULL;
	PEB_LDR_DATA *pLdrData = NULL;

	_asm {
		mov eax, fs:[0x18];//获取指向self的指针
		mov pTeb, eax;//赋值给pTeb
	}

	pPeb = pTeb->ProcessEnvironmentBlock;//获取环境块的地址
	pLdrData = pPeb->Ldr;//获取LDR的地址
	InLoadOrderModuleTraverse((LDR_MODULE *)pLdrData->InLoadOrderModuleList.Flink, pPeb->Ldr);//传入第一个模块的地址

	system("pause");

	return 0;
}
           
利用Ldr获得加载的DLL的基址

继续阅读