根据我们上次所讲,其大概流程是差不多理清了,但是还有具体的一些细节和一个Tls的大头还没阐述,先把LoadDll的具体细节讲完,这里推荐大家去看毛德操先生的著作《内核情景分析》,这本书对ReactOS(一种也是基于NT的内核,但是不同于Windows,它是完全开源的)的解析入木三分,我读完这个DLL的装载后大有所获,所以强烈推荐这本好书!!!
好了,接下来就是我们的正题了先复习下调用流程。
LdrInitializeThunk > LdrPEStartup() > LdrFixupImports()> LdrpProcessImportDirectory() > LdrpProcessImportDirectoryEntry()> LdrGetExportByOrdinal() > LdrFixupForward() 首先复习下LdrFixupForward的内容
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, '.');//将转向DLL的dll模块名和转向函数分割
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))//若未加载转向的DLL
{
Status = LdrLoadDll(NULL,//加载该DLL,及初始化其依赖的DLL
NULL,
&DllName,
&BaseAddress);
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;
}
可以看到LdrFixupForward->LdrLoadDll 这才是我们今天的主角
/*
在LdrFixupForward里调用语句Status = LdrLoadDll(NULL,NULL,&DllName,&BaseAddress);
*/
NTSTATUS NTAPI
LdrLoadDll (IN PWSTR SearchPath OPTIONAL,
IN PULONG LoadFlags OPTIONAL,
IN PUNICODE_STRING Name,
OUT PVOID *BaseAddress /* also known as HMODULE*, and PHANDLE 'DllHandle' */)
{
NTSTATUS Status;
PLDR_DATA_TABLE_ENTRY Module;
PPEB Peb = NtCurrentPeb();//获得peb指针
TRACE_LDR("LdrLoadDll, loading %wZ%s%Sn",
Name,
SearchPath ? L" from " : L"",
SearchPath ? SearchPath : L"");
Status = LdrpLoadModule(SearchPath, LoadFlags ? *LoadFlags : 0, Name, &Module, BaseAddress);//下发给LdrpLoadModule
if (NT_SUCCESS(Status) &&//若成功加载,并且不是映射为单纯的数据文件,因为我们这里是DLL文件,所以肯定进这个控制流
(!LoadFlags || 0 == (*LoadFlags & LOAD_LIBRARY_AS_DATAFILE)))
{
if (!(Module->Flags & LDRP_PROCESS_ATTACH_CALLED))//模块并不是进程加载时被调用
{
RtlEnterCriticalSection(Peb->LoaderLock);
Status = LdrpAttachProcess();
RtlLeaveCriticalSection(Peb->LoaderLock);
}
}
if ((!Module) && (NT_SUCCESS(Status)))//若模块
return Status;
*BaseAddress = NT_SUCCESS(Status) ? Module->DllBase : NULL;//返填充
return Status;
}
/*
LdrpLoadModule //通过该函数装载模块和其依赖DLL
LdrpAttachProcess //调用该函数的初始化函数
*/
可以看到,LdrLoadDll只是起到一个类似于stub的功能,主要实现下发给了LdrpLoadModule用以加载该模块及其依赖的DLL。而对于LdrpAttachProcess而言,则是调用这些DLL的初始化函数(dwReason == DLL_PROCESS_ATTACH),这个之后再讲,先把加载捋清了。
LdrFixupForward->LdrLoadDll->LdrpLoadModule
static NTSTATUS
LdrpLoadModule(IN PWSTR SearchPath OPTIONAL,
IN ULONG LoadFlags,
IN PUNICODE_STRING Name, //模块名
PLDR_DATA_TABLE_ENTRY *Module,//模块信息
PVOID *BaseAddress OPTIONAL)
{
UNICODE_STRING AdjustedName;
UNICODE_STRING FullDosName;
NTSTATUS Status;
PLDR_DATA_TABLE_ENTRY tmpModule;
HANDLE SectionHandle;
SIZE_T ViewSize;
PVOID ImageBase;
PIMAGE_NT_HEADERS NtHeaders;
BOOLEAN MappedAsDataFile;
PVOID ArbitraryUserPointer;
if (Module == NULL)
{
Module = &tmpModule;
}
/* adjust the full dll name */
LdrAdjustDllName(&AdjustedName, Name, FALSE);//调节全路径至BaseDllName
DPRINT("%wZn", &AdjustedName);
MappedAsDataFile = FALSE;
/* Test if dll is already loaded */
Status = LdrFindEntryForName(&AdjustedName, Module, TRUE);
if (NT_SUCCESS(Status))//若加载过就不必再加载了
{
RtlFreeUnicodeString(&AdjustedName);
if (NULL != BaseAddress)
{
*BaseAddress = (*Module)->DllBase;
}
}
else
{
/* Open or create dll image section */
Status = LdrpMapKnownDll(&AdjustedName, &FullDosName, &SectionHandle);//先映射看是否是已知DLL KnownDll
if (!NT_SUCCESS(Status))//若失败,调用LdrpMapDllImageFile
{
MappedAsDataFile = (0 != (LoadFlags & LOAD_LIBRARY_AS_DATAFILE));//是否映射为数据文件
Status = LdrpMapDllImageFile(SearchPath, &AdjustedName, &FullDosName,
MappedAsDataFile, &SectionHandle);
}
if (!NT_SUCCESS(Status))
{
DPRINT1("Failed to create or open dll section of '%wZ' (Status %lx)n", &AdjustedName, Status);
RtlFreeUnicodeString(&AdjustedName);
return Status;
}
RtlFreeUnicodeString(&AdjustedName);
/* Map the dll into the process */
ViewSize = 0;
ImageBase = 0;
ArbitraryUserPointer = NtCurrentTeb()->Tib.ArbitraryUserPointer;
NtCurrentTeb()->Tib.ArbitraryUserPointer = FullDosName.Buffer;
Status = NtMapViewOfSection(SectionHandle,//要映射的区域句柄
NtCurrentProcess(),//映射到的进程空间
&ImageBase,//成功映射后会返回该模块的基址
0,
0,
NULL,
&ViewSize,//设置为0,表明映射全部的区域
ViewShare,//表明可映射到任意子进程 因为是DLL,所以是理所当然的
0,
PAGE_READONLY);
NtCurrentTeb()->Tib.ArbitraryUserPointer = ArbitraryUserPointer;
if (!NT_SUCCESS(Status))
{
DPRINT1("map view of section failed (Status 0x%08lx)n", Status);
RtlFreeUnicodeString(&FullDosName);
NtClose(SectionHandle);
return(Status);
}
if (NULL != BaseAddress)
{
*BaseAddress = ImageBase;
}
if (!MappedAsDataFile)//若不是数据文件
{
/* Get and check the NT headers */
NtHeaders = RtlImageNtHeader(ImageBase);//检查若不是以数据文件的形式映射,检查它的PE文件格式
if (NtHeaders == NULL)//如果为空 表明失败
{
DPRINT1("RtlImageNtHeaders() failedn");
NtUnmapViewOfSection (NtCurrentProcess (), ImageBase);
NtClose (SectionHandle);
RtlFreeUnicodeString(&FullDosName);
return STATUS_UNSUCCESSFUL;
}
}
DPRINT("Mapped %wZ at %xn", &FullDosName, ImageBase);
if (MappedAsDataFile)
{
ASSERT(NULL != BaseAddress);
if (NULL != BaseAddress)
{
*BaseAddress = (PVOID) ((char *) *BaseAddress + 1);
}
*Module = NULL;
RtlFreeUnicodeString(&FullDosName);
NtClose(SectionHandle);
return STATUS_SUCCESS;//此时数据文件会返回
}
/* If the base address is different from the
* one the DLL is actually loaded, perform any
* relocation. */
if (ImageBase != (PVOID) NtHeaders->OptionalHeader.ImageBase)//会自动在加载DLL的时候对DLL进行重定位操作
{
DPRINT1("Relocating (%lx -> %p) %wZn",
NtHeaders->OptionalHeader.ImageBase, ImageBase, &FullDosName);
Status = LdrPerformRelocations(NtHeaders, ImageBase);
if (!NT_SUCCESS(Status))
{
DPRINT1("LdrPerformRelocations() failedn");
NtUnmapViewOfSection (NtCurrentProcess (), ImageBase);
NtClose (SectionHandle);
RtlFreeUnicodeString(&FullDosName);
return STATUS_UNSUCCESSFUL;
}
}
*Module = LdrAddModuleEntry(ImageBase, NtHeaders, FullDosName.Buffer);//生成一个LDR_MODULE_DATA_TABLE,并填好相应的信息
(*Module)->SectionPointer = SectionHandle;//添加SectionPointer
if (ImageBase != (PVOID) NtHeaders->OptionalHeader.ImageBase)//添加Flags
{
(*Module)->Flags |= LDRP_IMAGE_NOT_AT_BASE;//Flag要添加 不在期望地址
}
if (NtHeaders->FileHeader.Characteristics & IMAGE_FILE_DLL)
{
(*Module)->Flags |= LDRP_IMAGE_DLL;//Flag添加DLL属性
}
/* fixup the imported calls entry points */
Status = LdrFixupImports(SearchPath, *Module);//DLL的导入表也要修复
if (!NT_SUCCESS(Status))
{
DPRINT1("LdrFixupImports failed for %wZ, status=%xn", &(*Module)->BaseDllName, Status);
return Status;
}
#if DBG || defined(KDBG)
LdrpLoadUserModuleSymbols(*Module);//如果处于调试状态下,加载符号以方便调试
#endif /* DBG || KDBG */
RtlEnterCriticalSection(NtCurrentPeb()->LoaderLock);
InsertTailList(&NtCurrentPeb()->Ldr->InInitializationOrderModuleList,//添加到以初始化顺序的链表中
&(*Module)->InInitializationOrderModuleList);
RtlLeaveCriticalSection (NtCurrentPeb()->LoaderLock);
}
return STATUS_SUCCESS;
}
/*
LdrpMapKnownDll 先在KnownDll中查询是否有 InitializeObjectAttributes 直接NtOpenSection 看能否成功 成功后 填充下FullDosName,说明对于KnownDll,内存中是有拷贝的。
LdrpMapDllImageFile 若KnownDll中查询不到,则调用该函数,建立一个Section,其实也就是一个内存块,需要区分是否是dataFile
NtMapViewOfSection 建立完需要映射到用户空间
LdrPerformRelocations 然后就进行重定位
LdrFixupImports 修复导入表
LdrAddModuleEntry 为该模块建立一个LDR_DATA_TABLE_MODULE,并返回指向它的指针,初始化的过程中对Flags会进行相应的设置
if DBG LdrpLoadUserModuleSymbols
InsertTailList 挂入本进程的模块队列。
*/
可以看到LdrpLoadModule先通过调用LdrpMapKnownDll,查找KnownDll下的是否有我们要找的,有的话完事大吉。没有的话就要走常规流程LdrpMapDllImageFile,加载到内存。但是不论是从KnownDll中加载,还是常规加载,其根本目的是要获得SectionHandle的,以操纵这个模块,但是现在我们只是加载,但是还没有映射到我们的用户空间中,所以还需要NtMapViewOfSection,映射进来。接着就是我们很熟悉的流程,进行重定位,然后修复导入表。结束后,初始化生成一个挂接到我们的模块链表里。至此,一个LoadDll的加载就过去大半了。
下面来详细分析这几个重要的函数的实现
LdrFixupForward->LdrLoadDll->LdrpLoadModule->LdrpMapKnownDll
LdrpMapKnownDll
static NTSTATUS
LdrpMapKnownDll(IN PUNICODE_STRING DllName,
OUT PUNICODE_STRING FullDosName,
OUT PHANDLE SectionHandle)
{
OBJECT_ATTRIBUTES ObjectAttributes;
NTSTATUS Status;
DPRINT("LdrpMapKnownDll() calledn");
if (LdrpKnownDllsDirHandle == NULL)//若KnownDll目录为空 在注册表中有专门的目录会记住
{
DPRINT("Invalid 'KnownDlls' directoryn");
return STATUS_UNSUCCESSFUL;
}
DPRINT("LdrpKnownDllPath '%wZ'n", &LdrpKnownDllPath);
InitializeObjectAttributes(&ObjectAttributes,//将DllName以ObjectAttributes进行封装
DllName,
OBJ_CASE_INSENSITIVE,
LdrpKnownDllsDirHandle,//RootDirectory 这里指向注册表KnownDll的Handle
NULL);
Status = NtOpenSection(SectionHandle,//赋予读/写/执行权限 ,并得到它的句柄
SECTION_MAP_READ | SECTION_MAP_WRITE | SECTION_MAP_EXECUTE,
&ObjectAttributes);
if (!NT_SUCCESS(Status))//若失败
{
DPRINT("NtOpenSection() failed for '%wZ' (Status 0x%08lx)n", DllName, Status);
return Status;
}
//填充FullDosName结构
FullDosName->Length = LdrpKnownDllPath.Length + DllName->Length + sizeof(WCHAR);
FullDosName->MaximumLength = FullDosName->Length + sizeof(WCHAR);
FullDosName->Buffer = RtlAllocateHeap(RtlGetProcessHeap(),
0,
FullDosName->MaximumLength);
if (FullDosName->Buffer == NULL)//如果请求空间失败
{
FullDosName->Length = 0;
FullDosName->MaximumLength = 0;
return STATUS_SUCCESS;
}
//将路径信息进行拷贝
wcscpy(FullDosName->Buffer, LdrpKnownDllPath.Buffer);
wcscat(FullDosName->Buffer, L"\\");
wcscat(FullDosName->Buffer, DllName->Buffer);
DPRINT("FullDosName '%wZ'n", FullDosName);
DPRINT("LdrpMapKnownDll() donen");
return STATUS_SUCCESS;
}
/*
LdrpKnownDllsDirHandle
InitializeObjectAttributes
NtOpenSection 获得SectionHandle
*/
流程大概是判断LdrpKnownDllsDirHandle是否存在,不存在当然就没继续的必要,直接返回,一般注册表中都会在核心DLL的留下副本,防止DLL被劫持。
然后就是NtOpenSection的过程,这里其实就是遍历寻找看这个目录下有没有我们需要的DLL Handle,如果没有,那么就直接返回错误只。有的话其SectionHandle被填充,另外FullDosName也会被填充,会返回到LdrpLoadModule填充ArbitraryUserPointer,这个域是系统使用的,我们不需要管。
接着若在KnownDll中,我们就得从常规流程中加载它。
LdrFixupForward->LdrLoadDll->LdrpLoadModule->LdrpMapDllImageFile
/*
文件加载到内存
*/
static NTSTATUS
LdrpMapDllImageFile(IN PWSTR SearchPath OPTIONAL,
IN PUNICODE_STRING DllName,
OUT PUNICODE_STRING FullDosName,
IN BOOLEAN MapAsDataFile,
OUT PHANDLE SectionHandle)
{
WCHAR SearchPathBuffer[MAX_PATH];
WCHAR DosName[MAX_PATH];
UNICODE_STRING FullNtFileName;
OBJECT_ATTRIBUTES FileObjectAttributes;
HANDLE FileHandle;
char BlockBuffer [1024];
PIMAGE_DOS_HEADER DosHeader;
PIMAGE_NT_HEADERS NTHeaders;
IO_STATUS_BLOCK IoStatusBlock;
NTSTATUS Status;
ULONG len;
DPRINT("LdrpMapDllImageFile() calledn");
/*
应用程序EXE所在的路径。
当前目录(可通过SetCurrentDirectory设置,GetCurrentDirectory获取)
系统目录(通常是C:\Windows\System32,WOW64程序也会重定向到C:\Windows\SysWOW64目录,可以通过GetSystemDirectory获取)
16位系统目录(通常是,C:\Windows\System)
Windows目录(通常是,C:\Windows,可以通过GetWindowsDirectory获取)
PATH环境变量指定的目录
*/
if (SearchPath == NULL)//如果路径为空,设置默认路径,顺序是当前目录msystem32目录,系统目录,path变量
{
/* get application running path */
wcscpy (SearchPathBuffer, NtCurrentPeb()->ProcessParameters->ImagePathName.Buffer);
len = wcslen (SearchPathBuffer);
while (len && SearchPathBuffer[len - 1] != L'\\')
len--;
if (len) SearchPathBuffer[len-1] = L'\0';
wcscat (SearchPathBuffer, L";");
wcscat (SearchPathBuffer, SharedUserData->NtSystemRoot);
wcscat (SearchPathBuffer, L"\\system32;");
wcscat (SearchPathBuffer, SharedUserData->NtSystemRoot);
wcscat (SearchPathBuffer, L";.");
SearchPath = SearchPathBuffer;
}
if (RtlDosSearchPath_U (SearchPath,//判断有无该路径
DllName->Buffer,
NULL,
MAX_PATH,
DosName,
NULL) == 0)
return STATUS_DLL_NOT_FOUND;
if (!RtlDosPathNameToNtPathName_U (DosName,//转换成NT格式的路径
&FullNtFileName,
NULL,
NULL))
return STATUS_DLL_NOT_FOUND;
DPRINT("FullNtFileName %wZn", &FullNtFileName);
InitializeObjectAttributes(&FileObjectAttributes,//将NT格式的路径封装到FileObjectAttributes
&FullNtFileName,
0,
NULL,
NULL);
DPRINT("Opening dll "%wZ"n", &FullNtFileName);
Status = NtOpenFile(&FileHandle,//打开该文件 获得文件句柄
GENERIC_READ|SYNCHRONIZE,
&FileObjectAttributes,
&IoStatusBlock,
FILE_SHARE_READ,
FILE_SYNCHRONOUS_IO_NONALERT);
if (!NT_SUCCESS(Status))
{
DPRINT1("Dll open of %wZ failed: Status = 0x%08lxn",
&FullNtFileName, Status);
RtlFreeHeap (RtlGetProcessHeap (),
0,
FullNtFileName.Buffer);
return Status;
}
RtlFreeHeap (RtlGetProcessHeap (),//释放NT的unicode字符串的所占空间
0,
FullNtFileName.Buffer);
if (!MapAsDataFile)//若不是映射为数据文件
{
Status = NtReadFile(FileHandle,
NULL,
NULL,
NULL,
&IoStatusBlock,
BlockBuffer,//读取1kb的数据,这里为EXE或DLL的头部信息
sizeof(BlockBuffer),
NULL,
NULL);
if (!NT_SUCCESS(Status))
{
DPRINT("Dll header read failed: Status = 0x%08lxn", Status);
NtClose(FileHandle);
return Status;
}
/*
* Overlay DOS and NT headers structures to the
* buffer with DLL's header raw data.
*/
DosHeader = (PIMAGE_DOS_HEADER) BlockBuffer;
NTHeaders = (PIMAGE_NT_HEADERS) (BlockBuffer + DosHeader->e_lfanew);
/*
* Check it is a PE image file.
*/
if ((DosHeader->e_magic != IMAGE_DOS_SIGNATURE)//判断是否是PE格式
|| (DosHeader->e_lfanew == 0L)
|| (*(PULONG)(NTHeaders) != IMAGE_NT_SIGNATURE))
{
DPRINT("NTDLL format invalidn");
NtClose(FileHandle);
return STATUS_UNSUCCESSFUL;
}
}
/*
1. Create a section for dll.
*/
Status = NtCreateSection(SectionHandle,//分配一块内存块
SECTION_ALL_ACCESS,
NULL,
NULL,
PAGE_READONLY,
MapAsDataFile ? SEC_COMMIT : SEC_IMAGE,//区域状态变成映像,当一般数据文件被映射进了进程空间后,区域状态变成映射。
FileHandle);
NtClose(FileHandle);
if (!NT_SUCCESS(Status))
{
DPRINT("NTDLL create section failed: Status = 0x%08lxn", Status);
return Status;
}
RtlCreateUnicodeString(FullDosName,
DosName);
return Status;
}
/*
SearchPath的设置
RtlDosSearchPath_U
RtlDosPathNameToNtPathName_U
InitializeObjectAttributes
NtOpenFile
判断是否是MapAsDataFile 若是 读取1KB大小的信息判断其是否是PE格式
NtCreateSection 对于数据文件设置为SEC_COMMIT,对于映像文件,设置为SEC_IMAGE
*/
LdrpMapDllImageFile主要流程是先设置SearchPath,在这里,我们也不难能发觉DLL是先从当前目录开始搜索,这给我们进行DLL劫持提供了理论基础,然后调用RtlDosSearchPath_U按给定的的搜寻该路径寻找是否真实存在该文件,若存在,则调用RtlDosPathNameToNtPathName_U转换为NT路径,供InitializeObjectAttributes包装,以方便NtOpenFile,从而获取了该文件的句柄。对于DLL文件而言,我们先进行读文件操作,这里是调用了NtReadFile函数,来读取它到我们的buffer里,读取的大小是1kb,目的是为了检测是否是PE文件格式,若是的话,那么就说明它确实是一个DLL文件无疑了。这个时候才会通过 NtCreateSection真正的把它加载进内存里。
这个将DLL加载进用户空间并挂钩到我们的EXE模块的过程就结束了,调用链如下。
LdrFixupForward->LdrLoadDll->LdrpLoadModule->LdrpMapDllImageFile/LdrpKnownDllsDirHandle->NtMapViewOfSection->LdrPerformRelocations->LdrFixupImports
接下来我们看LdrpAttachProcess对DLL的初始化过程
/**********************************************************************
* NAME LOCAL
* LdrpAttachProcess
* DESCRIPTION
* Initialize all dll's which are prepered for loading
* ARGUMENTS
* none
* RETURN VALUE
* status
* REVISIONS
*
* NOTE
* The loader lock must be held on entry.
*/
static NTSTATUS
LdrpAttachProcess(VOID)
{
PLIST_ENTRY ModuleListHead;
PLIST_ENTRY Entry;
PLDR_DATA_TABLE_ENTRY Module;
BOOLEAN Result;
NTSTATUS Status = STATUS_SUCCESS;
DPRINT("LdrpAttachProcess() called for %wZn",//打印EXE模块的名字
&ExeModule->BaseDllName);
ModuleListHead = &NtCurrentPeb()->Ldr->InInitializationOrderModuleList;//获得LIST_ENTRY的指针。
Entry = ModuleListHead->Flink;//获得第一个PLDR_DATA_TABLE_ENTRY
while (Entry != ModuleListHead)//遍历
{
Module = CONTAINING_RECORD(Entry, LDR_DATA_TABLE_ENTRY, InInitializationOrderModuleList);//获得模块基址
if (!(Module->Flags & (LDRP_LOAD_IN_PROGRESS|LDRP_UNLOAD_IN_PROGRESS|LDRP_ENTRY_PROCESSED)))//若未设置这些标志位
{
Module->Flags |= LDRP_LOAD_IN_PROGRESS;//设置标志位,表示正处于PROCESS加载阶段
TRACE_LDR("%wZ loaded - Calling init routine at %x for process attachingn",//打印初始化的DLL名和它的入口点
&Module->BaseDllName, Module->EntryPoint);
Result = LdrpCallDllEntry(Module, DLL_PROCESS_ATTACH, (PVOID)(Module->LoadCount == LDRP_PROCESS_CREATION_TIME ? 1 : 0));//熟悉的DLL_PROCESS_ATTACH,表示进程被加载时所调用
if (!Result)//返回值要为0才对,即DllMain的返回值
{
Status = STATUS_DLL_INIT_FAILED;
break;
}
if (Module->Flags & LDRP_IMAGE_DLL && Module->EntryPoint != 0)//若是DLL映像 另外加上标志位
{
Module->Flags |= LDRP_PROCESS_ATTACH_CALLED|LDRP_ENTRY_PROCESSED;//设置成DLL_PROCESS_ATTACH被调用过了 并且设置入口被处理的标志
}
else
{
Module->Flags |= LDRP_ENTRY_PROCESSED;
}
Module->Flags &= ~LDRP_LOAD_IN_PROGRESS;//设置标志位,表示加载阶段结束,现在应该是PROCESS_ATTACH_CALLED了
}
Entry = Entry->Flink;//遍历下一个
}
DPRINT("LdrpAttachProcess() donen");
return Status;
}
/*
InInitializationOrderModuleList遍历
设置 LDRP_LOAD_IN_PROGRESS
LdrpCallDllEntry
LDRP_PROCESS_ATTACH_CALLED LDRP_ENTRY_PROCESSED当程序再次调用该DLL时,就不会再进行初始化
*/
LdrAttachProcess的过程实际上就是调用LdrpCallDllEntry,对每一个未初始化过的模块进行初始化调用并设置相关的标志位的过程。细节方面上大概是先检查LDRP_LOAD_IN_PROGRESS|LDRP_UNLOAD_IN_PROGRESS|LDRP_ENTRY_PROCESSED标志位,这些标志位的含义是 在创建过程中;在卸载过程中;以被初始化过了,而这些标志位正常来说是不会被设置的。然后进入后会设置LDRP_LOAD_IN_PROGRESS表示现在在创建的过程中,接着就会调用真正的初始化函数LdrpCallDllEntry。成功后会设置LDRP_PROCESS_ATTACH_CALLED及LDRP_ENTRY_PROCESSED,表示attach process成功执行。
LdrFixupForward->LdrLoadDll->LdrpLoadModule->LdrAttachProcess->LdrpCallDllEntry
static BOOLEAN LdrpCallDllEntry(PLDR_DATA_TABLE_ENTRY Module, DWORD dwReason, PVOID lpReserved)
{
if (!(Module->Flags & LDRP_IMAGE_DLL) ||//若不是DLL映像或者无入口点,直接返回
Module->EntryPoint == 0)
{
return TRUE;
}
LdrpTlsCallback(Module, dwReason);
return ((PDLLMAIN_FUNC)Module->EntryPoint)(Module->DllBase, dwReason, lpReserved);//调用DllMain dwReason是DLL_PROCESS_ATTACH
}
LdrpCallDllEntry做了两件事,一件是调用LdrpTlsCallback函数调用该模块的Tls回调函数(如果有的话),第二件事则是调用DllMain,传入的dwReason是DLL_PROCESS_ATTACHED,执行位置是模块的EntryPoint处(EntryPoint的设置是在LdrAdd)。
关于LdrpTlsCallback,留到下次再讲。对于LoadDll,它的作用就是加载给定的DllName指定的DLL,和它的依赖DLL,并初始化其Tls和DllMain的过程。