本篇主要讲述进程的启动过程、线程的调度与切换、进程挂靠
一、进程的启动过程:
BOOL CreateProcess
(
LPCTSTR lpApplicationName, //
LPTSTR lpCommandLine, // command line string
LPSECURITY_ATTRIBUTES lpProcessAttributes, // SD
LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD
BOOL bInheritHandles, //
DWORD dwCreationFlags, // creation flags
LPVOID lpEnvironment, // new environment block
LPCTSTR lpCurrentDirectory, // current directory name
LPSTARTUPINFO lpStartupInfo, // startup information
LPPROCESS_INFORMATION lpProcessInformation // process information
);
这个Win32API在内部最终调用如下:
CreateProcess(…)
{
…
NtCreateProcess(…);//间接调用这个系统服务,先创建进程
NtCreateThread(…);//间接调用这个系统服务,再创建该进程的第一个线程(也即主线程)
…
}
进程的4GB地址空间分两部分,内核空间+用户空间
看下面几个定义:
#define MmSystemRangeStart 0x80000000 //系统空间的起点
#define MM_USER_PROB_ADDRESS MmSystemRangeStart-64kb //除去高端的64kb隔离区
#define MM_HIGHEST_USER_ADDRESS MmUserProbAddress-1 //实际的用户空间中最高可访问地址
#define MM_LOWEST_USER_ADDRESS 64kb //实际的用户空间中最低可访问地址
#define KI_USER_SHARED_DATA 0xffdf0000 //内核空间与用户空间共享的一块区域
由此可见,用户地址空间的范围实际上是从 64kb---->0x80000000-64kb 这块区域。
(访问NULL指针报异常的原因就是NULL(0)落在了最前面的64kb保留区中)
内核中提供了一个全局结构变量,该结构的类型是KUSER_SHARED_DATA。内核中的那个结构体变量所在的虚拟页面起始地址为:0xffdf0000,大小为一个页面大小。这个内核页面对应的物理内存页面也映射到了每个进程的用户地址空间中,而且是固定映在同一处:0x7ffe0000。这样,用户空间的程序直接访问用户空间中的这个虚拟地址,就相当于直接访问了内核空间中的那个公共页面。所以,那个内核页面称之为内核空间提供给各个进程的一块共享之地。(事实上,这个公共页面非常有用,可以在这个页面中放置代码,应用程序直接在r3层运行这些代码,如在内核中进行IAT hook)
如上:【用户空间的范围就是低2GB的空间除去前后64kb后的那块区域】
圈定了用户空间的地皮后,现在就到了划分用户空间的时候了。
用户空间的布局:(以区段(Area)为单位进行划分)
NTSTATUS
MmInitializeProcessAddressSpace(IN PEPROCESS Process,IN PVOID Section,IN OUT PULONG Flags)
{
NTSTATUS Status = STATUS_SUCCESS;
SIZE_T ViewSize = 0;
PVOID ImageBase = 0;
PROS_SECTION_OBJECT SectionObject = Section;
USHORT Length = 0;
…
KeAttachProcess(&Process->Pcb);//必须将当前线程挂靠到子进程的地址空间
Process->AddressSpaceInitialized = 2;
if (SectionObject)
{
FileName = SectionObject->FileObject->FileName;
Source = (PWCHAR)((PCHAR)FileName.Buffer + FileName.Length);
if (FileName.Buffer)
{
while (Source > FileName.Buffer)
{
if (*--Source ==L”\\”)
{
Source++;
break;
}
else
Length++;
}
}
Destination = Process->ImageFileName;//任务管理器显示的进程名就是这个(大小写相同)
Length = min(Length, sizeof(Process->ImageFileName) - 1);
while (Length--) *Destination++ = (UCHAR)*Source++;
*Destination = ’\0’;
//将进程的exe文件映射到地址空间中
Status = MmMapViewOfSection(Section,Process,&ImageBase,0,0,NULL,&ViewSize,0,
MEM_COMMIT,…);
Process->SectionBaseAddress = ImageBase;//记录实际映射到的地址(一般为0x00400000)
}
KeDetachProcess();//撤销挂靠
return Status;
}
//上面的函数将进程的exe文件映射到用户地址空间中(注意exe文件内部是分开按节映射)
NTSTATUS PspMapSystemDll(PEPROCESS Process,PVOID *DllBase,BOOLEAN UseLargePages)
{
LARGE_INTEGER Offset = {{0, 0}};
SIZE_T ViewSize = 0; PVOID ImageBase = 0;
//将NTDLL.dll文件映射到地址空间(每个NTDLL.dll事实上都映射到所有进程地址空间的同一处)
Status = MmMapViewOfSection(PspSystemDllSection,Process,&ImageBase,0,0,&Offset,&ViewSize,
ViewShare,0,…);
if (DllBase) *DllBase = ImageBase;
return Status;
}
上面这个函数将ntdll.dll映射到地址空间
下面这个函数创建该进程的PEB(Process Environment Block)
NTSTATUS MmCreatePeb(PEPROCESS Process,PINITIAL_PEB InitialPeb,OUT PPEB *BasePeb)
{
PPEB Peb = NULL;
SIZE_T ViewSize = 0;
PVOID TableBase = NULL;
KAFFINITY ProcessAffinityMask = 0;
SectionOffset.QuadPart = (ULONGLONG)0;
*BasePeb = NULL;
KeAttachProcess(&Process->Pcb);//因为PEB指针是子进程中的地址,所以要挂靠
//创建一个PEB
Status = MiCreatePebOrTeb(Process, sizeof(PEB), (PULONG_PTR)&Peb);
RtlZeroMemory(Peb, sizeof(PEB));
//根据传入的InitialPeb参数初始化新建的peb
Peb->InheritedAddressSpace = InitialPeb->InheritedAddressSpace;
Peb->Mutant = InitialPeb->Mutant;
Peb->ImageUsesLargePages = InitialPeb->ImageUsesLargePages;
Peb->ImageBaseAddress = Process->SectionBaseAddress;//
Peb->OSMajorVersion = NtMajorVersion; Peb->OSMinorVersion = NtMinorVersion;
Peb->OSBuildNumber = (USHORT)(NtBuildNumber & 0x3FFF);
Peb->OSPlatformId = 2; /* VER_PLATFORM_WIN32_NT */
Peb->OSCSDVersion = (USHORT)CmNtCSDVersion;
Peb->NumberOfProcessors = KeNumberProcessors;
//经典的两个调试检测标志
Peb->BeingDebugged = (BOOLEAN)(Process->DebugPort != NULL ? TRUE : FALSE);
Peb->NtGlobalFlag = NtGlobalFlag;
Peb->MaximumNumberOfHeaps = (PAGE_SIZE - sizeof(PEB)) / sizeof(PVOID);
Peb->ProcessHeaps = (PVOID*)(Peb + 1);//PEB结构体后面是一个堆数组
NtHeaders = RtlImageNtHeader(Peb->ImageBaseAddress);//获取文件头中的NT头
Characteristics = NtHeaders->FileHeader.Characteristics;
if (NtHeaders)
{
_SEH2_TRY
{
ImageConfigData = RtlImageDirectoryEntryToData(Peb->ImageBaseAddress,TRUE,
IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG,&ViewSize);
Peb->ImageSubsystem = NtHeaders->OptionalHeader.Subsystem;
Peb->ImageSubsystemMajorVersion = NtHeaders->OptionalHeader.MajorSubsystemVersion;
Peb->ImageSubsystemMinorVersion = NtHeaders->OptionalHeader.MinorSubsystemVersion;
if (NtHeaders->OptionalHeader.Win32VersionValue)
{
Peb->OSMajorVersion = NtHeaders->OptionalHeader.Win32VersionValue & 0xFF;
Peb->OSMinorVersion = (NtHeaders->OptionalHeader.Win32VersionValue >> 8) & 0xFF;
Peb->OSBuildNumber = (NtHeaders->OptionalHeader.Win32VersionValue >> 16) & 0x3FFF;
Peb->OSPlatformId = (NtHeaders->OptionalHeader.Win32VersionValue >> 30) ^ 2;
}
if (ImageConfigData != NULL)
{
ProbeForRead(ImageConfigData,sizeof(IMAGE_LOAD_CONFIG_DIRECTORY),
sizeof(ULONG));//读取pe文件中的加载配置信息
if (ImageConfigData->CSDVersion)
Peb->OSCSDVersion = ImageConfigData->CSDVersion;
if (ImageConfigData->ProcessAffinityMask)
ProcessAffinityMask = ImageConfigData->ProcessAffinityMask;
}
if (Characteristics & IMAGE_FILE_UP_SYSTEM_ONLY)
Peb->ImageProcessAffinityMask = 0;
else
Peb->ImageProcessAffinityMask = ProcessAffinityMask;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
KeDetachProcess();
_SEH2_YIELD(return STATUS_INVALID_IMAGE_PROTECT);
}
_SEH2_END;
}
KeDetachProcess();
*BasePeb = Peb;
return STATUS_SUCCESS;
}
如上,上面这个函数为进程创建一个PEB并根据exe文件头中的某些信息初始化里面的某些字段
事实上,这个PEB结构体的地址固定安排在0x7FFDF000处,占据一个页面大小。该页中这个PEB结构体后面就是一个堆数组,存放该进程中创建的所有堆。
下面的函数为子进程分配一个参数块(即创建参数)和环境变量块(即环境变量字符串)
NTSTATUS
BasepInitializeEnvironment(HANDLE ProcessHandle,PPEB Peb,//子进程的Peb
LPWSTR ApplicationPathName,LPWSTR lpCurrentDirectory,
LPWSTR lpCommandLine,
LPVOID lpEnvironment,//传给子进程的环境变量块
SIZE_T EnvSize,//环境变量块的大小
LPSTARTUPINFOW StartupInfo,DWORD CreationFlags,
BOOL InheritHandles)
{
PRTL_USER_PROCESS_PARAMETERS RemoteParameters = NULL;
PPEB OurPeb = NtCurrentPeb();//当前进程(即父进程)的Peb
LPVOID Environment = lpEnvironment;
RetVal = GetFullPathNameW(ApplicationPathName, MAX_PATH,FullPath,&Remaining);
RtlInitUnicodeString(&ImageName, FullPath);
RtlInitUnicodeString(&CommandLine, lpCommandLine);
RtlInitUnicodeString(&CurrentDirectory, lpCurrentDirectory);
if (StartupInfo->lpTitle)
RtlInitUnicodeString(&Title, StartupInfo->lpTitle);
else
RtlInitUnicodeString(&Title, L"");
Status = RtlCreateProcessParameters(&ProcessParameters,&ImageName,
lpCurrentDirectory ?&CurrentDirectory : NULL,
&CommandLine,Environment,&Title,..);
if (Environment)
Environment = ScanChar = ProcessParameters->Environment;
else
Environment = ScanChar = OurPeb->ProcessParameters->Environment;
if (ScanChar)
{
EnviroSize =CalcEnvSize(ScanChar);//计算环境变量块的长度
Size = EnviroSize;
//为子进程分配一个环境变量块(跨进程远程分配内存)
Status = ZwAllocateVirtualMemory(ProcessHandle,
(PVOID*)&ProcessParameters->Environment,
0,&Size,MEM_COMMIT,PAGE_READWRITE);
//将环境变量块复制到子进程的空间中
ZwWriteVirtualMemory(ProcessHandle,ProcessParameters->Environment,
Environment,
EnviroSize,
NULL);
}
ProcessParameters->StartingX = StartupInfo->dwX;
ProcessParameters->StartingY = StartupInfo->dwY;
ProcessParameters->CountX = StartupInfo->dwXSize;
ProcessParameters->CountY = StartupInfo->dwYSize;
ProcessParameters->CountCharsX = StartupInfo->dwXCountChars;
ProcessParameters->CountCharsY = StartupInfo->dwYCountChars;
ProcessParameters->FillAttribute = StartupInfo->dwFillAttribute;
ProcessParameters->WindowFlags = StartupInfo->dwFlags;
ProcessParameters->ShowWindowFlags = StartupInfo->wShowWindow;
if (StartupInfo->dwFlags & STARTF_USESTDHANDLES)//让子进程使用自定义的三个标准IO句柄
{ //经常用于匿名管道重定向
ProcessParameters->StandardInput = StartupInfo->hStdInput;
ProcessParameters->StandardOutput = StartupInfo->hStdOutput;
ProcessParameters->StandardError = StartupInfo->hStdError;
}
if (CreationFlags & DETACHED_PROCESS)
ProcessParameters->ConsoleHandle = HANDLE_DETACHED_PROCESS;
else if (CreationFlags & CREATE_NO_WINDOW)
ProcessParameters->ConsoleHandle = HANDLE_CREATE_NO_WINDOW;
else if (CreationFlags & CREATE_NEW_CONSOLE)
ProcessParameters->ConsoleHandle = HANDLE_CREATE_NEW_CONSOLE;
else
{
//让子进程继承父进程的控制台句柄
ProcessParameters->ConsoleHandle = OurPeb->ProcessParameters->ConsoleHandle;
//让子进程继承父进程的三个标准句柄
if (!(StartupInfo->dwFlags &
(STARTF_USESTDHANDLES | STARTF_USEHOTKEY | STARTF_SHELLPRIVATE)))
{
BasepCopyHandles(ProcessParameters,OurPeb->ProcessParameters,InheritHandles);
}
}
Size = ProcessParameters->Length;//参数块本身的长度
//在子进程中分配一个参数块
Status = NtAllocateVirtualMemory(ProcessHandle,&RemoteParameters,0,&Size,
MEM_COMMIT,PAGE_READWRITE);
ProcessParameters->MaximumLength = Size;
//在子进程中分配一个参数块
Status = NtWriteVirtualMemory(ProcessHandle,RemoteParameters,ProcessParameters,
ProcessParameters->Length,NULL);
//将参数块复制到子进程的地址空间中
Status = NtWriteVirtualMemory(ProcessHandle,
&Peb->ProcessParameters,
&RemoteParameters,
sizeof(PVOID),
NULL);
RtlDestroyProcessParameters(ProcessParameters);
return STATUS_SUCCESS;
}
下面的函数创建第一个线程的(用户栈、内核栈、初始内核栈帧)
HANDLE
BasepCreateFirstThread(HANDLE ProcessHandle,LPSECURITY_ATTRIBUTES lpThreadAttributes,
PSECTION_IMAGE_INFORMATION SectionImageInfo,PCLIENT_ID ClientId)
{
BasepCreateStack(ProcessHandle,
SectionImageInfo->MaximumStackSize,//默认为1MB
SectionImageInfo->CommittedStackSize,//默认为4kb
&InitialTeb);//创建(即分配)该线程的用户栈
BasepInitializeContext(&Context,
NtCurrentPeb(),//赋给context.ebx
SectionImageInfo->TransferAddress,//赋给context.eax(也即oep)
InitialTeb.StackBase,// 赋给context.esp
0);//0表示是主线程的用户空间总入口
ObjectAttributes = BasepConvertObjectAttributes(&LocalObjectAttributes,
lpThreadAttributes,NULL);
Status = NtCreateThread(&hThread,THREAD_ALL_ACCESS,ObjectAttributes,ProcessHandle,
ClientId,&Context,&InitialTeb,TRUE);
Status = BasepNotifyCsrOfThread(hThread, ClientId);//通知csrss进程线程创建通知
return hThread;
}
下面的函数用来分配一个用户栈(每个线程都要分配一个)【栈底、栈顶、提交界】
NTSTATUS
BasepCreateStack(HANDLE hProcess,
SIZE_T StackReserve,//栈的保留大小。默认为1MB
SIZE_T StackCommit,//初始提交大小。默认为4KB,一个页面
OUT PINITIAL_TEB InitialTeb)//用来构造初始teb
{
ULONG_PTR Stack = NULL;
BOOLEAN UseGuard = FALSE;
Status = NtQuerySystemInformation(SystemBasicInformation,&SystemBasicInfo,
sizeof(SYSTEM_BASIC_INFORMATION),NULL);
if (hProcess == NtCurrentProcess())
{
Headers = RtlImageNtHeader(NtCurrentPeb()->ImageBaseAddress);
StackReserve = (StackReserve) ?
StackReserve : Headers->OptionalHeader.SizeOfStackReserve;
StackCommit = (StackCommit) ?
StackCommit : Headers->OptionalHeader.SizeOfStackCommit;
}
else
{
StackReserve = (StackReserve) ? StackReserve :SystemBasicInfo.AllocationGranularity;
StackCommit = (StackCommit) ? StackCommit : SystemBasicInfo.PageSize;
}
//栈的区段长度对齐64kb
StackReserve = ROUND_UP(StackReserve, SystemBasicInfo.AllocationGranularity);
StackCommit = ROUND_UP(StackCommit, SystemBasicInfo.PageSize);
//预定这么大小的栈(默认1MB)
Status = ZwAllocateVirtualMemory(hProcess, (PVOID*)&Stack,0,&StackReserve,MEM_RESERVE,
PAGE_READWRITE);
InitialTeb->AllocatedStackBase = (PVOID)Stack;//栈区段的分配基址
InitialTeb->StackBase = (PVOID)(Stack + StackReserve);//栈底
Stack += StackReserve - StackCommit;
if (StackReserve > StackCommit)
{
UseGuard = TRUE;
Stack -= SystemBasicInfo.PageSize;
StackCommit += SystemBasicInfo.PageSize; //多提交一个保护页
}
//初始提交这么大小的页面(也就是最常见的一个页外加一个保护页的大小)
Status = ZwAllocateVirtualMemory(hProcess, (PVOID*)&Stack,0,&StackCommit,MEM_COMMIT,
PAGE_READWRITE);
InitialTeb->StackLimit = (PVOID)Stack;// StackLimit表示第一个尚未提交页的边界
if (UseGuard)
{
SIZE_T GuardPageSize = SystemBasicInfo.PageSize;
Status = ZwProtectVirtualMemory(hProcess, (PVOID*)&Stack,&GuardPageSize,
PAGE_GUARD | PAGE_READWRITE);//改为PAGE_GUARD属性
InitialTeb->StackLimit = (PVOID)((ULONG_PTR)InitialTeb->StackLimit - GuardPageSize);
}
return STATUS_SUCCESS;
}
下面这个函数构造该线程的初始寄存器上下文
VOID
BasepInitializeContext(IN PCONTEXT Context,IN PVOID Parameter,IN PVOID StartAddress,
IN PVOID StackAddress,IN ULONG ContextType)
{
Context->Eax = (ULONG)StartAddress;//oep或用户指定的线程入口函数
Context->Ebx = (ULONG)Parameter;//peb
Context->Esp = (ULONG)StackAddress;//栈底就是初始栈顶
Context->SegFs = KGDT_R3_TEB | RPL_MASK;//fs指向TEB
Context->SegEs = KGDT_R3_DATA | RPL_MASK;
Context->SegDs = KGDT_R3_DATA | RPL_MASK;
Context->SegCs = KGDT_R3_CODE | RPL_MASK;
Context->SegSs = KGDT_R3_DATA | RPL_MASK;
Context->SegGs = 0;
Context->EFlags = 0x3000; // IOPL 3
if (ContextType == 1)
Context->Eip = (ULONG)BaseThreadStartupThunk; //普通线程的用户空间总入口
else if (ContextType == 2) //纤程
Context->Eip = (ULONG)BaseFiberStartup;
else
Context->Eip = (ULONG)BaseProcessStartThunk; //主线程的用户空间总入口
Context->ContextFlags = CONTEXT_FULL;//所有字段全部有效
Context->Esp -= sizeof(PVOID);//腾出参数空间
}
当线程创建起来后,会紧跟着创建它的teb。现在暂时不看NtCreateThread是怎样实现的,看一下teb的创建过程。
NTSTATUS
MmCreateTeb(IN PEPROCESS Process,
IN PCLIENT_ID ClientId,//线程的客户id即【进程id.线程id】
IN PINITIAL_TEB InitialTeb,
OUT PTEB *BaseTeb)//返回teb的地址
{
NTSTATUS Status = STATUS_SUCCESS;
*BaseTeb = NULL;
KeAttachProcess(&Process->Pcb);//挂靠到子进程地址空间
Status = MiCreatePebOrTeb(Process, sizeof(TEB), (PULONG_PTR)&Teb);//从peb处往低地址端搜索
_SEH2_TRY
{
RtlZeroMemory(Teb, sizeof(TEB));
Teb->NtTib.ExceptionList = -1;//初始是没有seh
Teb->NtTib.Self = (PNT_TIB)Teb;//将self指向指针结构的地址,方便寻址
Teb->NtTib.Version = 30 << 8;
Teb->ClientId = *ClientId;
Teb->RealClientId = *ClientId;
Teb->ProcessEnvironmentBlock = Process->Peb;//关键,teb中有个指针指向peb
Teb->CurrentLocale = PsDefaultThreadLocaleId;
if ((InitialTeb->PreviousStackBase == NULL) &&
(InitialTeb->PreviousStackLimit == NULL))
{
Teb->NtTib.StackBase = InitialTeb->StackBase;//栈底
Teb->NtTib.StackLimit = InitialTeb->StackLimit;//提交边界(最近未提交页的地址)
Teb->DeallocationStack = InitialTeb->AllocatedStackBase;
}
else
{
Teb->NtTib.StackBase = InitialTeb->PreviousStackBase;
Teb->NtTib.StackLimit = InitialTeb->PreviousStackLimit;
}
Teb->StaticUnicodeString.MaximumLength = sizeof(Teb->StaticUnicodeBuffer);
Teb->StaticUnicodeString.Buffer = Teb->StaticUnicodeBuffer;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
KeDetachProcess();
*BaseTeb = Teb;
return Status;
}
这样,经过以上的操作后,进程用户空间的典型布局就定出来了。
----------------------------------------------------------------------------------------->
64kb 64kb 64kb 一般1MB 一般在0x00400000处 n*4kb 4kb 4kb
禁区|环境变量块|参数块|主线程的栈|其它空间|exe文件各个节|其他空间|各teb|peb|内核用户共享区|
--------------------------->
60kb 64kb 0x80000000开始
无效区|隔离区|系统空间…
用户空间的布局:一句口诀【环、参、栈、文、堆、t、p】
进程的创建:
NTSTATUS
NtCreateProcess(OUT PHANDLE ProcessHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN HANDLE ParentProcess,
IN BOOLEAN InheritObjectTable,
IN HANDLE SectionHandle OPTIONAL,
IN HANDLE DebugPort OPTIONAL,
IN HANDLE ExceptionPort OPTIONAL)
{
ULONG Flags = 0;
if ((ULONG)SectionHandle & 1) Flags = PS_REQUEST_BREAKAWAY;
if ((ULONG)DebugPort & 1) Flags |= PS_NO_DEBUG_INHERIT;
if (InheritObjectTable) Flags |= PS_INHERIT_HANDLES;
return NtCreateProcessEx(ProcessHandle,
DesiredAccess,
ObjectAttributes,
ParentProcess,
Flags,
SectionHandle,
DebugPort,
ExceptionPort,
FALSE);
}
NTSTATUS
NtCreateProcessEx(OUT PHANDLE ProcessHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN HANDLE ParentProcess,
IN ULONG Flags,
IN HANDLE SectionHandle OPTIONAL,
IN HANDLE DebugPort OPTIONAL,
IN HANDLE ExceptionPort OPTIONAL,
IN BOOLEAN InJob)
{
if (!ParentProcess)
Status = STATUS_INVALID_PARAMETER;
else
{
Status = PspCreateProcess(ProcessHandle,
DesiredAccess,
ObjectAttributes,
ParentProcess,
Flags,
SectionHandle,
DebugPort,
ExceptionPort,
InJob);
}
return Status;
}
如上,CreateProcess API调用NtCreateProcess系统服务,最终会调用下面的函数完成进程的创建工作
NTSTATUS
PspCreateProcess(OUT PHANDLE ProcessHandle,//返回子进程的句柄
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN HANDLE ParentProcess OPTIONAL,//父进程可以是任意第三方进程
IN ULONG Flags,
IN HANDLE SectionHandle OPTIONAL,//exe文件的section对象
IN HANDLE DebugPort OPTIONAL,//调试器进程中某个线程的调试端口
IN HANDLE ExceptionPort OPTIONAL)
{
ULONG DirectoryTableBase[2] = {0,0};//为子进程分配的页目录所在的物理页面地址
PETHREAD CurrentThread = PsGetCurrentThread();
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
PEPROCESS CurrentProcess = PsGetCurrentProcess();
PACCESS_STATE AccessState = &LocalAccessState;//记录着当前线程的令牌和申请的访问权限
BOOLEAN NeedsPeb = FALSE;//表示是否需要为其创建一个peb,绝大多数都要
if (ParentProcess)//事实上只有”system”进程没有父进程
{
Status = ObReferenceObjectByHandle(ParentProcess,
PROCESS_CREATE_PROCESS,//表示要为其创建子进程
PsProcessType,PreviousMode, (PVOID*)&Parent);
Affinity = Parent->Pcb.Affinity;//继承父进程的cpu亲缘性
}
else
{
Parent = NULL;
Affinity = KeActiveProcessors;
}
MinWs = PsMinimumWorkingSet;
MaxWs = PsMaximumWorkingSet;
//关键。创建该进程的内核对象结构
Status = ObCreateObject(PreviousMode,
PsProcessType,//进程对象类型
ObjectAttributes,PreviousMode,NULL,
sizeof(EPROCESS),//内核进程对象
0,0, (PVOID*)&Process);
RtlZeroMemory(Process, sizeof(EPROCESS));
InitializeListHead(&Process->ThreadListHead);//初始时该进程尚无任何线程
PspInheritQuota(Process, Parent);//继承父进程的资源配额块
ObInheritDeviceMap(Parent, Process);//继承父进程的磁盘卷设备位图
if (Parent)
Process->InheritedFromUniqueProcessId = Parent->UniqueProcessId;//记录父进程的pid
if (SectionHandle)//exe文件的section,一般都有
{
//获得对应的section对象
Status = ObReferenceObjectByHandle(SectionHandle,SECTION_MAP_EXECUTE,
MmSectionObjectType,PreviousMode,
(PVOID*)&SectionObject);
}
Else …
Process->SectionObject = SectionObject;//记录该进程的exe文件section
if (DebugPort)//由调试器启动的子进程,都会传递一个调试端口给子进程
{
Status = ObReferenceObjectByHandle(DebugPort,
DEBUG_OBJECT_ADD_REMOVE_PROCESS,
DbgkDebugObjectType,PreviousMode,
(PVOID*)&DebugObject);
//每个被调进程与调试器中的一个调试器线程通过一个调试端口连接,形成一个调试会话
Process->DebugPort = DebugObject; //可用于检测调试
if (Flags & PS_NO_DEBUG_INHERIT)//指示不可将调试端口再继承给它的子进程
InterlockedOr((PLONG)&Process->Flags, PSF_NO_DEBUG_INHERIT_BIT);
}
else
{
if (Parent)
DbgkCopyProcessDebugPort(Process, Parent);//继承父进程的调试端口
}
if (ExceptionPort)
{
Status = ObReferenceObjectByHandle(ExceptionPort,PORT_ALL_ACCESS,LpcPortObjectType,
PreviousMode, (PVOID*)&ExceptionPortObject);
Process->ExceptionPort = ExceptionPortObject;
}
Process->ExitStatus = STATUS_PENDING;//默认的退出码
if (Parent)
{
/*创建页目录和内核部分的页表,然后从系统公共的内核页表中复制内核空间中的那些页表项(这样,每个进程的内核地//址空间的映射就相同了)*/
MmCreateProcessAddressSpace(MinWs,Process,DirectoryTableBase)
}
Else …
InterlockedOr((PLONG)&Process->Flags, PSF_HAS_ADDRESS_SPACE_BIT);
Process->Vm.MaximumWorkingSetSize = MaxWs;
//初始化进程对象的内部结构成员
KeInitializeProcess(&Process->Pcb,PROCESS_PRIORITY_NORMAL,Affinity,DirectoryTableBase);
Status = PspInitializeProcessSecurity(Process, Parent);//继承父进程的令牌
Process->PriorityClass = PROCESS_PRIORITY_CLASS_NORMAL;//初始创建时都是普通优先级类
Status = STATUS_SUCCESS;
if (SectionHandle) //一般都有
{
//初始化地址空间并将exe文件映射到用户空间中
Status = MmInitializeProcessAddressSpace(Process,SectionObject,&Flags,ImageFileName);
NeedsPeb = TRUE;
}
Else …
if (SectionObject)//映射(即加载)exe文件后,再映射ntdll.dll到用户空间(事实上固定映到某处)
PspMapSystemDll(Process, NULL, FALSE);
CidEntry.Object = Process;
CidEntry.GrantedAccess = 0;
//进程id、线程id实际上都是全局PspCidTable句柄表中的句柄,他们也指向对应的对象
Process->UniqueProcessId = ExCreateHandle(PspCidTable, &CidEntry);//分配pid进程号
Process->ObjectTable->UniqueProcessId = Process->UniqueProcessId;
if ((Parent) && (NeedsPeb))//用户空间中的进程都会分配一个peb,且固定在某处
{
RtlZeroMemory(&InitialPeb, sizeof(INITIAL_PEB));
InitialPeb.Mutant = (HANDLE)-1;
if (SectionHandle)
Status = MmCreatePeb(Process, &InitialPeb, &Process->Peb);//创建peb(固定在某处)
Else …
}
/*将进程加入全局的“活动进程链表”中,这个链表仅供系统统计用,因此可以恣意篡改,如隐藏进程。任务管理器等其他绝大多数进程枚举工具内部就是遍历的这个进程链表*/
InsertTailList(&PsActiveProcessHead, &Process->ActiveProcessLinks);
//这个函数用来将进程对象插入句柄表,返回一个进程句柄
Status = ObInsertObject(Process,AccessState,DesiredAccess,1,NULL,&hProcess);
//根据进程的优先级类计算该进程的基本优先级和时间片(初始创建时作为后台进程)
Process->Pcb.BasePriority =PspComputeQuantumAndPriority(Process,
PsProcessPriorityBackground,&Quantum);
Process->Pcb.QuantumReset = Quantum;
KeQuerySystemTime(&Process->CreateTime);//记录进程的创建时间
PspRunCreateProcessNotifyRoutines(Process, TRUE);//发出一个进程创建通知消息
*ProcessHandle = hProcess;//返回对应的句柄
return Status;
}
上面的NtCreateProcess、PspCreateProces只是创建了一个进程(它的内核对象、地址空间等),进程本身是不能运行的,所以CreateProcess API最终还会调用NtCreateThread创建并启动主线程。
线程从运行空间角度看,分为两种线程:
1、 用户线程(主线程和CreateThread创建的普通线程都是用户线程):线程部分代码运行在用户空间
2、 内核线程(由驱动程序调用PsCreateSystemThread创建的线程):线程的所有代码运行在内核空间
两种线程的运行路径分别为:
1、 KiThreadStartup->PspUserThreadStartup->用户空间中的公共入口->映像文件中的入口
2、 KiThreadStartup->PspSystemThreadStartup->内核空间中用户指定的入口
下面是每个用户线程的启动流程
NTSTATUS
NtCreateThread(OUT PHANDLE ThreadHandle,//返回线程句柄
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN HANDLE ProcessHandle,//目标进程
OUT PCLIENT_ID ClientId,//返回pid.tid
IN PCONTEXT ThreadContext,//线程初始的用户空间寄存器上下文
IN PINITIAL_TEB InitialTeb,//线程的初始teb
IN BOOLEAN CreateSuspended)//是否初始创建为挂起态
{
INITIAL_TEB SafeInitialTeb = *InitialTeb;
if (KeGetPreviousMode() != KernelMode)
{
//用户空间线程必须指定初始的寄存器上下文(因为要模拟回到用户空间)
if (!ThreadContext) return STATUS_INVALID_PARAMETER;
}
return PspCreateThread(ThreadHandle,DesiredAccess,ObjectAttributes,ProcessHandle,
NULL,ClientId,ThreadContext,&SafeInitialTeb,CreateSuspended,
NULL,//用户线程无需StartRoutine
NULL);//用户线程无需StartContext
}
NTSTATUS
PspCreateThread(OUT PHANDLE ThreadHandle, //返回线程句柄
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN HANDLE ProcessHandle, //目标进程(用于创建用户线程)
IN PEPROCESS TargetProcess, //目标进程(用于创建内核线程)
OUT PCLIENT_ID ClientId, //返回pid.tid
IN PCONTEXT ThreadContext,//用户空间的初始寄存器上下文
IN PINITIAL_TEB InitialTeb, //线程的初始teb(内核线程不需要)
IN BOOLEAN CreateSuspended, //是否初始创建为挂起态
IN PKSTART_ROUTINE StartRoutine OPTIONAL,//内核线程的用户指定入口
IN PVOID StartContext OPTIONAL)//入口参数
{
PTEB TebBase = NULL;
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
PACCESS_STATE AccessState = &LocalAccessState;
if (StartRoutine) PreviousMode = KernelMode;//只有内核线程才会显式指定StartRoutine
if (ProcessHandle)//用户线程的目标进程
{
Status = ObReferenceObjectByHandle(ProcessHandle,PROCESS_CREATE_THREAD,PsProcessType,
PreviousMode, (PVOID*)&Process,NULL);
}
else
Process = TargetProcess;
//关键。创建该线程对象的内核结构
Status = ObCreateObject(PreviousMode,PsThreadType,ObjectAttributes,PreviousMode,NULL,
sizeof(ETHREAD),0,0, (PVOID*)&Thread);
RtlZeroMemory(Thread, sizeof(ETHREAD));
Thread->ExitStatus = STATUS_PENDING;
Thread->ThreadsProcess = Process;//指定该线程的所属进程
Thread->Cid.UniqueProcess = Process->UniqueProcessId; //指定该线程的所属进程的pid
CidEntry.Object = Thread;
CidEntry.GrantedAccess = 0;
Thread->Cid.UniqueThread = ExCreateHandle(PspCidTable, &CidEntry);//分配一个tid
InitializeListHead(&Thread->IrpList);//该线程发起的所有未完成的irp请求链表
InitializeListHead(&Thread->PostBlockList);
InitializeListHead(&Thread->ActiveTimerListHead);
if (ThreadContext)//if 用户线程
{
//用户线程都会分配一个teb
Status = MmCreateTeb(Process, &Thread->Cid, InitialTeb, &TebBase);
Thread->StartAddress = ThreadContext->Eip;//用户空间中的公共入口
Thread->Win32StartAddress = ThreadContext->Eax;//真正线程入口(oep或用户指定的入口)
//初始化线程对象结构、构造用户线程的初始运行环境
Status = KeInitThread(&Thread->Tcb,
PspUserThreadStartup,//内核中的用户线程派遣函数入口
NULL,//StartRoutine=NULL,使用用户空间中那个公共的入口
Thread->StartAddress,//StartContext
ThreadContext,//用户空间中的初始寄存器上下文
TebBase,&Process->Pcb);
}
Else //内核线程
{
Thread->StartAddress = StartRoutine;//用户指定的入口
//初始化线程对象结构、构造内核线程的初始运行环境
Status = KeInitThread(&Thread->Tcb,
PspSystemThreadStartup, //内核中的内核线程派遣函数入口
StartRoutine, //用户指定的入口
StartContext,//入口参数
NULL,//无需context
NULL,//无需teb
&Process->Pcb);
}
InsertTailList(&Process->ThreadListHead, &Thread->ThreadListEntry);//插入进程总线程链表
Process->ActiveThreads++;
KeStartThread(&Thread->Tcb);//设置该线程的初始优先级、时间片信息(从进程继承)
PspRunCreateThreadNotifyRoutines(Thread, TRUE);//通知系统线程创建消息
if (CreateSuspended) KeSuspendThread(&Thread->Tcb);//挂起线程
//将令牌与申请的权限传递到访问状态中
Status = SeCreateAccessStateEx(NULL,ThreadContext ?PsGetCurrentProcess() : Process,
&LocalAccessState,&AuxData,DesiredAccess,…);
Status = ObInsertObject(Thread,AccessState,DesiredAccess,0,NULL,&hThread);//插入句柄表
if (NT_SUCCESS(Status))
{
if (ClientId) *ClientId = Thread->Cid;
*ThreadHandle = hThread;
}
KeQuerySystemTime(&Thread->CreateTime);//记录线程的创建时间
KeReadyThread(&Thread->Tcb); //构造好初始运行环境后,加入就绪队列,现在线程就将跑起来了
return Status;
}
如上,上面函数创建内核线程对象,然后调用下面的函数初始化对象结构,创建它的内核栈,然后构造好它的初始运行环境(指内核栈中的初始状态),设置好初始的优先级和时间片后,就启动线程运行(指加入就绪队列)。这样,当该线程不久被调度运行时,就能跟着内核栈中初始的状态,一直运行下去(指调度时:恢复线程切换线程,从KiThreadStartup函数开始运行,然后恢复用户空间寄存器现场,回到用户空间的公共总入口处(kernel32模块中的BaseProcessStrartThunk或BaseThreadStrartThunk)继续执行)
NTSTATUS
KeInitThread(IN OUT PKTHREAD Thread,
IN PKSYSTEM_ROUTINE SystemRoutine,//用户线程是PspUserThreadStartup
IN PKSTART_ROUTINE StartRoutine,//用户线程是NULL,使用公共的总入口
IN PVOID StartContext,//入口参数
IN PCONTEXT Context,//用户空间的初始寄存器上下文
IN PVOID Teb,//用户线程的初始teb
IN PKPROCESS Process)
{
BOOLEAN AllocatedStack = FALSE;//表示是否分配了内核栈
KeInitializeDispatcherHeader(&Thread->DispatcherHeader,ThreadObject,
sizeof(KTHREAD) / sizeof(LONG),FALSE);//线程也是可等待对象
InitializeListHead(&Thread->MutantListHead);
for (i = 0; i< (THREAD_WAIT_OBJECTS + 1); i++)
Thread->WaitBlock[i].Thread = Thread;//线程内部内置的四个预定等待块
Thread->EnableStackSwap = TRUE; //指示内核栈可以被置换到外存
Thread->IdealProcessor = 1;
Thread->SwapBusy = FALSE;//一个标记当前线程是否正在进行切换的标记
Thread->KernelStackResident = TRUE; //线程初始创建时,内核栈当然位于物理内存中
Thread->AdjustReason = AdjustNone;//优先级的调整原因
Thread->ServiceTable = KeServiceDescriptorTable;//该线程使用的系统服务表描述符表(非SSDT)
//初始时,线程的两个APC队列都为空
InitializeListHead(&Thread->ApcState.ApcListHead[0]);
InitializeListHead(&Thread->ApcState.ApcListHead[1]);
Thread->ApcState.Process = Process;//当前进程
Thread->ApcStatePointer[OriginalApcEnvironment] = &Thread->ApcState;
Thread->ApcStatePointer[AttachedApcEnvironment] = &Thread->SavedApcState;
Thread->ApcStateIndex = OriginalApcEnvironment;
Thread->ApcQueueable = TRUE;//标记初始时,APC队列可插入
//一个专用于挂起线程的APC,后文会有介绍
KeInitializeApc(&Thread->SuspendApc,Thread,
OriginalApcEnvironment,
KiSuspendNop,
KiSuspendRundown,
KiSuspendThread,//该apc真正的函数
KernelMode,NULL);
KeInitializeSemaphore(&Thread->SuspendSemaphore, 0, 2);
Timer = &Thread->Timer;//可复用
KeInitializeTimer(Timer);
TimerWaitBlock = &Thread->WaitBlock[TIMER_WAIT_BLOCK];//定时器固定占用一个等待快
TimerWaitBlock->Object = Timer;
TimerWaitBlock->WaitKey = STATUS_TIMEOUT;
TimerWaitBlock->WaitType = WaitAny;
TimerWaitBlock->NextWaitBlock = NULL;
TimerWaitBlock->WaitListEntry.Flink = &Timer->Header.WaitListHead;
TimerWaitBlock->WaitListEntry.Blink = &Timer->Header.WaitListHead;
Thread->Teb = Teb;//记录teb
KernelStack = MmCreateKernelStack(FALSE, 0);//关键。分配该线程的内核栈
AllocatedStack = TRUE;//标记为已分配
Thread->InitialStack = KernelStack;//初始的内核栈顶(即栈底)
Thread->StackBase = KernelStack;//内核栈底
Thread->StackLimit = KernelStack -12kb;//普通线程的内核栈的大小为12kb
Thread->KernelStackResident = TRUE;//初始时,内核栈当然位于物理内存中
Status = STATUS_SUCCESS;
//关键。下面这个函数构造初始的内核栈帧(模拟切换时的状态)
KiInitializeContextThread(Thread,
SystemRoutine,//用户线程为PspUserThreadStartup
StartRoutine,//用户线程为NULL(表示使用公共总入口)
StartContext,//入口参数
Context);//用户空间的初始寄存器上下文
Thread->State = Initialized;//标记为已初始化好,可以运行了
return Status;
}
下面这个函数就是用来实际执行构造线程的初始运行环境(即初始的内核栈状态)工作
初始的内核栈会模拟该线程仿佛以前曾经运行过,曾经被切换后的状态,这样,该线程一旦得到初始调度机会,就向得到重新调度机会一样,继续运行。
每个处于非运行状态的线程的内核栈的布局是:(从栈底到栈顶)【浮点、trap、函数、切】
浮点寄存器帧|trap现场帧|内核各层函数参数、局部变量帧|线程切换帧
每次发生系统调用、中断、异常时线程都会进入内核,在内核栈先保存浮点寄存器,然后保存寄存器现场,
进入内核函数嵌套调用,最后由于时间片等原因发生线程切换,保存切换时的现场,等待下次调度运行时,从上次切换出时的断点处继续执行。
注意每当重回到用户空间后,线程的内核栈就是空白的。一个线程的绝大多数时间都是运行在用户空间,因此,绝大多数时刻,线程的内核栈都呈现空白状态(里面没存放任何数据)。
下面这个函数就是用来初始构造模拟线程被切换出时的现场(实际线程还没运行过,即还没切换过)。
非常关键。
VOID
KiInitializeContextThread(IN PKTHREAD Thread,
IN PKSYSTEM_ROUTINE SystemRoutine,//用户线程为PspUserThreadStartup
IN PKSTART_ROUTINE StartRoutine, //用户线程为NULL(表示公共总入口)
IN PVOID StartContext, //入口参数
IN PCONTEXT ContextPointer) //用户线程的初始上下文(内核线程没有)
{
PFX_SAVE_AREA FxSaveArea;//内核栈中的浮点寄存器保存区
PFXSAVE_FORMAT FxSaveFormat;
PKSTART_FRAME StartFrame;//线程公共起始函数KiThreadStartup的栈帧
PKSWITCHFRAME CtxSwitchFrame;//切换帧
PKTRAP_FRAME TrapFrame;//trap现场帧
CONTEXT LocalContext;//临时变量
PCONTEXT Context = NULL;
ULONG ContextFlags;
PKUINIT_FRAME InitFrame;//线程的初始内核栈帧(由浮点帧、trap帧、起始函数帧、切换帧组成)
InitFrame = (PKUINIT_FRAME)( Thread->InitialStack - sizeof(KUINIT_FRAME));
FxSaveArea = &InitFrame->FxSaveArea;//初始帧中的浮点保存区
RtlZeroMemory(FxSaveArea,KTRAP_FRAME_LENGTH + sizeof(FX_SAVE_AREA));
TrapFrame = &InitFrame->TrapFrame;//初始帧中的trap现场帧(最重要)
StartFrame = &InitFrame->StartFrame;//起始函数(指KiThreadStartup)的参数帧(重要)
CtxSwitchFrame = &InitFrame->CtxSwitchFrame;//切换帧(非常重要)
if (ContextPointer)//如果是要构造用户线程的初始帧
{
RtlCopyMemory(&LocalContext, ContextPointer, sizeof(CONTEXT));
Context = &LocalContext;
ContextFlags = CONTEXT_CONTROL;
{初始化浮点寄存器部分略}
Context->ContextFlags &= ~CONTEXT_DEBUG_REGISTERS;//初始时不需要调试寄存器
//关键。模拟保存进入内核空间中时的现场
KeContextToTrapFrame(Context,NULL,TrapFrame,Context->ContextFlags | ContextFlags,
UserMode);//将Context中各个寄存器填写到Trap帧中(模拟自陷现场)
TrapFrame->HardwareSegSs |= RPL_MASK;
TrapFrame->SegDs |= RPL_MASK;TrapFrame->SegEs |= RPL_MASK;
TrapFrame->Dr7 = 0;//不需要调试寄存器
TrapFrame->DbgArgMark = 0xBADB0D00;
TrapFrame->PreviousPreviousMode = UserMode;
TrapFrame->ExceptionList = -1;
Thread->PreviousMode = UserMode;//模拟从用户空间自陷进来时构造的帧
StartFrame->UserThread = TRUE;//相当于push传参给KiThreadStartup
}
else
{
{内核线程则会初始化成不同的浮点寄存器,略}
Thread->PreviousMode = KernelMode; //模拟从内核空间发起系统调用时构造的帧
StartFrame->UserThread = FALSE; //相当于push传参给KiThreadStartup
}
StartFrame->StartContext = StartContext;//相当于push传参给KiThreadStartup
StartFrame->StartRoutine = StartRoutine; //相当于push传参给KiThreadStartup
StartFrame->SystemRoutine = SystemRoutine; //相当于push传参给KiThreadStartup
//关键。模拟线程仿佛上次在执行call KiThreadStartup时,被切换了出去
CtxSwitchFrame->RetAddr = KiThreadStartup;//以后线程一调度就从这儿开始执行下去
CtxSwitchFrame->ApcBypassDisable = TRUE;
CtxSwitchFrame->ExceptionList = -1;//线程的初始内核seh链表当然为空(-1表示空)
Thread->KernelStack = CtxSwitchFrame;//记录上次切换时的内核栈顶(模拟的)
}
为了弄懂线程初始时的内核栈布局,必须理解下面几个结构体定义和函数。
typedef struct _KUINIT_FRAME //每个线程的初始内核栈帧
{
KSWITCHFRAME CtxSwitchFrame;//切换帧
KSTART_FRAME StartFrame;//KiThreadStartup函数的参数帧
KTRAP_FRAME TrapFrame;//trap现场帧
FX_SAVE_AREA FxSaveArea;//浮点保存区
} KUINIT_FRAME, *PKUINIT_FRAME;
其中浮点保存区就位于栈底,向上依次是trap现场帧、KiThreadStartup的参数帧、切换帧
typedef struct _KSTART_FRAME //KiThreadStartup的参数帧
{
PKSYSTEM_ROUTINE SystemRoutine; //用户线程为PspUserThreadStartup
PKSTART_ROUTINE StartRoutine;//用户线程为NULL(表示使用公共总入口)
PVOID StartContext;//入口参数
BOOLEAN UserThread;//标志
} KSTART_FRAME, *PKSTART_FRAME;
typedef struct _KSWITCHFRAME //切换帧
{
PVOID ExceptionList;//保存线程切换时的内核she链表(不是用户空间中的seh)
Union
{
BOOLEAN ApcBypassDisable;//用于首次调度
UCHAR WaitIrql;//用于保存切换时的WaitIrql
};
PVOID RetAddr;//保存发生切换时的断点地址(以后切换回来时从这儿继续执行)
} KSWITCHFRAME, *PKSWITCHFRAME;
不管是用户线程还是内核线程,都是最开始从下面这个函数开始执行起来的。
Void KiThreadStartup(PKSYSTEM_ROUTINE SystemRoutine //用户线程为PspUserThreadStartup
PKSTART_ROUTINE StartRoutine //转用作PspUserThreadStartup的参数
Void* StartContext//转用作PspUserThreadStartup的参数
BOOL UserThread
)
{
Xor ebx,ebx
Xor esi,esi
Xor edi,edi
Xor ebp,ebp
Mov ecx,APC_LEVEL
Call KfLowerIrql //降到APC级别
Pop eax //弹出的第一个值刚好是SystemRoutine
Call eax //调用SystemRoutine(注意StartRoutine,StartContext又是它的参数)
----------------------------------华丽的分割线------------------------------------------
//注意若创建的是内核线程,那么上面的eax是PspSystemThreadStartup,这个函数是“不返回的”,
它执行完毕后不会ret回来,而是直接跳去用户指定的内核入口了。反之,若能回来,那么可以肯定是
用户线程(而且,StartRoutine和StartContext这两个参数已被PspSystemThreadStartup在内部弹出)。那么,现在就可以顺利弹出trap帧,恢复用户空间中的寄存器上下文,继续执行,于是Jmp KiServiceExit2, jmp到那儿去,退回用户空间。
Pop ecx //此时ecx=栈帧中UserThread字段的值
Or ecx,ecx
Jz BadThread //UserThread不为1就显示蓝屏界面
Mov ebp,esp //此时的内核栈顶就是trap帧的地址。
Jmp KiServiceExit2 //此时内核栈中只剩余浮点保存区和trap帧,将恢复用户空间现场退回用户空间
}
上面的线程公共入口函数内部会call SystemRoutine 进入对应的函数。如果创建的是用户线程,调用的就是PspUserThreadStartup。
VOID
PspUserThreadStartup(IN PKSTART_ROUTINE StartRoutine,//对于用户线程无意义
IN PVOID StartContext)//对于用户线程无意义
{
BOOLEAN DeadThread = FALSE;
KeLowerIrql(PASSIVE_LEVEL);
Thread = PsGetCurrentThread();
if (Thread->DeadThread)
DeadThread = TRUE;
Else …
if (!(Thread->DeadThread) && !(Thread->HideFromDebugger))
DbgkCreateThread(Thread, StartContext);//通知内核调试器一个新线程启动了
if (!DeadThread)
{
KeRaiseIrql(APC_LEVEL, &OldIrql);
//返回用户空间的总入口前先执行一下apc,完成其他初始工作(如加载其他依赖库)
KiInitializeUserApc(KeGetExceptionFrame(&Thread->Tcb),
KeGetTrapFrame(&Thread->Tcb),
PspSystemDllEntryPoint,//ntdll.LdrInitializeThunk
NULL,PspSystemDllBase,NULL);
KeLowerIrql(PASSIVE_LEVEL);
}
Else …
//这个函数是典型的‘返回型’函数,会返回到上面函数的call eax指令后面,进而退回用户空间的总入口函数去去执行。不过,在退回总入口前,这儿插入了一个APC,执行完这个附加的APC后才会正式退回用户空间的总入口(因为这个LdrInitializeThunk APC函数还有一些重要的附加工作要做)
Return;
}
相信大家一直有一个疑问,就是用户空间的总入口到底做了什么工作,我们后文再看。
-------------------------------------------------------------------------------------
现在我们回到CreateProcess API,总结一下这个函数到底在内部干了什么,看以下总结
1、 打开目标可执行文件
若是exe文件,先检查‘映像劫持’键,然后打开文件,创建一个section,等候映射
若是 bat、cmd脚本文件,则启动的是cmd.exe进程,脚本文件作为命令行参数
若是DOS的exe、com文件,启动ntvdm.exe v86进程,原文件作为命令行参数
若是posix、os2文件,启动对应的子系统服务进程
2、 创建、初始化进程对象;创建初始化地址空间;加载映射exe和ntdll文件;分配一个PEB
3、 创建、初始化主线程对象;创建TEB;构造初始的运行环境(内核初始栈帧)
4、 通知windows子系统(csrss.exe进程)新进程创建事件(csrss.exe进程含有绝大多数进程的句柄)
这样,进程、主线程都创建起来了,只需等待得到cpu调度便可投入运行。
-------------------------------------------------------------------------------------
现在具体看一下CreateProcess的创建过程,它会在内部直接转调CreateProcessInternalW函数
BOOL
CreateProcessInternalW(HANDLE hToken,//暂时无用
LPCWSTR lpApplicationName,//程序文件名
LPWSTR lpCommandLine,//命令行
LPSECURITY_ATTRIBUTES lpProcessAttributes,//SD
LPSECURITY_ATTRIBUTES lpThreadAttributes,//SD
BOOL bInheritHandles,//是否继承父进程句柄表中的那些可继承句柄
DWORD dwCreationFlags,
LPVOID lpEnvironment,//环境变量块
LPCWSTR lpCurrentDirectory,//指定给子进程的当前目录
LPSTARTUPINFOW lpStartupInfo,//附加启动信息
LPPROCESS_INFORMATION lpProcessInformation,//返回创建结果
PHANDLE hNewToken)//暂时无用
{
BOOLEAN CmdLineIsAppName = FALSE;//表示文件名是否就是命令行
UNICODE_STRING ApplicationName = { 0, 0, NULL };
HANDLE hSection = NULL, hProcess = NULL, hThread = NULL, hDebug = NULL;
LPWSTR CurrentDirectory = NULL;
PPEB OurPeb = NtCurrentPeb();//当前进程即父进程的peb
SIZE_T EnvSize = 0;//环境变量块的大小
//检查下面的‘映像劫持’键,略
{HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options}
if ((dwCreationFlags & (DETACHED_PROCESS | CREATE_NEW_CONSOLE)) ==
(DETACHED_PROCESS | CREATE_NEW_CONSOLE))
{
SetLastError(ERROR_INVALID_PARAMETER);//这俩标志不可同时使用
return FALSE;
}
StartupInfo = *lpStartupInfo;
RtlZeroMemory(lpProcessInformation, sizeof(PROCESS_INFORMATION));
PriorityClass.Foreground = FALSE;//初始创建的进程作为后台进程看待
//根据创建标志计算对应的优先级类别(一种优先级类对应一种基本优先级)
PriorityClass.PriorityClass = (UCHAR)BasepConvertPriorityClass(dwCreationFlags);
GetAppName:
if (!lpApplicationName)//很常见
{
NameBuffer = RtlAllocateHeap(RtlGetProcessHeap(),0,MAX_PATH * sizeof(WCHAR));
lpApplicationName = lpCommandLine;
处理NameBuffer,略
lpApplicationName = NameBuffer;// 最终获得命令行中包含的应用程序文件名
}
else if (!lpCommandLine || *lpCommandLine == UNICODE_NULL)
{
CmdLineIsAppName = TRUE;
lpCommandLine = (LPWSTR)lpApplicationName;
}
//事实上只是为程序文件创建一个section,等待映射(函数名有误导)
Status = BasepMapFile(lpApplicationName, &hSection, &ApplicationName);
if (!NT_SUCCESS(Status))
{
If(是一个bat批脚本文件)
命令行改为“cmd /c bat文件名”,goto GetAppName,重新解析
Else …
}
if (!StartupInfo.lpDesktop)//继承父进程的桌面
StartupInfo.lpDesktop = OurPeb->ProcessParameters->DesktopInfo.Buffer;
//查询section对象的映像文件信息
Status = ZwQuerySection(hSection,SectionImageInformation,
&SectionImageInfo,sizeof(SectionImageInfo),NULL);
if (SectionImageInfo.ImageCharacteristics & IMAGE_FILE_DLL) 失败返回;
if (IMAGE_SUBSYSTEM_WINDOWS_GUI == SectionImageInfo.SubSystemType)
{
dwCreationFlags &= ~CREATE_NEW_CONSOLE;//GUI程序无需控制台
dwCreationFlags |= DETACHED_PROCESS;
}
ObjectAttributes = BasepConvertObjectAttributes(&LocalObjectAttributes,
lpProcessAttributes,NULL);
//if创建的是一个要被当前线程调试的子进程
if (dwCreationFlags & (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS))
{
Status = DbgUiConnectToDbg();//连接到
hDebug = DbgUiGetThreadDebugObject();//为当前线程创建一个调试端口(用来父子进程通信)
}
//关键。调用系统服务,创建内核中的进程对象,并初始化其地址空间等N多内容
Status = NtCreateProcess(&hProcess,PROCESS_ALL_ACCESS,ObjectAttributes,
NtCurrentProcess(),bInheritHandles,hSection,
hDebug,); //当前线程的调试端口(将传给子进程)
//设置进程的优先级类别
if (PriorityClass.PriorityClass != PROCESS_PRIORITY_CLASS_INVALID)
{
Status = NtSetInformationProcess(hProcess,ProcessPriorityClass,
&PriorityClass,sizeof(PROCESS_PRIORITY_CLASS));
}
Status = NtQueryInformationProcess(hProcess,ProcessBasicInformation,&ProcessBasicInfo,
sizeof(ProcessBasicInfo),NULL);
if(lpEnvironment && !(dwCreationFlags & CREATE_UNICODE_ENVIRONMENT))
lpEnvironment = BasepConvertUnicodeEnvironment(&EnvSize, lpEnvironment);
RemotePeb = ProcessBasicInfo.PebBaseAddress;//子进程的peb地址(实际上是固定的)
//关键。创建子进程的参数块和环境变量块
Status = BasepInitializeEnvironment(hProcess,RemotePeb,lpApplicationName,
CurrentDirectory,lpCommandLine,
lpEnvironment,EnvSize,//环境变量块的地址、长度
&StartupInfo,dwCreationFlags,bInheritHandles);
//如果没有显式指定这三个标准句柄给子进程,就继承父进程中的那3个标准句柄(最常见)
if (!bInheritHandles && !(StartupInfo.dwFlags & STARTF_USESTDHANDLES) &&
SectionImageInfo.SubSystemType == IMAGE_SUBSYSTEM_WINDOWS_CUI)
{
PRTL_USER_PROCESS_PARAMETERS RemoteParameters;
Status = NtReadVirtualMemory(hProcess,&RemotePeb->ProcessParameters,
&RemoteParameters,sizeof(PVOID),NULL);
BasepDuplicateAndWriteHandle(hProcess,OurPeb->ProcessParameters->StandardInput,
&RemoteParameters->StandardInput);
BasepDuplicateAndWriteHandle(hProcess,OurPeb->ProcessParameters->StandardOutput,
&RemoteParameters->StandardOutput);
BasepDuplicateAndWriteHandle(hProcess,OurPeb->ProcessParameters->StandardError,
&RemoteParameters->StandardError);
}
//通知csrss.exe进程,一个新的进程已创建
Status = BasepNotifyCsrOfCreation(dwCreationFlags,ProcessBasicInfo.UniqueProcessId,
bInheritHandles);
--------------------------------------华丽的分割线---------------------------------
//至此,已创建好了进程,接下来创建该进程中的第一个线程(主线程)
//这个函数创建主线程的用户栈、内核栈,并建立起初始的运行环境(内核栈帧)
hThread = BasepCreateFirstThread(hProcess,lpThreadAttributes,&SectionImageInfo,
&ClientId);//返回线程的pid.tid
if (!(dwCreationFlags & CREATE_SUSPENDED))
NtResumeThread(hThread, &Dummy);//恢复线程,挂入就绪队列(即可以开始运行这个线程了)
lpProcessInformation->dwProcessId = (DWORD)ClientId.UniqueProcess;
lpProcessInformation->dwThreadId = (DWORD)ClientId.UniqueThread;
lpProcessInformation->hProcess = hProcess;
lpProcessInformation->hThread = hThread;
return TRUE;
}
NTSTATUS
BasepMapFile(IN LPCWSTR lpApplicationName,OUT PHANDLE hSection,
IN PUNICODE_STRING ApplicationName)
{
RelativeName.Handle = NULL;
//转为NT路径格式
RtlDosPathNameToNtPathName_U(lpApplicationName,ApplicationName,NULL,&RelativeName)
if (RelativeName.DosPath.Length)
ApplicationName = &RelativeName.DosPath;
InitializeObjectAttributes(&ObjectAttributes,ApplicationName,OBJ_CASE_INSENSITIVE,
RelativeName.Handle,NULL);
//打开程序文件
Status = NtOpenFile(&hFile,SYNCHRONIZE | FILE_EXECUTE | FILE_READ_DATA,&ObjectAttributes,
&IoStatusBlock,FILE_SHARE_DELETE | FILE_SHARE_READ,
FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE);
//为文件创建一个公共section,等候映射(多个进程可以同用一个exe文件)
Status = NtCreateSection(hSection,SECTION_ALL_ACCESS,NULL,NULL,PAGE_EXECUTE,
SEC_IMAGE,hFile);
NtClose(hFile);
return Status;
}
如上,这个函数的名字有误导,其实只是创建一个section,并没有立即映射到地址空间(多个进程可以共享同一程序文件的)
当用户线程从内核的KiThreadStartup运行起来后,进入PspUserThreadStartup,最后回到用户空间的总入口处(主线程的用户空间根是BaseProcessStartThunk,其他线程的用户空间根是BaseThreadStartThunk)继续运行。不过前文讲了,在正式从内核回到用户空间的的总入口前,会扫描执行中途插入的APC函数,做完附加的APC工作后才从总入口处继续运行。插入的这个APC函数是LdrInitializeThunk,它的主要工作是负责加载exe文件依赖的所有动态库以及其他工作。
LdrInitializeThunk() //APC
{
Lea eax,[esp+16]
Mov [esp+4],eax //第一个参数=Context*
Xor ebp,ebp
Jmp LdrpInit //实际的工作
}
VOID LdrpInit(PCONTEXT Context,PVOID SystemArgument1,PVOID SystemArgument2) //APC
{
if (!LdrpInitialized)//if 主线程
{
LdrpInit2(Context, SystemArgument1, SystemArgument2);
LdrpInitialized = TRUE;
}
LdrpAttachThread();//各线程创建后都会通知进程中的所有模块一个ThreadAttach消息
}
看看主线程的初始化工作,也即进程的初始化工作,如下:
VOID LdrpInit2(PCONTEXT Context,PVOID SystemArgument1,PVOID SystemArgument2)
{
PPEB Peb = NtCurrentPeb();//现在就是子进程的peb啦(在子进程的地址空间中)
PVOID BaseAddress = SystemArgument1;//ntdll模块的地址
ImageBase = Peb->ImageBaseAddress;
PEDosHeader = (PIMAGE_DOS_HEADER) ImageBase;
if (PEDosHeader->e_magic != IMAGE_DOS_SIGNATURE || PEDosHeader->e_lfanew == 0L ||
*(PULONG)((PUCHAR)ImageBase + PEDosHeader->e_lfanew) != IMAGE_NT_SIGNATURE)
{
//验证PE文件签名
ZwTerminateProcess(NtCurrentProcess(), STATUS_INVALID_IMAGE_FORMAT);
}
RtlNormalizeProcessParams(Peb->ProcessParameters);
NTHeaders = (PIMAGE_NT_HEADERS)((ULONG_PTR)ImageBase + PEDosHeader->e_lfanew);
Status = ZwQuerySystemInformation(SystemBasicInformation,&SystemInformation,
sizeof(SYSTEM_BASIC_INFORMATION),NULL);
Peb->NumberOfProcessors = SystemInformation.NumberOfProcessors;
RtlInitializeHeapManager();
//创建进程的默认堆
Peb->ProcessHeap = RtlCreateHeap(HEAP_GROWABLE,NULL,
NTHeaders->OptionalHeader.SizeOfHeapReserve,//一般为0
NTHeaders->OptionalHeader.SizeOfHeapCommit,//一般为4kb
NULL,NULL);
RtlpInitializeVectoredExceptionHandling();//初始化向量化异常
RtlInitializeCriticalSection(&PebLock);
Peb->FastPebLock = &PebLock;
//初始化peb中内置的动态tls位图
Peb->TlsBitmap = &TlsBitMap;
Peb->TlsExpansionBitmap = &TlsExpansionBitMap;
Peb->TlsExpansionCounter = 64;
RtlInitializeBitMap(&TlsBitMap, Peb->TlsBitmapBits,64);//固定指向内置的那个64位tls位图
RtlInitializeBitMap(&TlsExpansionBitMap, Peb->TlsExpansionBitmapBits,1024);
//初始化回调表
Peb->KernelCallbackTable = RtlAllocateHeap(RtlGetProcessHeap(),0,sizeof(PVOID) *
(USER32_CALLBACK_MAXIMUM + 1));
RtlInitializeCriticalSection(&LoaderLock);
Peb->LoaderLock = &LoaderLock;
//从默认堆中分配一个加载信息块
Peb->Ldr = (PPEB_LDR_DATA) RtlAllocateHeap(Peb->ProcessHeap,0,sizeof(PEB_LDR_DATA));
Peb->Ldr->Length = sizeof(PEB_LDR_DATA);
Peb->Ldr->Initialized = FALSE;//表示尚未完成初始化(也即尚未完成加载dll等工作)
Peb->Ldr->SsHandle = NULL;
InitializeListHead(&Peb->Ldr->InLoadOrderModuleList);//加载顺序的模块表
InitializeListHead(&Peb->Ldr->InMemoryOrderModuleList);//内存地址顺序的模块表
//初始化顺序模块表,初始化顺序与加载顺序相反(最底层的dll最先得到初始化)
InitializeListHead(&Peb->Ldr->InInitializationOrderModuleList);
LoadImageFileExecutionOptions(Peb);
ExeModule = RtlAllocateHeap(Peb->ProcessHeap,0,sizeof(LDR_DATA_TABLE_ENTRY));
ExeModule->DllBase = Peb->ImageBaseAddress;
RtlCreateUnicodeString(&ExeModule->FullDllName,
Peb->ProcessParameters->ImagePathName.Buffer);
RtlCreateUnicodeString(&ExeModule->BaseDllName,
wcsrchr(ExeModule->FullDllName.Buffer, L'\\') + 1);
ExeModule->Flags = LDRP_ENTRY_PROCESSED;//exe模块没有dll标志
ExeModule->LoadCount = -1;//标记为无法动态卸载
ExeModule->TlsIndex = -1;ExeModule->SectionPointer = NULL;ExeModule->CheckSum = 0;
NTHeaders = RtlImageNtHeader(ExeModule->DllBase);
ExeModule->SizeOfImage = LdrpGetResidentSize(NTHeaders);
ExeModule->TimeDateStamp = NTHeaders->FileHeader.TimeDateStamp;
//先插入exe文件的模块描述符
InsertTailList(&Peb->Ldr->InLoadOrderModuleList,&ExeModule->InLoadOrderLinks);
wcscpy(FullNtDllPath, SharedUserData->NtSystemRoot);//一般为C:\Windows
wcscat(FullNtDllPath, L"\\system32\\ntdll.dll");
NtModule = (PLDR_DATA_TABLE_ENTRY)
RtlAllocateHeap(Peb->ProcessHeap,0,sizeof(LDR_DATA_TABLE_ENTRY));
memset(NtModule, 0, sizeof(LDR_DATA_TABLE_ENTRY));
NtModule->DllBase = BaseAddress;
NtModule->EntryPoint = 0;
RtlCreateUnicodeString(&NtModule->FullDllName, FullNtDllPath);
RtlCreateUnicodeString(&NtModule->BaseDllName, L"ntdll.dll");
NtModule->Flags = LDRP_IMAGE_DLL | LDRP_ENTRY_PROCESSED;
NtModule->LoadCount = -1;//标记无法动态卸载ntdll.dll
NtModule->TlsIndex = -1;NtModule->SectionPointer = NULL;NtModule->CheckSum = 0;
NTHeaders = RtlImageNtHeader(NtModule->DllBase);
NtModule->SizeOfImage = LdrpGetResidentSize(NTHeaders);
NtModule->TimeDateStamp = NTHeaders->FileHeader.TimeDateStamp;
//再插入ntdll文件的模块描述符
InsertTailList(&Peb->Ldr->InLoadOrderModuleList,&NtModule->InLoadOrderLinks);
InsertTailList(&Peb->Ldr->InInitializationOrderModuleList,
&NtModule->InInitializationOrderModuleList);//NTDLL不依赖其他库
LdrpInitLoader();//获取“\KnownDlls\KnownDllPath”路径
//PE加载器的核心函数,用来执行模块的重定位、加载导入库、处理tls
ExeModule->EntryPoint = LdrPEStartup(ImageBase, NULL, NULL, NULL);
Peb->Ldr->Initialized = TRUE;//标志该进程的所有dll都已加载完成
if (Peb->BeingDebugged)
DbgBreakPoint();//int 3通知调试器, 首次触发调试中断
}
进程初始时的重点工作就是加载exe文件依赖的所有子孙dll,由下面的函数完成这项工作
(注意这个函数专用来启动初始化进程的主exe文件,是启动阶段的核心函数)
PEPFUNC LdrPEStartup (PVOID ImageBase,//exe文件的内存地址(进程的主exe文件)
HANDLE SectionHandle,
PLDR_DATA_TABLE_ENTRY* Module,
PWSTR FullDosName)
{
PEPFUNC EntryPoint = NULL;
DosHeader = (PIMAGE_DOS_HEADER) ImageBase;
NTHeaders = (PIMAGE_NT_HEADERS) ((ULONG_PTR)ImageBase + DosHeader->e_lfanew);
//if 实际加载地址与pe头中的预期加载地址不同,执行重定位工作(常见于dll文件,exe文件也可能)
if (ImageBase != (PVOID) NTHeaders->OptionalHeader.ImageBase)
Status = LdrPerformRelocations(NTHeaders, ImageBase);//遍历.reloc节中项目,执行重定位
if (Module != NULL)//也即if 是dll文件(事实上这个条件永不满足)
{
*Module = LdrAddModuleEntry(ImageBase, NTHeaders, FullDosName);//加入加模块载顺序链表
(*Module)->SectionPointer = SectionHandle;
}
Else //也即进程的主exe文件,这才是正题
{
Module = &tmpModule;
Status = LdrFindEntryForAddress(ImageBase, Module);//直接在模块表中查找
}
if (ImageBase != (PVOID) NTHeaders->OptionalHeader.ImageBase)
(*Module)->Flags |= LDRP_IMAGE_NOT_AT_BASE;
Status = RtlAllocateActivationContextStack(&ActivationContextStack);
if (NT_SUCCESS(Status))
{
NtCurrentTeb()->ActivationContextStackPointer = ActivationContextStack;
NtCurrentTeb()->ActivationContextStackPointer->ActiveFrame = NULL;
}
Status = LdrFixupImports(NULL, *Module);//加载子孙dll,修正IAT导入表
Status = LdrpInitializeTlsForProccess();//初始化进程的静态tls,详见后文
if (NT_SUCCESS(Status))
{
LdrpAttachProcess();//发送一个ProcessAttach消息,调用该模块的DllMain函数
LdrpTlsCallback(*Module, DLL_PROCESS_ATTACH);
}
if (NTHeaders->OptionalHeader.AddressOfEntryPoint != 0)
EntryPoint = (ULONG_PTR)ImageBase+ NTHeaders->OptionalHeader.AddressOfEntryPoint;
return EntryPoint;//返回oep
}
下面的函数加载指定模块依赖的所有子孙dll
NTSTATUS
LdrFixupImports(IN PWSTR SearchPath OPTIONAL,//自定义的dll搜索路径(不提供的话就使用标准路径)
IN PLDR_DATA_TABLE_ENTRY Module)//指定模块
{
ULONG TlsSize = 0;
NTSTATUS Status = STATUS_SUCCESS;
//获取tls目录
TlsDirectory = (PIMAGE_TLS_DIRECTORY)RtlImageDirectoryEntryToData(Module->DllBase,
TRUE,IMAGE_DIRECTORY_ENTRY_TLS,&Size);
if (TlsDirectory)
{
TlsSize = TlsDirectory->EndAddressOfRawData- TlsDirectory->StartAddressOfRawData
+ TlsDirectory->SizeOfZeroFill;
if (TlsSize > 0 && NtCurrentPeb()->Ldr->Initialized)//if 动态加载该模块
TlsDirectory = NULL;// 动态加载的模块不支持静态tls
}
ImportModuleDirectory = (PIMAGE_IMPORT_DESCRIPTOR)
RtlImageDirectoryEntryToData(Module->DllBase,
TRUE,IMAGE_DIRECTORY_ENTRY_IMPORT,&Size);
BoundImportDescriptor = (PIMAGE_BOUND_IMPORT_DESCRIPTOR)
RtlImageDirectoryEntryToData(Module->DllBase,TRUE,
MAGE_DIRECTORY_ENTRY_BOUND_IMPORT,&Size);
if (BoundImportDescriptor != NULL && ImportModuleDirectory == NULL)
return STATUS_UNSUCCESSFUL;
if (BoundImportDescriptor) 处理绑定导入表,略
else if (ImportModuleDirectory)
{
ImportModuleDirectoryCurrent = ImportModuleDirectory;//当前依赖的模块
while (ImportModuleDirectoryCurrent->Name)//遍历IMT导入模块表中的各个依赖模块
{
ImportedName = Module->DllBase + ImportModuleDirectoryCurrent->Name;//模块名
if (SearchPath == NULL) //如果没提供自定义搜索路径,就构造一个标准搜索路径
{
//标准搜索路径是:exe文件目录;当前目录;Sytem32目录;Windows目录;Path环境变量
ModulePath = LdrpQueryAppPaths(Module->BaseDllName.Buffer);
Status = LdrpGetOrLoadModule(ModulePath, ImportedName, &ImportedModule, TRUE);
if (NT_SUCCESS(Status)) goto Success;
}
//在模块加载表中查找该模块或者加载该模块(找不到就加载)
Status = LdrpGetOrLoadModule(SearchPath, ImportedName, &ImportedModule, TRUE);
Success:
//处理该依赖模块的IAT导入地址表(获取各个导入函数的实际地址,填到IAT对应的表项中)
Status = LdrpProcessImportDirectoryEntry(Module, ImportedModule, ImportModuleDirectoryCurrent);
ImportModuleDirectoryCurrent++;//下一个依赖的模块
}
}
if (TlsDirectory && TlsSize > 0)
LdrpAcquireTlsSlot(Module, TlsSize, FALSE);
return STATUS_SUCCESS;
}
NTSTATUS LdrpGetOrLoadModule(PWCHAR SearchPath,//搜索路径
PCHAR Name,//模块名,是ASC形式
PLDR_DATA_TABLE_ENTRY* Module,
BOOLEAN Load)//指找不到的话,是否加载
{
RtlInitAnsiString(&AnsiDllName, Name);
Status = RtlAnsiStringToUnicodeString(&DllName, &AnsiDllName, TRUE);
Status = LdrFindEntryForName (&DllName, Module, Load);
if (Load && !NT_SUCCESS(Status))
{
Status = LdrpLoadModule(SearchPath,0,&DllName,Module,NULL);
if (NT_SUCCESS(Status))
Status = LdrFindEntryForName (&DllName, Module, FALSE);
}
return Status;
}
之所以要在加载模块表中查找,找不到才加载,是因为避免同一个模块加载两次。下面的函数用来加载一个模块。(注意这个函数也供LoadLibrary API内部间接调用)
NTSTATUS
LdrpLoadModule(IN PWSTR SearchPath OPTIONAL,
IN ULONG LoadFlags,
IN PUNICODE_STRING Name,//模块名
PLDR_DATA_TABLE_ENTRY *Module,
PVOID *BaseAddress OPTIONAL)//返回实际加载的地址
{
if (Module == NULL)
Module = &tmpModule;
LdrAdjustDllName(&AdjustedName, Name, FALSE);
MappedAsDataFile = FALSE;
Status = LdrFindEntryForName(&AdjustedName, Module, TRUE);//仍要先查找
if (NT_SUCCESS(Status))
{
if (NULL != BaseAddress)
*BaseAddress = (*Module)->DllBase;
}
else
{
//先尝试在\KnownDlls对象目录中查找该dll文件的section对象
Status = LdrpMapKnownDll(&AdjustedName, &FullDosName, &SectionHandle);
if (!NT_SUCCESS(Status))//若找不到,则为该dll文件创建一个映像文件section
{
MappedAsDataFile = (0 != (LoadFlags & LOAD_LIBRARY_AS_DATAFILE));
//内部会调用NtCreateSection系统服务,创建一个section
Status = LdrpMapDllImageFile(SearchPath, &AdjustedName, &FullDosName,
MappedAsDataFile, &SectionHandle);
}
ViewSize = 0;//表示映射整个dll文件
ImageBase = 0;//表示不指定映射的地址
ArbitraryUserPointer = NtCurrentTeb()->NtTib.ArbitraryUserPointer;
NtCurrentTeb()->NtTib.ArbitraryUserPointer = FullDosName.Buffer;
Status = NtMapViewOfSection(SectionHandle,NtCurrentProcess(),
&ImageBase,0,0,NULL,&ViewSize,ViewShare,0,…);
NtCurrentTeb()->NtTib.ArbitraryUserPointer = ArbitraryUserPointer;
if (NULL != BaseAddress)
*BaseAddress = ImageBase;
if (MappedAsDataFile)//dll可以当做纯数据文件加载
{
if (NULL != BaseAddress)
*BaseAddress = (PVOID) ((char *) *BaseAddress + 1);//复用标志
*Module = NULL;
return STATUS_SUCCESS;
}
//if 实际加载映射的地址与该dll期望的加载地址不同,执行重定位
if (ImageBase != (PVOID) NtHeaders->OptionalHeader.ImageBase)
Status = LdrPerformRelocations(NtHeaders, ImageBase);
*Module = LdrAddModuleEntry(ImageBase, NtHeaders, FullDosName.Buffer);
(*Module)->SectionPointer = SectionHandle;
if (ImageBase != (PVOID) NtHeaders->OptionalHeader.ImageBase)
(*Module)->Flags |= LDRP_IMAGE_NOT_AT_BASE;
if (NtHeaders->FileHeader.Characteristics & IMAGE_FILE_DLL)
(*Module)->Flags |= LDRP_IMAGE_DLL;
//又加载该dll本身依赖的所有其他dll,修正它的导入表
Status = LdrFixupImports(SearchPath, *Module);
//当所有子孙dll初始化完后,自己才初始化完毕,此时才加入初始化顺序链表中
InsertTailList(&NtCurrentPeb()->Ldr->InInitializationOrderModuleList,
&(*Module)->InInitializationOrderModuleList);
}
return STATUS_SUCCESS;
}
至此,进程启动时的初始化工作已经初始完毕。Exe文件及其依赖的所有dll以及tls工作最终都完成处理了,这时候该APC函数将返回。这个LdrInitializeThunk要返回哪里呢?答案是返回到内核,然后才恢复用户寄存器现场,正式退回用户空间,执行主线程的用户空间总入口函数BaseProcessStartThunk,换句话说,当程序流执行到BaseProcessStartThunk这个函数时,进程已初始化,各dll已完成加载。此时,万事俱备,只欠东风了,线程可以放马在用户空间执行了。
_BaseProcessStartThunk@0://主线程的用户空间总入口(内核总入口是KiThreadStartup)
{
xor ebp, ebp
push eax //oep
push 0 //表示不会返回
jmp _BaseProcessStartup@4
}
__declspec(noreturn) )//主线程的入口
VOID BaseProcessStartup(PPROCESS_START_ROUTINE lpStartAddress
{
UINT uExitCode = 0;
_SEH2_TRY //放在try块中保护
{
NtSetInformationThread(NtCurrentThread(),ThreadQuerySetWin32StartAddress,
&lpStartAddress,sizeof(PPROCESS_START_ROUTINE));
//lpStartAddress即oep,一般就是WinMainCRTStartup/MainCRTStartup
uExitCode = (lpStartAddress)();//转去oep
}
_SEH2_EXCEPT(BaseExceptionFilter(_SEH2_GetExceptionInformation()))
{
uExitCode = _SEH2_GetExceptionCode();
}
_SEH2_END;
ExitProcess(uExitCode);//当WinMain函数正常退出后,进程才退出
}
-------------------------------------------------------------------------------------
_BaseThreadStartupThunk@0: //一般普通线程的用户空间总入口(内核总入口是KiThreadStartup)
{
xor ebp, ebp
push ebx //用户自己的context*参数
push eax //用户自己的线程入口函数
push 0 //表示不会返回
jmp _BaseThreadStartup@8
}
__declspec(noreturn) //一般普通线程的入口
VOID BaseThreadStartup(LPTHREAD_START_ROUTINE lpStartAddress,//用户自己的线程入口函数
LPVOID lpParameter)//用户自己函数的context*
{
volatile UINT uExitCode = 0;
_SEH2_TRY //也置于try块中保护
{
uExitCode = (lpStartAddress)((PVOID)lpParameter);
}
_SEH2_EXCEPT(BaseThreadExceptionFilter(_SEH2_GetExceptionInformation()))
{
uExitCode = _SEH2_GetExceptionCode();
} _SEH2_END;
ExitThread(uExitCode);//用户自己的线程入口函数返回后,线程自然退出
}
注:
Dll可以在进程启动初期,被PE加载器静态加载外,程序员也可以调用LoadLibrary API显式的动态加载。看一下这个函数的原理,实际上这个函数不是API,是个宏,指向LoadLibraryW/LoadLibraryA。
HINSTANCE LoadLibraryW (LPCWSTR lpLibFileName)
{
return LoadLibraryExW (lpLibFileName, 0, 0);
}
HINSTANCE
LoadLibraryExW (
LPCWSTR lpLibFileName,
HANDLE hFile,
DWORD dwFlags
)
{
if (dwFlags & DONT_RESOLVE_DLL_REFERENCES)
DllCharacteristics = IMAGE_FILE_EXECUTABLE_IMAGE;
dwFlags &= LOAD_WITH_ALTERED_SEARCH_PATH;
SearchPath = GetDllLoadPath(lpLibFileName);//构造该dll的标准搜索路径
RtlInitUnicodeString(&DllName, (LPWSTR)lpLibFileName);
if (dwFlags & LOAD_LIBRARY_AS_DATAFILE)
{
//在加载模块表中查找该dll
Status = LdrGetDllHandle(SearchPath, NULL, &DllName, (PVOID*)&hInst);
if (!NT_SUCCESS(Status))//若找不到
{
Status = LoadLibraryAsDatafile(SearchPath, DllName.Buffer, &hInst);
Return Status;
}
}
if (InWindows) //Windows中的实现
Status = LdrLoadDll(SearchPath,&DllCharacteristics,&DllName, (PVOID*)&hInst);
Else //ROS中的实现
Status = LdrLoadDll(SearchPath, &dwFlags, &DllName, (PVOID*)&hInst);
if ( !NT_SUCCESS(Status))
{
SetLastErrorByStatus (Status);
return NULL;
}
return hInst;
}
//下面的函数用来从指定dll文件路径构造一个dll搜索路径(完整原代码)
LPWSTR GetDllLoadPath(LPCWSTR lpModule)
{
ULONG Pos = 0, Length = 0;
PWCHAR EnvironmentBufferW = NULL;
LPCWSTR lpModuleEnd = NULL;
UNICODE_STRING ModuleName;
DWORD LastError = GetLastError();
if ((lpModule != NULL) && (wcslen(lpModule) > 2) && (lpModule[1] == ':'))
lpModuleEnd = lpModule + wcslen(lpModule);
else
{
ModuleName = NtCurrentPeb()->ProcessParameters->ImagePathName;
lpModule = ModuleName.Buffer;
lpModuleEnd = lpModule + (ModuleName.Length / sizeof(WCHAR));
}
if (lpModule != NULL)
{
while (lpModuleEnd > lpModule && *lpModuleEnd != L'/' &&
*lpModuleEnd != L'\\' && *lpModuleEnd != L':')
{
--lpModuleEnd;
}
Length = (lpModuleEnd - lpModule) + 1;
}
//看到没,LoadLibrary的dll搜索路径顺序是这样(注意与静态加载时的搜索路径不同)
Length += GetCurrentDirectoryW(0, NULL);
Length += GetDllDirectoryW(0, NULL);
Length += GetSystemDirectoryW(NULL, 0);
Length += GetWindowsDirectoryW(NULL, 0);
Length += GetEnvironmentVariableW(L"PATH", NULL, 0);
EnvironmentBufferW = RtlAllocateHeap(RtlGetProcessHeap(), 0,Length * sizeof(WCHAR));
if (lpModule)
{
RtlCopyMemory(EnvironmentBufferW, lpModule, (lpModuleEnd - lpModule) *sizeof(WCHAR));
Pos += lpModuleEnd - lpModule;
EnvironmentBufferW[Pos++] = L';';
}
Pos += GetCurrentDirectoryW(Length, EnvironmentBufferW + Pos);
EnvironmentBufferW[Pos++] = L';';
Pos += GetDllDirectoryW(Length - Pos, EnvironmentBufferW + Pos);
EnvironmentBufferW[Pos++] = L';';
Pos += GetSystemDirectoryW(EnvironmentBufferW + Pos, Length - Pos);
EnvironmentBufferW[Pos++] = L';';
Pos += GetWindowsDirectoryW(EnvironmentBufferW + Pos, Length - Pos);
EnvironmentBufferW[Pos++] = L';';
Pos += GetEnvironmentVariableW(L"PATH", EnvironmentBufferW + Pos, Length - Pos);
SetLastError(LastError);
return EnvironmentBufferW;
}
NTSTATUS NTAPI
LdrLoadDll (IN PWSTR SearchPath OPTIONAL,
IN PULONG LoadFlags OPTIONAL,
IN PUNICODE_STRING Name,
OUT PVOID *BaseAddress)//也即返回的hModule
{
PPEB Peb = NtCurrentPeb();
Status = LdrpLoadModule(SearchPath, LoadFlags ? *LoadFlags : 0, Name, &Module, BaseAddress);
if (NT_SUCCESS(Status) && (!LoadFlags || 0 == (*LoadFlags & LOAD_LIBRARY_AS_DATAFILE)))
{
if (!(Module->Flags & LDRP_PROCESS_ATTACH_CALLED))
Status = LdrpAttachProcess();//通知一个ProcessAttach消息
}
*BaseAddress = NT_SUCCESS(Status) ? Module->DllBase : NULL;
return Status;
}
下面的函数在每次一个新线程创建时调用,用以调用各个模块的DllMain和tls回调函数
NTSTATUS LdrpAttachThread (VOID)
{
Status = LdrpInitializeTlsForThread();
if (NT_SUCCESS(Status))
{
ModuleListHead = &NtCurrentPeb()->Ldr->InInitializationOrderModuleList;
Entry = ModuleListHead->Flink;
while (Entry != ModuleListHead)//遍历初始化顺序模块表
{
Module = CONTAINING_RECORD(Entry, LDR_DATA_TABLE_ENTRY, InInitializationOrderModuleList);
if (Module->Flags & LDRP_PROCESS_ATTACH_CALLED &&
!(Module->Flags & LDRP_DONT_CALL_FOR_THREADS) &&
!(Module->Flags & LDRP_UNLOAD_IN_PROGRESS))
{
//调用DllMain,注意是DLL_THREAD_ATTACH通知码
LdrpCallDllEntry(Module, DLL_THREAD_ATTACH, NULL);
}
Entry = Entry->Flink;
}
Entry = NtCurrentPeb()->Ldr->InLoadOrderModuleList.Flink;//exe模块
Module = CONTAINING_RECORD(Entry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
LdrpTlsCallback(Module, DLL_THREAD_ATTACH);
}
return Status;
}
下面的函数在主线程创建时调用,用以调用各个模块的DllMain和tls回调函数
NTSTATUS LdrpAttachProcess(VOID)
{
NTSTATUS Status = STATUS_SUCCESS;
ModuleListHead = &NtCurrentPeb()->Ldr->InInitializationOrderModuleList;
Entry = ModuleListHead->Flink;
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;
//调用DllMain,注意是DLL_PROCESS_ATTACH通知码
Result = LdrpCallDllEntry(Module, DLL_PROCESS_ATTACH, (Module->LoadCount ==
LDRP_PROCESS_CREATION_TIME ? 1 : 0));
if (Module->Flags & LDRP_IMAGE_DLL && Module->EntryPoint != 0)
Module->Flags |= LDRP_PROCESS_ATTACH_CALLED|LDRP_ENTRY_PROCESSED;
else
Module->Flags |= LDRP_ENTRY_PROCESSED;
Module->Flags &= ~LDRP_LOAD_IN_PROGRESS;
}
Entry = Entry->Flink;
}
return Status;
}