概述
相比于Linux中的进程管理,即创建一个线程的同时创建一个进程。而Windows中却不是这样,它是先创建一个进程作为容器,然后创建第一个线程,也就是对于CreateProcess而言,它先调用NtCreateProcess创建进程,然后调用NtCreateThread创建进程的第一个线程。
Windows进程的用户空间
一些重要的宏如下
#define MM_HIGHEST_USER_ADDRESS *MmHighestUserAddress
#define MM_USER_PROBE_ADDRESS *MmUserProbeAddress
#define MM_LOWEST_USER_ADDRESS 0x10000
对于MmHighestUserAddress和MmUserProbeAddress而言,他们是在系统初始化的时候被赋值的,即MmUserProbeAddress其值为MmSystemRangeStart-0x10000,而MmHighestUserAddress为MmUserProbeAddress-1,也就是严格来讲,从0x8000-0000开始处的下沿64kb也是属于用户不可访问的范围,并且之前我们在系统调用中提到过有一个SharedUserData是一块“飞地”的存在,它虽然在系统内核空间中,但却是可以访问到的。其大小也是64kb,位置是在0xffdf0000
用户空间格局的创建
用户空间的创建及其大的格局基本上是通过内核函数MmCreateProcessAddressSpace实现
其流程大概是
- 调用MmInitializeAddressSpace初始化MADDRESS_SPACE结构
- 通过调用CreateMemoryArea将0x80000000下沿64kb设置成PAGE_NOACCESS属性
- 通过调用CreateMemoryArea将SharedUserData所在页面设置为PAGE_EXECUTE_READ属性,所以实际上只有一个页面的大小的飞地,其余部分设置成PAGE_NOACCESS
- 通过MmMapViewOfSection将该可执行映像装入用户空间,其起始地址正是ImageBase,接着初始化一些域 例如文件名 并且填充到EPROCESS
用户空间最重要的自然是可执行映像,在进行NtCreateProcess之前,调用者已经为目标映像创建好一个Section,即文件映射区,现在通过NtCreateProcess将它映射到用户空间中,由于此时空间基本为空,根据PE文件的头部信息,我们总能加载到期望地址上去。
当然除了重要的EXE映像,还要许多重要的DLL文件,例如ntdll.dll,这是一个非常重要的DLL,系统调用的时候也需要用到这个DLL。Windows内核在初始化阶段首次需要这个DLL的时候为其创建文件映射区,以后凡是用到这个DLL只要映射这个文件映射区即可。ntdll的映射是由PspCreateProcess->PspMapSystemDll完成的。
ntdll的加载
NTSTATUS
NTAPI
INIT_FUNCTION
PspMapSystemDll(IN PEPROCESS Process,
IN PVOID *DllBase,
IN BOOLEAN UseLargePages)
{
NTSTATUS Status;
LARGE_INTEGER Offset = {
{
0, 0}};
SIZE_T ViewSize = 0;
PVOID ImageBase = 0;
/* Map the System DLL */
Status = MmMapViewOfSection(PspSystemDllSection, //调用MmMapViewOfSection将ntdll映射到ImageBase处 类型是可读写
Process,
(PVOID*)&ImageBase,
0,
0,
&Offset,
&ViewSize,
ViewShare,
0,
PAGE_READWRITE);
if (Status != STATUS_SUCCESS)
{
/* Normalize status code */
Status = STATUS_CONFLICTING_ADDRESSES;
}
/* Write the image base and return status */
if (DllBase) *DllBase = ImageBase; //DllBase是ntdll的加载基址
return Status;
}
这里的ntdll加载进内存是通过MmMapViewOfSection将该共享文件对象映射进内存中,这里也就是用户空间里。
PEB的建立
PEB的建立是通过PspCreateProcess->MmCreatePeb
NTSTATUS
NTAPI
MmCreatePeb(IN PEPROCESS Process,
IN PINITIAL_PEB InitialPeb,
OUT PPEB *BasePeb)
{
PPEB Peb = NULL;
LARGE_INTEGER SectionOffset;
SIZE_T ViewSize = 0;
PVOID TableBase = NULL;
PIMAGE_NT_HEADERS NtHeaders;
PIMAGE_LOAD_CONFIG_DIRECTORY ImageConfigData;
NTSTATUS Status;
USHORT Characteristics;
KAFFINITY ProcessAffinityMask = 0;
SectionOffset.QuadPart = (ULONGLONG)0;
*BasePeb = NULL;
//
// Attach to Process
//
KeAttachProcess(&Process->Pcb);
//
// Map NLS Tables
//
Status = MmMapViewOfSection(ExpNlsSectionPointer, //映射NLS码表
(PEPROCESS)Process,
&TableBase,
0,
0,
&SectionOffset,
&ViewSize,
ViewShare,
MEM_TOP_DOWN,
PAGE_READONLY);
DPRINT("NLS Tables at: %p\n", TableBase);
if (!NT_SUCCESS(Status))
{
/* Cleanup and exit */
KeDetachProcess();
return Status;
}
//
// Allocate the PEB
//
Status = MiCreatePebOrTeb(Process, sizeof(PEB), (PULONG_PTR)&Peb); //为Peb建立空间的主要函数
DPRINT("PEB at: %p\n", Peb);
if (!NT_SUCCESS(Status))
{
/* Cleanup and exit */
KeDetachProcess();
return Status;
}
//
// Use SEH in case we can't load the PEB
//
_SEH2_TRY
{
//
// Initialize the PEB
//
RtlZeroMemory(Peb, sizeof(PEB));
//
// Set up data
//
Peb->ImageBaseAddress = Process->SectionBaseAddress; //EPROCESS的SectionBaseAddress赋值给了ImageBaseAddress
Peb->InheritedAddressSpace = InitialPeb->InheritedAddressSpace; //对peb进行适当的初始化
Peb->Mutant = InitialPeb->Mutant;
Peb->ImageUsesLargePages = InitialPeb->ImageUsesLargePages;
//
// NLS
//
Peb->AnsiCodePageData = (PCHAR)TableBase + ExpAnsiCodePageDataOffset;
Peb->OemCodePageData = (PCHAR)TableBase + ExpOemCodePageDataOffset;
Peb->UnicodeCaseTableData = (PCHAR)TableBase + ExpUnicodeCaseTableDataOffset;
//
// Default Version Data (could get changed below)
//
Peb->OSMajorVersion = NtMajorVersion; //这些是从SharedUserData中获取的 根据上面的注释 是只读无法更改的
Peb->OSMinorVersion = NtMinorVersion;
Peb->OSBuildNumber = (USHORT)(NtBuildNumber & 0x3FFF);
Peb->OSPlatformId = 2; /* VER_PLATFORM_WIN32_NT */
Peb->OSCSDVersion = (USHORT)CmNtCSDVersion;
//
// Heap and Debug Data
//
Peb->NumberOfProcessors = KeNumberProcessors; //KeNumberProcessors即处理器的数量也给了Peb
Peb->BeingDebugged = (BOOLEAN)(Process->DebugPort != NULL); //这里给反调试做了依据!!! BeingDebugged是根据DebugPort而来的
Peb->NtGlobalFlag = NtGlobalFlag;
/*Peb->HeapSegmentReserve = MmHeapSegmentReserve;
Peb->HeapSegmentCommit = MmHeapSegmentCommit;
Peb->HeapDeCommitTotalFreeThreshold = MmHeapDeCommitTotalFreeThreshold;
Peb->HeapDeCommitFreeBlockThreshold = MmHeapDeCommitFreeBlockThreshold;
Peb->CriticalSectionTimeout = MmCriticalSectionTimeout;
Peb->MinimumStackCommit = MmMinimumStackCommitInBytes;
*/
Peb->MaximumNumberOfHeaps = (PAGE_SIZE - sizeof(PEB)) / sizeof(PVOID); //堆的最大数量
Peb->ProcessHeaps = (PVOID*)(Peb + 1); //可以看到Windows的堆指针是紧邻Peb之后的 并且是堆的一个指针数组
//
// Session ID
//
if (Process->Session) Peb->SessionId = 0; // MmGetSessionId(Process);
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
//
// Fail
//
KeDetachProcess();
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
//
// Use SEH in case we can't load the image
//
_SEH2_TRY
{
//
// Get NT Headers
//
NtHeaders = RtlImageNtHeader(Peb->ImageBaseAddress); //获取Pe映像头
Characteristics = NtHeaders->FileHeader.Characteristics; //获取Pe映像信息
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
//
// Fail
//
KeDetachProcess();
_SEH2_YIELD(return STATUS_INVALID_IMAGE_PROTECT);
}
_SEH2_END;
//
// Parse the headers
//
if (NtHeaders)
{
//
// Use SEH in case we can't load the headers
//
_SEH2_TRY
{
//
// Get the Image Config Data too
//
ImageConfigData = RtlImageDirectoryEntryToData(Peb->ImageBaseAddress,
TRUE,
IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG,
(PULONG)&ViewSize);
if (ImageConfigData)
{
//
// Probe it
//
ProbeForRead(ImageConfigData,
sizeof(IMAGE_LOAD_CONFIG_DIRECTORY),
sizeof(ULONG));
}
//
// Write subsystem data
//
Peb->ImageSubsystem = NtHeaders->OptionalHeader.Subsystem;
Peb->ImageSubsystemMajorVersion = NtHeaders->OptionalHeader.MajorSubsystemVersion;
Peb->ImageSubsystemMinorVersion = NtHeaders->OptionalHeader.MinorSubsystemVersion;
//
// Check for version data
//
if (NtHeaders->OptionalHeader.Win32VersionValue)
{
//
// Extract values and write them
//
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;
/* Process CSD version override */
if ((ImageConfigData) && (ImageConfigData->CSDVersion))
{
/* Take the value from the image configuration directory */
Peb->OSCSDVersion = ImageConfigData->CSDVersion;
}
}
/* Process optional process affinity mask override */
if ((ImageConfigData) && (ImageConfigData->ProcessAffinityMask))
{
/* Take the value from the image configuration directory */
ProcessAffinityMask = ImageConfigData->ProcessAffinityMask;
}
//
// Check if this is a UP image
if (Characteristics & IMAGE_FILE_UP_SYSTEM_ONLY)
{
//
// Force it to use CPU 0
//
/* FIXME: this should use the MmRotatingUniprocessorNumber */
Peb->ImageProcessAffinityMask = 0;
}
else
{
//
// Whatever was configured
//
Peb->ImageProcessAffinityMask = ProcessAffinityMask;
}
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
//
// Fail
//
KeDetachProcess();
_SEH2_YIELD(return STATUS_INVALID_IMAGE_PROTECT);
}
_SEH2_END;
}
//
// Detach from the Process
//
KeDetachProcess(); //撤销挂靠
*BasePeb = Peb;
return STATUS_SUCCESS;
}
这里首先映射NLS码表,这个是关于编码的,我们并不关心,我们的重心放到MiCreatePebOrTeb,这个函数为Peb建立了空间。
接着分配好空间就是对Peb进行初始化,首先是ImageBase的从EPROCESS到Peb的一个转移。然后是比较重要的就是Peb的BeingDebugged的来源是EPROCESS中的DebugPort域,当被调试的时候,DebugPort的值为-1,此时!=null,所以BeingDbugged的值就是这么来的。另外比较重要的就是Heap,它最大数量就是(PAGE_SIZE - sizeof(PEB)) / sizeof(PVOID),并且堆数组的起始地址是ProcessHeaps,它的定义是**(PVOID*)(Peb + 1)**,也就是紧邻在Peb之后,是Windows堆的指针数组。
顺便看一下MiCreatePebOrTeb
其核心是一个循环,对于peb而言,它总能在第一次循环的时候就将其数据结构安装到期望地址上。对于之后的Teb而言,每次是第2,3,4…循环之后能成功安装,这样多线程的Teb建立问题就不需要思虑了,即使某个线程结束了其生命,下一次新的线程建立teb会填充到那个位置。并且对于进程而言不存在堆栈问题,堆栈是针对于线程而言!
ProcessParameters的生成
PEB中有个字段ProcessParameters,这个字段指向的是本进程的"参数块",注意,进程的建立也是存在参数的!!!
是一个RTL_USER_PROCESS_PARAMETERS结构。
其设置是由BasepInitializeEnvironment处理的
[CreateProcessW->CreateProcessInternalW->BasepInitializeEnvironment]
NTSTATUS
WINAPI
BasepInitializeEnvironment(HANDLE ProcessHandle,
PPEB Peb,
LPWSTR ApplicationPathName,
LPWSTR lpCurrentDirectory,
LPWSTR lpCommandLine,
PWSTR lpEnvironment,
SIZE_T EnvSize,
LPSTARTUPINFOW StartupInfo,
DWORD CreationFlags,
BOOL InheritHandles)
{
WCHAR FullPath[MAX_PATH];
LPWSTR Remaining;
LPWSTR DllPathString;
PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
PRTL_USER_PROCESS_PARAMETERS RemoteParameters = NULL;
UNICODE_STRING DllPath, ImageName, CommandLine, CurrentDirectory;
UINT RetVal;
NTSTATUS Status;
PWCHAR ScanChar;
ULONG EnviroSize;
SIZE_T Size;
UNICODE_STRING Desktop, Shell, Runtime, Title;
PPEB OurPeb = NtCurrentPeb();
PWSTR Environment = lpEnvironment;
DPRINT("BasepInitializeEnvironment\n");
/* Get the full path name */
RetVal = GetFullPathNameW(ApplicationPathName,
MAX_PATH,
FullPath,
&Remaining);
DPRINT("ApplicationPathName: %S, FullPath: %S\n", ApplicationPathName,
FullPath);
/* Get the DLL Path */
DllPathString = BasepGetDllPath(FullPath, Environment); //获取dll路径
/* Initialize Strings */
RtlInitUnicodeString(&DllPath, DllPathString);
RtlInitUnicodeString(&ImageName, FullPath); //获取可执行映像路径
RtlInitUnicodeString(&CommandLine, lpCommandLine); //获取命令行参数
RtlInitUnicodeString(&CurrentDirectory, lpCurrentDirectory); //获取当前目录
/* Initialize more Strings from the Startup Info */
//这是对启动参数信息的复制 主要有视窗位置 标题等
if (StartupInfo->lpDesktop)
{
RtlInitUnicodeString(&Desktop, StartupInfo->lpDesktop);
}
else
{
RtlInitUnicodeString(&Desktop, L"");
}