Dll装载的实现
Dll装载主要由LdrInitializeThunk函数实现,具体如下
typedef struct _LDR_MODULE {
LIST_ENTRY InLoadOrderModuleList;//链表
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID BaseAddress;//基址
PVOID EntryPoint;//入口点
ULONG SizeOfImage;//映像大小
UNICODE_STRING FullDllName;//全路径
UNICODE_STRING BaseDllName;//本身的DLL名
ULONG Flags;
SHORT LoadCount;
SHORT TlsIndex;
LIST_ENTRY HashTableEntry;
ULONG TimeDateStamp;//时间戳 很重要 用以校对绑定输入的有效性
} LDR_MODULE, *PLDR_MODULE;//LDR_MODULE的结构
VOID STDCALL
__true_LdrInitializeThunk (ULONG Unknown1, ULONG Unknown2,//Thunk是转换的意思,是编译的专用术语,这里我们可以把它等价了“动态链接”
ULONG Unknown3, ULONG Unknown4)
{
. . . . . .
DPRINT("LdrInitializeThunk()/n");//打印信息
if (NtCurrentPeb()->Ldr == NULL || NtCurrentPeb()->Ldr->Initialized == FALSE)//检测Ldr模块是否存在和peb->ldr->Initialized是否被初始化决定是否要初始化模块
{
Peb = (PPEB)(PEB_BASE); //获取进程环境快其固定地址在0x7FFDF000 有两种方式获取进程的peb,一种是NtCurrentPeb宏,另一种就是PEB_BASE
DPRINT("Peb %x/n", Peb);
ImageBase = Peb->ImageBaseAddress; //EXE 映像在用户空间的起点
. . . .
/* Initialize NLS data */ //语言本地化有关
RtlInitNlsTables (Peb->AnsiCodePageData, Peb->OemCodePageData,
Peb->UnicodeCaseTableData, &NlsTable);
RtlResetRtlTranslations (&NlsTable);
NTHeaders = (PIMAGE_NT_HEADERS)(ImageBase + PEDosHeader->e_lfanew);
. . . . . .
/* create process heap */
/*
NTSYSAPI PVOID RtlCreateHeap(
ULONG Flags,
PVOID HeapBase, if NULL, allocates system memory for the heap from the process's virtual address space.
SIZE_T ReserveSize,
SIZE_T CommitSize,
PVOID Lock,
PRTL_HEAP_PARAMETERS Parameters);
*/
RtlInitializeHeapManager();//初始化进程堆
Peb->ProcessHeap = RtlCreateHeap(HEAP_GROWABLE, NULL, //创建一个堆、 及其第一个区块,其初始的大小来自映像头部的建议值,其中 SizeOfHeapReserve 是估计的最大值,SizeOfHeapCommit是初始值,这是在编译/连接时确定的。
NTHeaders->OptionalHeader.SizeOfHeapReserve, //保留堆大小
NTHeaders->OptionalHeader.SizeOfHeapCommit, //提交堆大小
NULL, NULL);
/* create loader information */
//PEB 中的 ProcessHeap 字段指向本进程用户空间可动态分配的内存区块“堆”
Peb->Ldr = (PPEB_LDR_DATA)RtlAllocateHeap (Peb->ProcessHeap,//分配一个PEB_LDR_DATA大小的堆,handle是Peb->ProcessHeap,这里用的是RTlCreateHeap的堆
0,
sizeof(PEB_LDR_DATA));
. . . . . .
/* PEB 中的另一个字段 Ldr是个 PEB_LDR_DATA 结构指针,所指向的数据结构用来为本进程维持三个“模块”
队列、即 InLoadOrderModuleList、InMemoryOrderModuleList、InInitializationOrderModuleList。
所谓“模块”就是 PE 格式的可执行映像,包括 EXE映像和 DLL 映像.
两个模块队列的不同之处在于排列的次序,一个是按装入的先后,一个是按装入的位置
每当为本进程装入一个模块、即.exe 映像或 DLL 映像时,就要为其分配/创建一个LDR_MODULE 数据结构,
并将其挂入 InLoadOrderModuleList。然后,完成对这个模块的动态连接以后,就把它挂入
InInitializationOrderModuleList 队列.LDR_MODULE 数据结构中有三个队列头,因而可以同时挂在三个队列
中。*/
Peb->Ldr->Length = sizeof(PEB_LDR_DATA);//设置为PEB_LDR_DATA大小
Peb->Ldr->Initialized = FALSE;//未初始化
Peb->Ldr->SsHandle = NULL;
InitializeListHead(&Peb->Ldr->InLoadOrderModuleList);//初始化三个链表
InitializeListHead(&Peb->Ldr->InMemoryOrderModuleList);
InitializeListHead(&Peb->Ldr->InInitializationOrderModuleList);
. . . . . .
/* add entry for ntdll */
NtModule = (PLDR_MODULE)RtlAllocateHeap (Peb->ProcessHeap,//分配一个LDR_MODULE大小的堆用以加载模块,实际上是一个_LDR_DATA_TABLE_ENTRY
0, //这里将ntdll这个模块串接到队列中
sizeof(LDR_MODULE));
. . . . . .
InsertTailList(&Peb->Ldr->InLoadOrderModuleList,//将新结点插入到顺序加载链表表尾
&NtModule->InLoadOrderModuleList);
InsertTailList(&Peb->Ldr->InInitializationOrderModuleList,//插入新结点到初始化模块链表表尾,因为ntdll无依赖dll,所以直接串接上
&NtModule->InInitializationOrderModuleList);
. . . . . .
/* add entry for executable (becomes first list entry) */
ExeModule = (PLDR_MODULE)RtlAllocateHeap (Peb->ProcessHeap,//将exe模块串接到链表里
0,
sizeof(LDR_MODULE));
. . . . . .
InsertHeadList(&Peb->Ldr->InLoadOrderModuleList,//插入到表头,所以实际上我们从LDR遍历的时候看到的第一个模块是该EXE模块
&ExeModule->InLoadOrderModuleList);
. . . . . .
/*当 CPU从 LdrPEStartup()返回时,EXE 对象需要直接或间接引入的所有 DLL 均已映射到用户空间并已完成连
接,对 EXE 模块的“启动” 、即初始化也已完成。
注意在调用 LdrPEStartup()时的参数 ImageBase 是目标 EXE 映像在用户空间的位置*/
EntryPoint = LdrPEStartup((PVOID)ImageBase, NULL, NULL, NULL);
. . . . . .
}
/* attach the thread */
RtlEnterCriticalSection(NtCurrentPeb()->LoaderLock); //进行加锁处理 对临界区进行处理
//目的是调用各个 DLL 的初始化过程,以及对 TLS、即“线程本地存储(Thread Local Storage)”的初始化
//TLS:有时候又确实需要让每个线程都对于同一个全局变量有一份自己的拷贝,TLS就是为此而设的
LdrpAttachThread();
RtlLeaveCriticalSection(NtCurrentPeb()->LoaderLock);//释放锁
}
再复习下PEB结构的知识
为什么要进行动态链接呢,因为现在虽然ntdll和我们的EXE模块被加载到用户空间,但是
并没有建立联系
,我们的程序并不能直接调用ntdll里的函数,所以这就需要LdrInitializeThunk函数起到一个类似
转换
的作用,通过将各个模块串接起来实现关系的建立。大概流程是如上所述,首先ntdll和exe程序是在程序里,系统会进行RtlCreateHeap操作,Create一个映像头的默认值,然后就根据返回的Handle操作这块堆,首先将ntdll加载,然后挂钩链表,接着将exe进行挂钩,但是挂接EXE模块的时候,是插入到表头的,所以实际上遍历LDR的时候第一个可见模块是EXE模块的相关信息。LdrInitializeThunk只是简单的进行了初始化,创建了进程堆,将EXE模块和ntdll模块这
两个模块
挂接到InLoadOrderModule链表上,其余的模块是由
LdrPEStartup
函数挂接的,同时LdrPEStartup函数计算出了
入口点
,作为返回值返回。
/*
LdrPEStartup里所做的事情
1. Relocate, if needed the EXE.
2. Fixup any imported symbol.
3. Compute the EXE's entry point.
*/
//注意在调用 LdrPEStartup()时的参数 ImageBase 是目标 EXE 映像在用户空间的位置,Module是加载的模块链
PEPFUNC LdrPEStartup (PVOID ImageBase, HANDLE SectionHandle,
PLDR_MODULE* Module, PWSTR FullDosName)
{
//PE 映像的 NtHeader 中有个指针,指向一个 OptionalHeader。说是“Optional”,实际上却是关键性的。在
//OptionalHeader中有个字段 ImageBase,是具体映像建议、或者说希望被装入的地址,即愿望地址
. . . . . .
DosHeader = (PIMAGE_DOS_HEADER) ImageBase;
NTHeaders = (PIMAGE_NT_HEADERS) (ImageBase + DosHeader->e_lfanew);
/*
* If the base address is different from the
* one the DLL is actually loaded, perform any
* relocation.
*/
if (ImageBase != (PVOID) NTHeaders->OptionalHeader.ImageBase)//判断基址是否一致,若EXE基址与期望基址不一致则进行重定位处理,其具体处理是在LdrPerformRelocations中实现的
{
DPRINT("LDR: Performing relocations/n");
//ImageBase 是目标 EXE 映像在用户空间的位置
Status = LdrPerformRelocations(NTHeaders, ImageBase);//若基址不是默认基址 则进行重定位处理
. . . . . .
}
if (Module != NULL)//也即假如是dll程序,但是事实上不可能满足(事实上这个条件永不满足)
{
*Module = LdrAddModuleEntry(ImageBase, NTHeaders, FullDosName);//LdrAddModuleEntry是创建一个LDR_DATA_TABLE_ENTRY,并返回这个模块信息的指针。
(*Module)->SectionHandle = SectionHandle;
}
else//所以这才是正题 之前LdrInitializeThunk调用LdrPEStartup的时候,传进的MODULE正是null
{
Module = &tmpModule;
Status = LdrFindEntryForAddress(ImageBase, Module);//这里函数返回后Module的值是EXE模块的指针,即LDR_DATA_TABLE_ENTRY结构的指针
. . . . . .
}
. . . . . .
/*
* If the DLL's imports symbols from other
* modules, fixup the imported calls entry points.
*/
//它所处理的就是当前模块所需DLL模块的装入(如果尚未装入的话)和连接。如前所述,这个函数递归地施行于所有的模块,直至最底层的“叶节点”ntdll.dll为止,类似多对一的一棵树。
DPRINT("About to fixup imports/n");
Status = LdrFixupImports(NULL, *Module);//将串接模块送入LdrFixupImports来实现输入表的修复,这里传入的是 PLDR_MODULE 即EXE模块的链表位置
if (!NT_SUCCESS(Status))//若失败,打印修复失败时的DLL名称
{
DPRINT1("LdrFixupImports() failed for %wZ/n", &(*Module)->BaseDllName);
return NULL;
}
DPRINT("Fixup done/n");
. . . . . .
Status = LdrpInitializeTlsForProccess();//初始化Tls
. . . . . .
/*
* Compute the DLL's entry point's address.
*/
. . . . . .
if (NTHeaders->OptionalHeader.AddressOfEntryPoint != 0)//计算EntryPoint并返回
{
EntryPoint = (PEPFUNC) (ImageBase + NTHeaders->OptionalHeader.AddressOfEntryPoint);
}
DPRINT("LdrPEStartup() = %x/n",EntryPoint);
return EntryPoint;
}
/*
获取PLDR_MODULE的EXE模块的指针
*/
NTSTATUS NTAPI
LdrFindEntryForAddress(PVOID Address,
PLDR_DATA_TABLE_ENTRY *Module)
{
PLIST_ENTRY ModuleListHead;
PLIST_ENTRY Entry;
PLDR_DATA_TABLE_ENTRY ModulePtr;
DPRINT("LdrFindEntryForAddress(Address %p)n", Address);
if (NtCurrentPeb()->Ldr == NULL)
return(STATUS_NO_MORE_ENTRIES);
RtlEnterCriticalSection(NtCurrentPeb()->LoaderLock);//进行遍历操作前先加锁 防止其他线程破坏
ModuleListHead = &NtCurrentPeb()->Ldr->InLoadOrderModuleList;//按加载顺序进行遍历 现在获得的是PLIST_ENTRY
Entry = ModuleListHead->Flink;//指向第一个
if (Entry == ModuleListHead)//如果此时指向本身,那么表明没有结点
{
RtlLeaveCriticalSection(NtCurrentPeb()->LoaderLock);
return(STATUS_NO_MORE_ENTRIES);
}
while (Entry != ModuleListHead)//只要不指向表头,就一直遍历,Entry为遍历指针
{
ModulePtr = CONTAINING_RECORD(Entry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);//获得该模块的头部
DPRINT("Scanning %wZ at %pn", &ModulePtr->BaseDllName, ModulePtr->DllBase);
if ((Address >= ModulePtr->DllBase) &&//如果传入的的Address在 DllBase~DllBase+SizeOfImage之间,表明找到了
((ULONG_PTR)Address <= ((ULONG_PTR)ModulePtr->DllBase + ModulePtr->SizeOfImage)))
{
*Module = ModulePtr;
RtlLeaveCriticalSection(NtCurrentPeb()->LoaderLock);
return(STATUS_SUCCESS);
}
Entry = Entry->Flink;
}
DPRINT("Failed to find module entry.n");
RtlLeaveCriticalSection(NtCurrentPeb()->LoaderLock);
return(STATUS_NO_MORE_ENTRIES);
}
下面看下重定位的细节处理,即当EXE基址与期望基址不符合时,内部的
绝对地址需要进行修复
。
系统重定位的操作
LdrPerformRelocations
如下:
typedef struct _IMAGE_DATA_DIRECTORY
{
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY,*PIMAGE_DATA_DIRECTORY;
//每个 IMAGE_BASE_RELOCATION 数据结构代表着一个“重定位块” ,每个重定位块的(容器)大小是两个页面(8KB),而 SizeOfBlock 则说明具体重定位块的实际大小。这实际的大小中包括了这 IMAGE_BASE_RELOCATION数据结构本身。
typedef struct _IMAGE_BASE_RELOCATION
{
DWORD VirtualAddress;
DWORD SizeOfBlock;
} IMAGE_BASE_RELOCATION,*PIMAGE_BASE_RELOCATION;
static NTSTATUS
LdrPerformRelocations(PIMAGE_NT_HEADERS NTHeaders, PVOID ImageBase)//传入EXE模块的NT头,和EXE模块的实际加载基址
{
. . . . . .
//PE 映像的 OptionalHeader 中有个大小为 16 的数组 DataDirectory[],其元素都是“数据目录” 、即IMAGE_DATA_DIRECTORY 数据结构:其中之一(下标为 5)就是“重定位目录” ,这是一个IMAGE_BASE_RELOCATION结构数组。
RelocationDDir =&NTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];//获得它在目录表中的位置地址
. . . . . .
ProtectSize = PAGE_SIZE;//使得重定位的大小以一页为单位 一个PAGE_SIZE是4KB
//所谓重定位,就是计算出实际装入地址与建议装入地址间的位移 Delta,然后调整每个重定位块中的每一个重定位项、即指针,具体就是在指针上加 Delta
Delta = (ULONG_PTR)ImageBase - NTHeaders->OptionalHeader.ImageBase;//实际的载入基址与默认基址的差值 存到Delta里
//IMAGE_BASE_RELOCATION结构数组
RelocationDir = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)ImageBase + RelocationDDir->VirtualAddress);//获得IMAGE_BASE_RELOCATION结构的起始地址
RelocationEnd = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)ImageBase + RelocationDDir->VirtualAddress + //重定位区块的大小
RelocationDDir->Size);
while (RelocationDir < RelocationEnd && RelocationDir->SizeOfBlock > 0)//开始遍历这个区间,这是重定位的核心循环,一次操控两个4KB,每次循环可以重定位8KB的块
{
Count = (RelocationDir->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(USHORT);//获得TypeOffset的项数
Page = ImageBase + RelocationDir->VirtualAddress;//定位到重定位数据所在的页VA 因为重定位是以页为单位的
TypeOffset = (PUSHORT)(RelocationDir + 1);//这个+1是sizeof(IMAGE_BASE_RELOCATION) 定位到要重定位的偏移数组即TypeOffset数组
/* Unprotect the page(s) we're about to relocate. */
ProtectPage = Page;//将要重定位的数据的页给ProtectPage
Status = NtProtectVirtualMemory(NtCurrentProcess(), &ProtectPage,//使要重定位的页临时改变为可读写属性,因为要修改数据,就需要读写东西
&ProtectSize, PAGE_READWRITE, &OldProtect);
. . . . . .
if (RelocationDir->VirtualAddress + PAGE_SIZE < NTHeaders->OptionalHeader.SizeOfImage)//若重定位数据的RVA+Size < SizeOfImage,说明下一页仍然需要进行重定位。如果程序占有的空间大于一页,那么需要多重定位一页
{
ProtectPage2 = ProtectPage + PAGE_SIZE;
Status = NtProtectVirtualMemory(NtCurrentProcess(), &ProtectPage2,//改写属性
&ProtectSize, PAGE_READWRITE, &OldProtect2);
. . . . . .
}
else
{
ProtectPage2 = NULL;
}
//具体的指针调整是由 LdrProcessRelocationBlock() 完成的,此前和此后的NtProtectVirtualMemory()只是为了先去除这些指针所在页面的写保护,而事后加以恢复。
RelocationDir = LdrProcessRelocationBlock(Page, Count, TypeOffset, Delta);//传入页地址,需要重定位的数据数目,需要重定位的数据偏移,需要修正的大小,让RelocationDir重新的移到修正过后的地方,然后若SizeOfBlock不为0,就会再次循环
. . . . . .
/* Restore old page protection. */
NtProtectVirtualMemory(NtCurrentProcess(),&ProtectPage,//恢复原先属性
&ProtectSize, OldProtect, &OldProtect);
if (ProtectPage2 != NULL)
{
NtProtectVirtualMemory(NtCurrentProcess(), &ProtectPage2,//恢复原先属性
&ProtectSize, OldProtect2, &OldProtect2);
}
}
return STATUS_SUCCESS;
}
重定位正是按页来修正的,一次改变两个页大小的写保护属性,然后进行数据的修正,这个操作其实需要两步,第一步确定要修正数据所在的页,然后定位到修正数据的偏移数组,每一个元素都是一个要修正的值,然后根据Count作为循环次数,Delta作为要修正的多少,最后统一以这个方式进行修正。
导入表的修复,此时各DLL的程序入口纪录在它们的LDR_MODULE数据结构中(但是ntdll.dll的入口已经不再需要,因为现在已经在这个模块里面了),借助InInitializationOrderModuleList队列就可依次调用所有DLL的初始化函数:
typedef struct _IMAGE_IMPORT_MODULE_DIRECTORY
{
DWORD dwRVAFunctionNameList;//导入函数的名字列表 即INT
DWORD dwUseless1;
DWORD dwUseless2;
DWORD dwRVAModuleName;//导入的DLL模块名
DWORD dwRVAFunctionAddressList;//导入函数地址列表 即IAT
}
static NTSTATUS
LdrFixupImports(IN PWSTR SearchPath OPTIONAL, IN PLDR_MODULE Module)//用于寻找DLL时的搜索路径, LDR_MODULE的指针
{
PIMAGE_IMPORT_DESCRIPTOR ImportModuleDirectory;
PIMAGE_IMPORT_DESCRIPTOR ImportModuleDirectoryCurrent;
PIMAGE_BOUND_IMPORT_DESCRIPTOR BoundImportDescriptor;
PIMAGE_BOUND_IMPORT_DESCRIPTOR BoundImportDescriptorCurrent;
PIMAGE_TLS_DIRECTORY TlsDirectory;
ULONG TlsSize = 0;
NTSTATUS Status;
PLDR_DATA_TABLE_ENTRY ImportedModule;
PCHAR ImportedName;
PWSTR ModulePath;
ULONG Size;
/*
* Process each import module.
*/
ImportModuleDirectory = (PIMAGE_IMPORT_MODULE_DIRECTORY)
RtlImageDirectoryEntryToData(Module->BaseAddress, TRUE,//通过模块基址,和RtlImageDirectoryEntryToData定位到它的输入表地址处
IMAGE_DIRECTORY_ENTRY_IMPORT, NULL);
BoundImportDescriptor = (PIMAGE_BOUND_IMPORT_DESCRIPTOR)
RtlImageDirectoryEntryToData(Module->BaseAddress, TRUE,//通过模块基址,和IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT定位到绑定输入的地方
IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT, NULL);
if (BoundImportDescriptor != NULL && ImportModuleDirectory == NULL)//因为本身绑定导入是辅助输入表加快搜索速度的,若输入表本身不存在,则报错
{
DPRINT1("%wZ has only a bound import directory\n", &Module->BaseDllName);
return STATUS_UNSUCCESSFUL;
}
if (BoundImportDescriptor) /* 如果绑定引入存在的话 */
{
DPRINT("BoundImportDescriptor %x\n", BoundImportDescriptor);//打印绑定输入描述符
BoundImportDescriptorCurrent = BoundImportDescriptor;//让BoundImportDescriptorCurrent来作为下面while循环的遍历的每一个绑定描述符
while (BoundImportDescriptorCurrent->OffsetModuleName)//当偏移不为0时,表明绑定输入是存在的,会一直遍历
{
ImportedName = (PCHAR)BoundImportDescriptor +
BoundImportDescriptorCurrent->OffsetModuleName;//获得绑定输入的DLL名字
TRACE_LDR("%wZ bound to %s\n", &Module->BaseDllName, ImportedName);//进行遍历,EXE的模块名绑定到 绑定输入表 中的相关Dll上
Status = LdrpGetOrLoadModule(SearchPath, ImportedName,//通过SearchPath找到或加载该模块,会通过LDR_MODULE->BaseDllName来二进制比较ImportedName
&ImportedModule, TRUE);//如果找不到就加载该模块 该模块的基址存放到了ImportedModule
. . . . . .
if (ImportedModule->TimeDateStamp != BoundImportDescriptorCurrent->TimeDateStamp)//首先检测引入模块的时间戳和绑定输入的时间戳是否一致,不一致只能采取常规方法引入
{
TRACE_LDR("%wZ has stale binding to %wZ\n",
&Module->BaseDllName, &ImportedModule->BaseDllName);
Status = LdrpProcessImportDirectory(Module, ImportedModule, ImportedName);//如果是一个stale时间戳,那么采用LdrpProcessImportDirectory常规引入
. . . . . .
}
else//说明时间戳是一致的,检测它转向的DLL模块
{
BOOLEAN WrongForwarder;//检测它的转向的DLL
WrongForwarder = FALSE;
. . . . . .
if (BoundImportDescriptorCurrent->NumberOfModuleForwarderRefs)//如果存在转向DLL
{
PIMAGE_BOUND_FORWARDER_REF BoundForwarderRef;
ULONG i;
PLDR_MODULE ForwarderModule;
PCHAR ForwarderName;
BoundForwarderRef = (PIMAGE_BOUND_FORWARDER_REF)(BoundImportDescriptorCurrent + 1);//转到它的dll的转向地址
for (i = 0;i < BoundImportDescriptorCurrent->NumberOfModuleForwarderRefs;i++, BoundForwarderRef++)//遍历之后的IMAGE_BOUND_FORWARDER_REF结构,判断转向的DLL的时间戳的有效性
{
ForwarderName = (PCHAR)BoundImportDescriptor +
BoundForwarderRef->OffsetModuleName;
TRACE_LDR("%wZ bound to %s via forwardes from %s\n",
&Module->BaseDllName, ForwarderName, ImportedName);
Status = LdrpGetOrLoadModule(SearchPath,//找到或者加载该依赖Dll
ForwarderName, &ForwarderModule, TRUE);
. . . . . .
if (ForwarderModule->TimeDateStamp !=//判断转向的DLL的时间戳的有效性和模块是否在它应有的基址上
BoundForwarderRef->TimeDateStamp ||
ForwarderModule->Flags & IMAGE_NOT_AT_BASE)
{
TRACE_LDR("%wZ has stale binding to %s\n",
&Module->BaseDllName, ForwarderName);
WrongForwarder = TRUE;//错误的转向
}
else
{
TRACE_LDR("%wZ has correct binding to %s\n",
&Module->BaseDllName, ForwarderName);
}
} //end for
}
if (WrongForwarder &&//如果转向错误 且导入模块的基址不在它的期望基址上
ImportedModule->Flags & IMAGE_NOT_AT_BASE)
{
Status = LdrpProcessImportDirectory(Module, ImportedModule,//只能采取常规方法导入该模块
ImportedName);
. . . . . .
}
else if (ImportedModule->Flags & IMAGE_NOT_AT_BASE)//若导入模块不在它应该在的期望地址上,进行重定位操作
{
TRACE_LDR("Adjust imports for %s from %wZ\n",
ImportedName, &Module->BaseDllName);
Status = LdrpAdjustImportDirectory(Module,//重定位操作
ImportedModule, ImportedName);
. . . . . .
}
else if (WrongForwarder)//如果转向DLL有问题,调用常规函数LdrpProcessImportDirectory处理导入表
{
. . . . . .
Status = LdrpProcessImportDirectory(Module,
ImportedModule, ImportedName);
. . . . . .
}
else
{
/* nothing to do */
}
}
BoundImportDescriptorCurrent +=//找到下一个绑定的DLL
BoundImportDescriptorCurrent->NumberOfModuleForwarderRefs + 1;
} //end while (BoundImportDescriptorCurrent->OffsetModuleName)
}
else if (ImportModuleDirectory) /* 普通引入 */
{
DPRINT("ImportModuleDirectory %x\n", ImportModuleDirectory);
ImportModuleDirectoryCurrent = ImportModuleDirectory;
while (ImportModuleDirectoryCurrent->dwRVAModuleName)//如果DLL模块名RVA不为空
{
ImportedName = (PCHAR)Module->BaseAddress + ImportModuleDirectoryCurrent->dwRVAModuleName;//获取模块名
TRACE_LDR("%wZ imports functions from %s\n",
&Module->BaseDllName, ImportedName);
Status = LdrpGetOrLoadModule(SearchPath, ImportedName,//加载该DLL模块
&ImportedModule, TRUE);
. . . . . .
Status = LdrpProcessImportDirectoryEntry(Module,ImportedModule, ImportModuleDirectoryCurrent);//核心修复交给LdrpProcessImportDirectoryEntry
. . . . . .
ImportModuleDirectoryCurrent++;
} //end while (ImportModuleDirectoryCurrent->dwRVAModuleName)
} //end if (ImportModuleDirectory)
if (TlsDirectory && TlsSize > 0)//如果Tls目录存在且Size>0,
{
LdrpAcquireTlsSlot(Module, TlsSize, FALSE);
}
return STATUS_SUCCESS;
}
大体的修复是这样的,假如绑定导入表不为空,先查找绑定导入表中的DLL,进行加载,然后对这个DLL的转向DLL进行判断,其判断依据主要是根据时间戳和Flag里的Base旗标。如果失败会调用
LdrpProcessImportDirectory
进行退而求其次的导入,即常规导入。然后从导入表中进行导入,主要调用的是
LdrpProcessImportDirectoryEntry
,其内部会分成
序号导入
和
名称导入
。下面给出
LdrFixupImports
的一些细节问题,有兴趣的读者可以看下源码,或者直接跳过源码看我的梳理图。
/*
导入表修复的核心函数
*/
LdrpProcessImportDirectoryEntry(PLDR_DATA_TABLE_ENTRY Module,//LDR_MODULE EXE模块地址
PLDR_DATA_TABLE_ENTRY ImportedModule,//要导入的模块
PIMAGE_IMPORT_DESCRIPTOR ImportModuleDirectory)//指向要导入的DLL的描述符
{
NTSTATUS Status;
PVOID* ImportAddressList;
PULONG FunctionNameList;
PVOID IATBase;
ULONG OldProtect;
ULONG Ordinal;
ULONG IATSize;
if (ImportModuleDirectory == NULL || ImportModuleDirectory->Name == 0)//若导入表为空,报错
{
return STATUS_UNSUCCESSFUL;
}
/* Get the import address list. */
ImportAddressList = (PVOID *)((ULONG_PTR)Module->DllBase + (ULONG_PTR)ImportModuleDirectory->FirstThunk);//定位到IAT
/* Get the list of functions to import. */
if (ImportModuleDirectory->OriginalFirstThunk != 0)//定位到INT
{
FunctionNameList = (PULONG) ((ULONG_PTR)Module->DllBase + (ULONG_PTR)ImportModuleDirectory->OriginalFirstThunk);
}
else//若OriginalFirstThunk为0,则使用FirstThunk的INT
{
FunctionNameList = (PULONG)((ULONG_PTR)Module->DllBase + (ULONG_PTR)ImportModuleDirectory->FirstThunk);
}
/* Get the size of IAT. */
IATSize = 0;
while (FunctionNameList[IATSize] != 0L)//INT表里每一项都是一个导入函数的Name的RVA
{
IATSize++;
}
/* Unprotect the region we are about to write into. */
IATBase = (PVOID)ImportAddressList;
IATSize *= sizeof(PVOID*);
Status = NtProtectVirtualMemory(NtCurrentProcess(),//改写模块的IAT前,先进行页属性的替换,保证该页可写
&IATBase,
&IATSize,
PAGE_READWRITE,
&OldProtect);
if (!NT_SUCCESS(Status))
{
DPRINT1("Failed to unprotect IAT.n");
return(Status);
}
/* Walk through function list and fixup addresses. */
while (*FunctionNameList != 0L)
{
if ((*FunctionNameList) & 0x80000000)//若是序号导入
{
Ordinal = (*FunctionNameList) & 0x7fffffff;//获取序号
*ImportAddressList = LdrGetExportByOrdinal(ImportedModule->DllBase, Ordinal);//传入导入模块的ImageBase和导入的序号值 返回后直接填充IAT
if ((*ImportAddressList) == NULL)//如果为NULL 表示导入失败 引发异常
{
DPRINT1("Failed to import #%ld from %wZn", Ordinal, &ImportedModule->FullDllName);
RtlpRaiseImportNotFound(NULL, Ordinal, &ImportedModule->FullDllName);
return STATUS_ENTRYPOINT_NOT_FOUND;
}
}
else//表明是用名字导入
{
IMAGE_IMPORT_BY_NAME *pe_name;
pe_name = RVA(Module->DllBase, *FunctionNameList);
*ImportAddressList = LdrGetExportByName(ImportedModule->DllBase, pe_name->Name, pe_name->Hint);
if ((*ImportAddressList) == NULL)
{
DPRINT1("Failed to import %s from %wZn", pe_name->Name, &ImportedModule->FullDllName);
RtlpRaiseImportNotFound((CHAR*)pe_name->Name, 0, &ImportedModule->FullDllName);
return STATUS_ENTRYPOINT_NOT_FOUND;
}
}
ImportAddressList++;
FunctionNameList++;
}
/* Protect the region we are about to write into. */
Status = NtProtectVirtualMemory(NtCurrentProcess(),
&IATBase,
&IATSize,
OldProtect,
&OldProtect);
if (!NT_SUCCESS(Status))
{
DPRINT1("Failed to protect IAT.n");
return(Status);
}
return STATUS_SUCCESS;
}
/*
用函数序号进行导入
*/
static PVOID
LdrGetExportByOrdinal (
PVOID BaseAddress,//导出函数的DLL的基址
ULONG Ordinal//要导出的函数的序号
)
{
PIMAGE_EXPORT_DIRECTORY ExportDir;
ULONG ExportDirSize;
PDWORD * ExFunctions;
PVOID Function;
ExportDir = (PIMAGE_EXPORT_DIRECTORY)RtlImageDirectoryEntryToData (BaseAddress,TRUE,IMAGE_DIRECTORY_ENTRY_EXPORT,&ExportDirSize);
ExFunctions = (PDWORD *)RVA(BaseAddress,ExportDir->AddressOfFunctions);//获取EAT
DPRINT("LdrGetExportByOrdinal(Ordinal %lu) = %pn",
Ordinal,
RVA(BaseAddress, ExFunctions[Ordinal - ExportDir->Base] )
);
Function = (0 != ExFunctions[Ordinal - ExportDir->Base]//若该Ordinal指向的值不是0,说明是一个有效的RVA,作为结果返回
? RVA(BaseAddress, ExFunctions[Ordinal - ExportDir->Base] )
: NULL);
if (((ULONG)Function >= (ULONG)ExportDir) &&//转发函数判断
((ULONG)Function < (ULONG)ExportDir + (ULONG)ExportDirSize))
{
DPRINT("Forward: %sn", (PCHAR)Function);
Function = LdrFixupForward((PCHAR)Function);
}
return Function;
}
/*
用名字导入
*/
static PVOID
LdrGetExportByName(PVOID BaseAddress,//导出DLL的模块基址
PUCHAR SymbolName,//函数名
WORD Hint)//函数序号
{
PIMAGE_EXPORT_DIRECTORY ExportDir;
PDWORD * ExFunctions;
PDWORD * ExNames;
USHORT * ExOrdinals;
PVOID ExName;
ULONG Ordinal;
PVOID Function;
LONG minn, maxn;
ULONG ExportDirSize;
DPRINT("LdrGetExportByName %p %s %hun", BaseAddress, SymbolName, Hint);
ExportDir = (PIMAGE_EXPORT_DIRECTORY)//获得IMAGE_DIRECTORY_ENTRY_EXPORT的地址
RtlImageDirectoryEntryToData(BaseAddress,
TRUE,
IMAGE_DIRECTORY_ENTRY_EXPORT,
&ExportDirSize);
if (ExportDir == NULL)
{
DPRINT1("LdrGetExportByName(): no export directory, "
"can't lookup %s/%hu!n", SymbolName, Hint);
return NULL;
}
//The symbol names may be missing entirely
if (ExportDir->AddressOfNames == 0)
{
DPRINT("LdrGetExportByName(): symbol names missing entirelyn");
return NULL;
}
/*
* Get header pointers
*/
ExNames = (PDWORD *)RVA(BaseAddress,
ExportDir->AddressOfNames);//EAT
ExOrdinals = (USHORT *)RVA(BaseAddress,
ExportDir->AddressOfNameOrdinals);//EAT和ENT的桥梁
ExFunctions = (PDWORD *)RVA(BaseAddress,
ExportDir->AddressOfFunctions);//ENT
/*
* Check the hint first
*/
if (Hint < ExportDir->NumberOfNames)//先校验了hint是否合法 不过hint不是必须的 只是为了加快搜索 这一步尝试用hint直接找到函数位置
{
ExName = RVA(BaseAddress, ExNames[Hint]);//获取了该hint对应的函数名
if (strcmp(ExName, (PCHAR)SymbolName) == 0)//若相等
{
Ordinal = ExOrdinals[Hint];//获得了在EAT的ordinal
Function = RVA(BaseAddress, ExFunctions[Ordinal]);//获得了对应的导出函数的地址
if (((ULONG)Function >= (ULONG)ExportDir) &&//若函数的地址在导出表的范围内,说明是一个转向函数,因为正常而言地址在代码段才对
((ULONG)Function < (ULONG)ExportDir + (ULONG)ExportDirSize))
{
DPRINT("Forward: %sn", (PCHAR)Function);
Function = LdrFixupForward((PCHAR)Function);//需要调用LdrFixupForward来找到最终的调用函数
if (Function == NULL)
{
DPRINT1("LdrGetExportByName(): failed to find %sn",SymbolName);
}
return Function;
}
if (Function != NULL)
return Function;
}
}
/*若hint无效,则采用二分搜索进行查找
* Binary search
*/
minn = 0;
maxn = ExportDir->NumberOfNames - 1;
while (minn <= maxn)
{
LONG mid;
LONG res;
mid = (minn + maxn) / 2;
ExName = RVA(BaseAddress, ExNames[mid]);
res = strcmp(ExName, (PCHAR)SymbolName);
if (res == 0)
{
Ordinal = ExOrdinals[mid];
Function = RVA(BaseAddress, ExFunctions[Ordinal]);
if (((ULONG)Function >= (ULONG)ExportDir) &&
((ULONG)Function < (ULONG)ExportDir + (ULONG)ExportDirSize))
{
DPRINT("Forward: %sn", (PCHAR)Function);
Function = LdrFixupForward((PCHAR)Function);
if (Function == NULL)
{
DPRINT1("LdrGetExportByName(): failed to find %sn",SymbolName);
}
return Function;
}
if (Function != NULL)
return Function;
}
else if (minn == maxn)
{
DPRINT("LdrGetExportByName(): binary search failedn");
break;
}
else if (res > 0)
{
maxn = mid - 1;
}
else
{
minn = mid + 1;
}
}
DPRINT("LdrGetExportByName(): failed to find %sn",SymbolName);
return (PVOID)NULL;
}
/*
对于转向函数而言,需要找到它的最终的位置
*/
static PVOID
LdrFixupForward(PCHAR ForwardName)
{
CHAR NameBuffer[128];
UNICODE_STRING DllName;
NTSTATUS Status;
PCHAR p;
PLDR_DATA_TABLE_ENTRY Module;
PVOID BaseAddress;
strcpy(NameBuffer, ForwardName);
p = strchr(NameBuffer, '.');//转向函数的形式一般是xxdll.function 比如use32.MessageBoxA
if (p != NULL)
{
*p = 0;
DPRINT("Dll: %s Function: %sn", NameBuffer, p+1);
RtlCreateUnicodeStringFromAsciiz (&DllName,
NameBuffer);
Status = LdrFindEntryForName (&DllName, &Module, FALSE);//尝试获取模块地址
/* FIXME:
* The caller (or the image) is responsible for loading of the dll, where the function is forwarded.
*/
if (!NT_SUCCESS(Status))//若失败
{
Status = LdrLoadDll(NULL, NULL, &DllName, &BaseAddress);//载入该dll模块
if (NT_SUCCESS(Status))
{
Status = LdrFindEntryForName (&DllName, &Module, FALSE);//获取该模块的地址
}
}
RtlFreeUnicodeString (&DllName);
if (!NT_SUCCESS(Status))
{
DPRINT1("LdrFixupForward: failed to load %sn", NameBuffer);
return NULL;
}
DPRINT("BaseAddress: %pn", Module->DllBase);
return LdrGetExportByName(Module->DllBase, (PUCHAR)(p+1), -1);//再次调用获取导出函数的地址
}
return NULL;
}
/*
当绑定导入表的某个直接引入DLL的Forwarder没问题,仅仅基址不在期望地址上,那么就需要重定位这个DLL的导入函数的地址即可
*/
static NTSTATUS
LdrpAdjustImportDirectory(PLDR_DATA_TABLE_ENTRY Module,//EXE模块
PLDR_DATA_TABLE_ENTRY ImportedModule,//被引入的DLL模块
PCHAR ImportedName)//DLL名
{
PIMAGE_IMPORT_DESCRIPTOR ImportModuleDirectory;
NTSTATUS Status;
PVOID* ImportAddressList;
PVOID Start;
PVOID End;
PULONG FunctionNameList;
PVOID IATBase;
ULONG OldProtect;
ULONG Offset;
ULONG IATSize;
PIMAGE_NT_HEADERS NTHeaders;
PCHAR Name;
ULONG Size;
DPRINT("LdrpAdjustImportDirectory(Module %p '%wZ', %p '%wZ', '%s')n",
Module, &Module->BaseDllName, ImportedModule, &ImportedModule->BaseDllName, ImportedName);
ImportModuleDirectory = (PIMAGE_IMPORT_DESCRIPTOR)
RtlImageDirectoryEntryToData(Module->DllBase,
TRUE,
IMAGE_DIRECTORY_ENTRY_IMPORT,
&Size);
if (ImportModuleDirectory == NULL)//若输入表为空
{
return STATUS_UNSUCCESSFUL;
}
while (ImportModuleDirectory->Name)//遍历EXE模块的输入表
{
Name = (PCHAR)Module->DllBase + ImportModuleDirectory->Name;//获得该输入描述符的DLLName
if (0 == _stricmp(Name, (PCHAR)ImportedName))//与我们要调节的DLL的名进行比对,若符合
{
/* Get the import address list. */
ImportAddressList = (PVOID *)((ULONG_PTR)Module->DllBase + (ULONG_PTR)ImportModuleDirectory->FirstThunk);//获得输入地址表
/* Get the list of functions to import. */
if (ImportModuleDirectory->OriginalFirstThunk != 0)//选择从哪个地方获得INT
{
FunctionNameList = (PULONG) ((ULONG_PTR)Module->DllBase + (ULONG_PTR)ImportModuleDirectory->OriginalFirstThunk);//获得INA
}
else
{
FunctionNameList = (PULONG)((ULONG_PTR)Module->DllBase + (ULONG_PTR)ImportModuleDirectory->FirstThunk);
}
/* Get the size of IAT. */
IATSize = 0;
while (FunctionNameList[IATSize] != 0L)//获得INT表大小
{
IATSize++;
}
/* Unprotect the region we are about to write into. */
IATBase = (PVOID)ImportAddressList;
IATSize *= sizeof(PVOID*);
Status = NtProtectVirtualMemory(NtCurrentProcess(),//临时改变页属性
&IATBase,
&IATSize,
PAGE_READWRITE,
&OldProtect);
if (!NT_SUCCESS(Status))
{
DPRINT1("Failed to unprotect IAT.n");
return(Status);
}
NTHeaders = RtlImageNtHeader (ImportedModule->DllBase);
Start = (PVOID)NTHeaders->OptionalHeader.ImageBase;//导入模块的基址的起始位置
End = (PVOID)((ULONG_PTR)Start + ImportedModule->SizeOfImage);//导入模块的结束位置
Offset = (ULONG)((ULONG_PTR)ImportedModule->DllBase - (ULONG_PTR)Start);//要修正的大小
/* Walk through function list and fixup addresses. */
while (*FunctionNameList != 0L)//以INT为结束标志
{
if (*ImportAddressList >= Start && *ImportAddressList < End)//有时候预先知道DLL的位置,所以会尝试将某些DLL的函数地址加载到IAT中,但是可能有误差,所以将这个地址值加上offset就是正确的地址值
{
(*ImportAddressList) = (PVOID)((ULONG_PTR)(*ImportAddressList) + Offset);
}
ImportAddressList++;
FunctionNameList++;
}
/* Protect the region we are about to write into. */
Status = NtProtectVirtualMemory(NtCurrentProcess(),
&IATBase,
&IATSize,
OldProtect,
&OldProtect);
if (!NT_SUCCESS(Status))
{
DPRINT1("Failed to protect IAT.n");
return(Status);
}
}
ImportModuleDirectory++;
}
return STATUS_SUCCESS;
}
/* 搜索路径的获取
if (SearchPath == NULL)
{
// get application running path
wcscpy (SearchPathBuffer, NtCurrentPeb()->ProcessParameters->ImagePathName.Buffer);//获取进程path
len = wcslen (SearchPathBuffer);
while (len && SearchPathBuffer[len - 1] != L'\\')//遍历寻找到倒数第一个\字符
len--;
if (len)
SearchPathBuffer[len-1] = L'\0';//舍弃后面的路径,即获得当前路径
wcscat (SearchPathBuffer, L";");//当前文件目录
wcscat (SearchPathBuffer, SharedUserData->NtSystemRoot);//是操作系统为每个进程提供的个共享数据结构,里面存放有很多重要的系统信息,如TickCount、系统时间、SystemRoot等
wcscat (SearchPathBuffer, L"\\system32;");//这里连接的是\Windows\system32目录
wcscat (SearchPathBuffer, SharedUserData->NtSystemRoot);//Windows目录
wcscat (SearchPathBuffer, L";.");
SearchPath = SearchPathBuffer;//将获取的路径给SearchPath
}
*/
LdrpGetOrLoadModule(PWCHAR SearchPath,//搜索路径
PCHAR Name,//模块名
PLDR_DATA_TABLE_ENTRY* Module,//模块链表
BOOLEAN Load)
{
ANSI_STRING AnsiDllName;
UNICODE_STRING DllName;
NTSTATUS Status;
DPRINT("LdrpGetOrLoadModule() called for %sn", Name);
RtlInitAnsiString(&AnsiDllName, Name);
Status = RtlAnsiStringToUnicodeString(&DllName, &AnsiDllName, TRUE);
if (!NT_SUCCESS(Status))
{
return Status;
}
Status = LdrFindEntryForName (&DllName, Module, Load);//根据名字返回相应的Module
if (Load && !NT_SUCCESS(Status))//如果要求加载它,并且我们获取不到它的模块指针,即处于未加载状态下,我们加载它 不然的话已经找到并放进Module里去了,所以不需要再加载
{
Status = LdrpLoadModule(SearchPath,0,&DllName,Module,NULL);//调用LdrpLoadModule加载该模块
if (NT_SUCCESS(Status))
{
Status = LdrFindEntryForName (&DllName, Module, FALSE);//在调用一次根据DLL名字寻找模块基址的例程
}
if (!NT_SUCCESS(Status))//若加载失败 即未找到该加载的模块的话
{
ULONG ErrorResponse;
ULONG_PTR ErrorParameter = (ULONG_PTR)&DllName;
DPRINT1("failed to load %wZn", &DllName);
NtRaiseHardError(STATUS_DLL_NOT_FOUND,//Raise 一个异常,code是STATUS_DLL_NOT_FOUND
1,
1,
&ErrorParameter,
OptionOk,
&ErrorResponse);
}
}
RtlFreeUnicodeString (&DllName);
return Status;
}
/*
根据名字返回模块基址
*/
static NTSTATUS
LdrFindEntryForName(PUNICODE_STRING Name,
PLDR_DATA_TABLE_ENTRY *Module,//遍历的模块链表
BOOLEAN Ref)
{
PLIST_ENTRY ModuleListHead;
PLIST_ENTRY Entry;
PLDR_DATA_TABLE_ENTRY ModulePtr;
BOOLEAN ContainsPath;
UNICODE_STRING AdjustedName;
DPRINT("LdrFindEntryForName(Name %wZ)n", Name);
if (NtCurrentPeb()->Ldr == NULL)
return(STATUS_NO_MORE_ENTRIES);
RtlEnterCriticalSection(NtCurrentPeb()->LoaderLock);//加锁
ModuleListHead = &NtCurrentPeb()->Ldr->InLoadOrderModuleList;
Entry = ModuleListHead->Flink;//获取第一个模块
if (Entry == ModuleListHead)//若指向本身,则说明没模块
{
RtlLeaveCriticalSection(NtCurrentPeb()->LoaderLock);
return(STATUS_NO_MORE_ENTRIES);
}
// NULL is the current process
if (Name == NULL)//若名字为空,说明找的是本身进程的模块
{
*Module = ExeModule;
RtlLeaveCriticalSection(NtCurrentPeb()->LoaderLock);
return(STATUS_SUCCESS);
}
ContainsPath = (Name->Length >= 2 * sizeof(WCHAR) && L':' == Name->Buffer[1]);//判断是全路径还是相对路径
LdrAdjustDllName (&AdjustedName, Name, !ContainsPath);//对路径进行一个统一的处理
if (LdrpLastModule)//若是最后一个模块
{
if ((! ContainsPath &&//对于相对路径而言,比对BaseDllName
0 == RtlCompareUnicodeString(&LdrpLastModule->BaseDllName, &AdjustedName, TRUE)) ||
(ContainsPath &&//对于绝对路径而言,比对FullDllName
0 == RtlCompareUnicodeString(&LdrpLastModule->FullDllName, &AdjustedName, TRUE)))
{
*Module = LdrpLastModule;//将该模块返回
if (Ref && (*Module)->LoadCount != LDRP_PROCESS_CREATION_TIME)//如果要加载 那么该模块的LoadCount++,也可以理解为引用计数加1
{
(*Module)->LoadCount++;
}
RtlLeaveCriticalSection(NtCurrentPeb()->LoaderLock);
RtlFreeUnicodeString(&AdjustedName);
return(STATUS_SUCCESS);
}
}
while (Entry != ModuleListHead)//进行遍历,判断条件跟上面的一致
{
ModulePtr = CONTAINING_RECORD(Entry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
DPRINT("Scanning %wZ %wZn", &ModulePtr->BaseDllName, &AdjustedName);
if ((! ContainsPath &&
0 == RtlCompareUnicodeString(&ModulePtr->BaseDllName, &AdjustedName, TRUE)) ||
(ContainsPath &&
0 == RtlCompareUnicodeString(&ModulePtr->FullDllName, &AdjustedName, TRUE)))
{
*Module = LdrpLastModule = ModulePtr;
if (Ref && ModulePtr->LoadCount != LDRP_PROCESS_CREATION_TIME)
{
ModulePtr->LoadCount++;
}
RtlLeaveCriticalSection(NtCurrentPeb()->LoaderLock);
RtlFreeUnicodeString(&AdjustedName);
return(STATUS_SUCCESS);
}
Entry = Entry->Flink;
}
/*
当绑定输入失效的时候,采取的常规导入方式函数
*/
static NTSTATUS
LdrpProcessImportDirectory(
PLDR_DATA_TABLE_ENTRY Module,
PLDR_DATA_TABLE_ENTRY ImportedModule,
PCHAR ImportedName)
{
NTSTATUS Status;
PIMAGE_IMPORT_DESCRIPTOR ImportModuleDirectory;
PCHAR Name;
ULONG Size;
DPRINT("LdrpProcessImportDirectory(%p '%wZ', '%s')n",
Module, &Module->BaseDllName, ImportedName);
ImportModuleDirectory = (PIMAGE_IMPORT_DESCRIPTOR)RtlImageDirectoryEntryToData(Module->DllBase, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT ,&Size);
if (ImportModuleDirectory == NULL)
{
return STATUS_UNSUCCESSFUL;
}
while (ImportModuleDirectory->Name)//遍历每一个DLL
{
Name = (PCHAR)Module->DllBase + ImportModuleDirectory->Name;
if (0 == _stricmp(Name, ImportedName))//当导入的DLL与导入表中的相匹配时
{
Status = LdrpProcessImportDirectoryEntry(Module, ImportedModule, ImportModuleDirectory);
if (!NT_SUCCESS(Status))
{
return Status;
}
}
ImportModuleDirectory++;
}
return STATUS_SUCCESS;
}
PLDR_DATA_TABLE_ENTRY
LdrAddModuleEntry(PVOID ImageBase,
PIMAGE_NT_HEADERS NTHeaders,
PWSTR FullDosName)
{
PLDR_DATA_TABLE_ENTRY Module;
Module = RtlAllocateHeap(RtlGetProcessHeap(), 0, sizeof (LDR_DATA_TABLE_ENTRY));//分配一个LDR_DATA_TABLE_ENTRY用于存放模块信息
ASSERT(Module);//失败了就不用继续了
memset(Module, 0, sizeof(LDR_DATA_TABLE_ENTRY));
Module->DllBase = (PVOID)ImageBase;
Module->EntryPoint = (PVOID)NTHeaders->OptionalHeader.AddressOfEntryPoint;//是一个RVA值
if (Module->EntryPoint != 0)
Module->EntryPoint = (PVOID)((ULONG_PTR)Module->EntryPoint + (ULONG_PTR)Module->DllBase);//填充真正的VA到EntryPoint
Module->SizeOfImage = LdrpGetResidentSize(NTHeaders);//获得真实的内存映像大小
if (NtCurrentPeb()->Ldr->Initialized == TRUE)//如果已经被初始化,那么LoadCount会变成1,否则设置为在创建阶段
{
/* loading while app is running */
Module->LoadCount = 1;
} else {
/*
* loading while app is initializing
* dll must not be unloaded
*/
Module->LoadCount = LDRP_PROCESS_CREATION_TIME;
}
Module->Flags = 0;
Module->TlsIndex = -1;//Tls初始化为0xFFFF
Module->CheckSum = NTHeaders->OptionalHeader.CheckSum;
Module->TimeDateStamp = NTHeaders->FileHeader.TimeDateStamp;
RtlCreateUnicodeString (&Module->FullDllName,
FullDosName);
RtlCreateUnicodeString (&Module->BaseDllName,
wcsrchr(FullDosName, L'\\') + 1);
DPRINT ("BaseDllName %wZn", &Module->BaseDllName);
RtlEnterCriticalSection (NtCurrentPeb()->LoaderLock);
InsertTailList(&NtCurrentPeb()->Ldr->InLoadOrderModuleList,//插入到按装载顺序模块的链表中
&Module->InLoadOrderLinks);
RtlLeaveCriticalSection(NtCurrentPeb()->LoaderLock);
return(Module);
}
-----------------------------------------------------------------------------------------------------------------------------
/*
* Compute size of an image as it is actually present in virt memory
* (i.e. excluding NEVER_LOAD sections)
*/
ULONG
LdrpGetResidentSize(PIMAGE_NT_HEADERS NTHeaders)
{
PIMAGE_SECTION_HEADER SectionHeader;
unsigned SectionIndex;
ULONG ResidentSize;
SectionHeader = (PIMAGE_SECTION_HEADER)((char *) &NTHeaders->OptionalHeader
+ NTHeaders->FileHeader.SizeOfOptionalHeader);
ResidentSize = 0;
for (SectionIndex = 0; SectionIndex < NTHeaders->FileHeader.NumberOfSections; SectionIndex++)//遍历每一个区块 每次遍历获得的尺寸是区块表中最大的VirtualAddress+VirtualSize的值,而不是直接根据OptionalHeader的值来的
{
if (0 == (SectionHeader->Characteristics & IMAGE_SCN_LNK_REMOVE)
&& ResidentSize < SectionHeader->VirtualAddress + SectionHeader->Misc.VirtualSize)
{
ResidentSize = SectionHeader->VirtualAddress + SectionHeader->Misc.VirtualSize;
}
SectionHeader++;
}
return ResidentSize;
}
调用链:
差不多就是这样,至于
LdrpInitializeTlsForProccess
,也是一个非常重要的模块,不过这次的内容非常多,所以讲Tls放到下节讲。
最后最后再总结下流程。
首先我们的ntdll和exe模块被加载入我们的用户空间中,但是还没有建立任何联系。但是ntdll的函数LdrInitializeThunk函数作为入口函数,是可以自动运行的。它会初始化peb的Ldr结构,并会将ntdll模块和exe模块挂接到InLoadOrderModuleList链表中,从而建立起联系,而EXE模块必定是插入到表头的。LdrInitializeThunk最主要的就是做了这件初始化工作,然后将剩下DLL的载入工作和EXE模块的重定位工作交给LdrPEStartup来完成。
LdrPEStartup的本身的工作也是起到一个类似“总指挥”的工作,主要是先判断EXE文件是否需要重定位,若需要,则将工作下发给LdrPerformRelocations。然后进行导入表的一个修复工作(实际上是对IAT的递归安装)。其主要任务交给了LdrFixupImports函数。它在内部主要是根据绑定导入和常规导入表的修复。为了加快IAT的安装,LdrFixupImports会尝试对绑定导入表中进行判断,最主要的是对时间戳和Forwarder的有效性的判断。若时间戳stale或者Forwarder的地址不在期望地址上,则只能下发给常规导入函数进行IAT的安装。