windows核心情景分析---程序執行緒
本篇主要講述程序的啟動過程、執行緒的排程與切換、程序掛靠
一、程序的啟動過程:
BOOL CreateProcess
(
LPTSTR lpCommandLine, // command line string
LPVOID lpEnvironment, // new environment block
LPCTSTR lpCurrentDirectory, // current directory name
LPSTARTUPINFO lpStartupInfo
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傳參給KiThreadS