NT内核创建进程之创建节区分析
流程图:
调试工具:WINDBG
调试对象:WINDOWS SERVER 2003
分析工具:IDA 5.5
NT系统在创建进程时,首先需要根据可执行文件创建节区对象,本文以WINDBG调试WINDOWS 2003 SERVER操作系统创建进程过程,跟踪其中创建节区部分的代码,来说明创建节区的步骤和相关实现。
跟踪的两个函数是MmCreateSection和MiCreateImageFileMap,首先来看一下MmCreateSection的大致流程。
进入函数后,首先进行函数参数的检查,主要是对AllocationAttributes和SectionPageProtection参数的检查。
然后判断FileHandle和FileObject是否有值,如果这两个参数均为NULL值,则表示即没有文件句柄也没有文件对象,函数返回错误。
如果是打开一个可执行应用程序,则参数FileHandle有值(文件对应句柄),FileObject参数为空。
接下来比较参数AllocationAttributes中是否有SEC_IMAGE属性,对于EXE文件AllocationAttributes具有SEC_IMAGE属性。如果有此属性调用CcWaitForUninitializeCacheMap函数,对文件对象创建缓存。
(注:以上部分的反汇编代码比较琐碎,故没有附上对应反汇编代码,详细的反汇编注释可阅读IDB文件)
之后,使用ExAllocatePoolWithTag函数在非换页内存池中申请一个大小为sizeof(CONTROL_AREA) + sizeof(MSUBSECTION)的空间,创建一个占位的控制区对象。真正的控制区对象在MiCreateImageFileMap函数内部创建,这里创建的占位控制区对象在MiCreateImageFileMap函数执行成功之后将会被释放掉。
对应反汇编代码如下:
.
代码:
text:00441FED push 'aCmM' ; Tag .text:00441FF2 push 68h ; NumberOfBytes 为 sizeof(CONTROL_AREA) + sizeof(MSUBSECTION) .text:00441FF4 push esi ; PoolType .text:00441FF5 call _ExAllocatePoolWithTag@12 ; ExAllocatePoolWithTag(x,x,x)
代码:
.text:0044208F lea eax, [ebp+var_GlobalNeeded] .text:00442092 push eax ; GlobalNeeded .text:00442093 push 1 ; PfnLockHeld .text:00442095 push [ebp+var_pFileObject] ; File .text:00442098 call _MiFindImageSectionObject@12 ;
.
代码:
text:00442225 push [ebp+var_NewControlArea] ; InputControlArea .text:00442228 push [ebp+var_ChangeFileReference_pFO] ; File .text:0044222B call _MiInsertImageSectionObject@8 ; MiInsertImageSectionObject(x,x)
如果MiFindImageSectionObject函数没有找到文件关联的控制区,则段对象也是空值,接下来会根据参数AllocationAttributes的取值决定是调用MiCreateImageFileMap函数创建映像文件映射,还是调用MiCreateDataFileMap函数创建数据文件映射。
.
代码:
text:0044238C cmp [ebp+var_AllocationAttributes], esi .text:0044238F lea eax, [ebp+Segment] .text:00442392 jz short var_AllocationAttributes_is_0 ; 对于可执行文件,跳转不成功 .text:00442394 push eax ; _SEGMENT ** .text:00442395 push [ebp+var_ChangeFileReference_pFO] ; _FILE_OBJECT * .text:00442398 call _MiCreateImageFileMap@8 ; MiCreateImageFileMap(x,x) .text:0044239D jmp short loc_4423B4 .text:0044239F ; --------------------------------------------------------------------------- .text:0044239F var_AllocationAttributes_is_0: .text:0044239F push [ebp+var_IgnoreFileSizing] ; IgnoreFileSizing .text:004423A2 push dword ptr [ebp+1Ch] ; AllocationAttributes .text:004423A5 push [ebp+arg_SectionPageProtection] ; SectionPageProtection .text:004423A8 push [ebp+arg_pInputMaximumSize] ; MaximumSize .text:004423AB push eax ; Segment .text:004423AC push [ebp+var_ChangeFileReference_pFO] ; File .text:004423AF call _MiCreateDataFileMap@24 ; MiCreateDataFileMap(x,x,x,x,x,x) .text:004423B4 loc_4423B4:
MiCreateImageFileMap函数首先调用FsRtlGetFileSize函数,获得文件的大小(所有字节数)。
代码:
PAGE:004F8426 push eax ; FileSize PAGE:004F8427 push [ebp+arg_pfobFile] ; FileObject PAGE:004F842A xor esi, esi PAGE:004F842C inc esi PAGE:004F842D xor edi, edi PAGE:004F842F mov [ebp+var_MarkModified], esi PAGE:004F8435 mov [ebp+var_MarkHeaderModified], edi PAGE:004F8438 call _FsRtlGetFileSize@8 ; FsRtlGetFileSize(x,x)
之后,使用MiGetPageForHeader函数,得到一个可用物理地址的页帧号,记为PageFrameNumber。
然后根据页帧号进行转换,得到该页帧号对应在页帧数据库中的地址。在WRK源码中使用一个宏MI_PFN_ELEMENT来完成转换,该宏定义如下:
#define MI_PFN_ELEMENT(index) (&MmPfnDatabase[index])
其对应的汇编代码如下:
代码:
PAGE:004F84B9 mov ecx, dword ptr _MmPfnDatabase PAGE:004F84C2 mov ebx, eax ; eax = PageFrameNumber PAGE:004F84C4 lea eax, [ebx+ebx*2] PAGE:004F84C7 lea eax, [ecx+eax*8] PAGE:004F84CA mov [ebp+var_Pfn1], eax
Pfn1 = MmPfnDatabase + PageFrameNumber* 24
*********************************************************************
关于页帧号数据库
页帧号数据库(PFN DataBase)概述
物理内存被分页,对于32位的CPU来说,每个物理页大小是4K。对于每一个物理页,系统使用一个24字节长的结构来保存它的相关信息,比如该物理页是否已经被使用。为了便于描述,我们把这个结构叫做_MMPFN,页帧号数据库项。页帧号数据库就是一个_MMPFN数组,这个数组的每一项对应一个物理页。比如PfnDataBase数组第0项,对应物理页0,也就是页帧号为0的物理页。第1项,对应物理1,也就是页帧号为1的物理页。系统把PfnDataBase的首地址保存在全局变量MmPfnDatabase中。现在我们来分析一下物理页的页帧号(PFN),物理页的物理地址范围,物理页的页帧号数据库项之间的关系。对于物理页i,它的页帧号是i。由物理地址从i*0x1000到i*0x1000+0xFFF,这4KB物理内存单元组成。对应的页帧号数据库项为第i项,虚拟地址为*MmPfnDatabase+i*0x18。比如在当前物理页为fef8,它的页帧号是fef8,由物理地址0x3000-0x3FFF这4k的物理内存单元组成,当前我的MmPfnDatabase中的值为0x 81800000,即PfnDataBase的首地址为0x 81800000,所以对应的_MMPFN虚拟地址为0x 81800000 + fef8*0x18 = 0x8197E740。
**********************************************************************
使用MiCopyHeaderIfResident函数,将文件映像头复制到之前由MiGetPageForHeader函数得到的物理页面上。MiCopyHeaderIfResident函数执行成功后,返回对应的虚拟地址。
之后通过计算得到该虚拟地址对应的页表地址。对应WRK源码中的宏:
#define MiGetPteAddress(va) ((PMMPTE)(((((ULONG)(va)) >> 12) << 2) + PTE_BASE))
这里的PTE_BASE是一个常量,为0xC0000000。
对应汇编代码如下:
代码:
PAGE:004F861B mov eax, esi ; esi = var_pBase PAGE:004F861D shr eax, 0Ah ; 右移10位,只剩高22位 PAGE:004F8620 and eax, 3FFFFCh ; 保留低22位 ;并且低2位被清掉,所以eax = (esi >> 12) << 2 PAGE:004F8625 mov edx, 1000h PAGE:004F862A sub eax, 40000000h PAGE:004F862F mov [ebp+IoStatus.Information], edx PAGE:004F8632 mov [ebp+var_pBasePte], eax
(0x f7aff000 >> 12 ) << 2 + 0x c0000000 = 0x c03debfc
接下来检查PE文件头是否MZ开头,检查DOS头中NT头偏移量是否合法,及其他各种PE文件是否合法的检查。并且会调用MiVerifyImageHeader函数,检查可执行文件的类型。
之后计算加载整个映像文件需要的内存页数,然后使用ExAllocatePoolWithTag函数,申请控制区(CONTROL_AREA)和子节区(SUBSECTION)空间。字节区空间的大小为sizeof(SUBSECTION)*(节区数+1),对应各个节区和文件映像头。
对应汇编代码如下:
代码:
PAGE:004F88DF inc eax ; eax = var_NumberOfSubsections PAGE:004F88E0 mov [ebp+var_SubsectionsAllocated], eax PAGE:004F88E3 shl eax, 5 ; 左移5位,相当于 x32(即sizeof(SUBSECTION)) PAGE:004F88E6 push 'iCmM' ; Tag PAGE:004F88EB add eax, 38h ; sizeof(_CONTROL_AREA) PAGE:004F88EE push eax ; NumberOfBytes PAGE:004F88EF push edi ; PoolType PAGE:004F88F0 call _ExAllocatePoolWithTag@12 ; ExAllocatePoolWithTag(x,x,x)
PAGE:004F8A86 mov [eax+_CONTROL_AREA.FilePointer], ecx
子节区的控制区对象成员执行申请到的控制区对象:
PAGE:004F8A89 mov [esi+_SUBSECTION.ControlArea], eax
段对象的BasedAddress成员设置为文件映像的基地址:
PAGE:004F8AB8 mov [ecx+_SEGMENT.BasedAddress], eax
设置子节区对象的各成员变量。
代码:
PAGE:004F8C65 sub [ebp+var_NumberOfPtes], eax PAGE:004F8C68 mov eax, [ebp+var_SizeOfHeaders] PAGE:004F8C6B mov edx, [esi+_SUBSECTION.u.SubsectionFlags] PAGE:004F8C6E shr eax, 9 PAGE:004F8C71 mov [esi+_SUBSECTION.NumberOfFullSectors], eax PAGE:004F8C74 mov eax, [ebp+var_SizeOfHeaders] PAGE:004F8C77 and eax, 1FFh ; MMSECTOR_MASK = 1ffh PAGE:004F8C7C shl eax, 14h PAGE:004F8C7F and edx, 0FFE0Eh PAGE:004F8C85 or eax, edx PAGE:004F8C87 or eax, 11h PAGE:004F8C8A and edi, 0FFFFFC3Fh PAGE:004F8C90 mov [esi+_SUBSECTION.u.SubsectionFlags], eax PAGE:004F8C93 or edi, 20h PAGE:004F8C96 xor eax, eax PAGE:004F8C98 mov [ecx+(_SEGMENT.SegmentPteTemplate)], edi PAGE:004F8C9B cmp [esi+_SUBSECTION.PtesInSubsection], eax
代码:
PAGE:004F8CA0 loop: PAGE:004F8CA0 mov ecx, [ebp+var_ReturnStatus] ; var_ReturnStatus = 0 PAGE:004F8CA3 cmp ecx, [ebp+var_SizeOfHeaders] PAGE:004F8CA6 mov edx, [ebp+arg_ppSegment] ; arg_ppSegment = _SEGMENT.PrototypePte PAGE:004F8CA9 sbb ecx, ecx PAGE:004F8CAB add [ebp+var_ReturnStatus], 1000h PAGE:004F8CB2 add [ebp+arg_ppSegment], 4 ; 指针加1 PAGE:004F8CB6 and ecx, edi ; edi = _SEGMENT.SegmentPteTemplate PAGE:004F8CB8 inc eax PAGE:004F8CB9 mov [edx+_SEGMENT.ControlArea], ecx PAGE:004F8CBB cmp eax, [esi+_SUBSECTION.PtesInSubsection] PAGE:004F8CBE jb short loop ; var_ReturnStatus = 0
代码:
PAGE:004F8F68 mov [esi+_SUBSECTION.NextSubsection], eax PAGE:004F8F6B mov esi, eax PAGE:004F8F6D mov eax, [ebp+var_pControlArea] PAGE:004F8F70 mov [esi+_SUBSECTION.ControlArea], eax ; esi 指向下一个_SUBSECTION PAGE:004F8F72 mov [esi+_SUBSECTION.NextSubsection], ebx ; ebx = 0 PAGE:004F8F75 mov [esi+_SUBSECTION.UnusedPtes], ebx PAGE:004F8F78 mov eax, [edx-8] ; eax = VirtualAddress PAGE:004F8F7B add eax, [ebp+var_ImageBase] PAGE:004F8F7E cmp [ebp+var_NextVa], eax PAGE:004F8F81 jnz error4 PAGE:004F8F87 cmp edi, ebx ; SectionVirtualSize 是否为 0 PAGE:004F8F89 jz error4 PAGE:004F8F8F mov eax, [ebp+var_ImageAlignment] PAGE:004F8F92 lea eax, [edi+eax-1] PAGE:004F8F96 cmp eax, edi ; edi = SectionVirtualSize PAGE:004F8F98 jbe error5 PAGE:004F8F9E shr eax, 0Ch PAGE:004F8FA1 and eax, [ebp+var_B0] ; var_B0 = fffff PAGE:004F8FA7 cmp eax, [ebp+var_NumberOfPtes] ; 54 < 6a PAGE:004F8FAA mov [esi+_SUBSECTION.PtesInSubsection], eax PAGE:004F8FAD ja error6 PAGE:004F8FB3 sub [ebp+var_NumberOfPtes], eax PAGE:004F8FB6 mov [esi+_SUBSECTION.u.LongFlags], ebx PAGE:004F8FB9 mov ecx, [edx] ; PointerToRawData PAGE:004F8FBB mov ebx, [ebp+var_FileAlignment] ; FileAlignment = fff PAGE:004F8FC1 shr ecx, 9 ; 9 == MMSECTOR_SHIFT PAGE:004F8FC4 mov [esi+_SUBSECTION.StartingSector], ecx PAGE:004F8FC7 mov edi, [edx] ; edi = PointerToRawData PAGE:004F8FC9 mov eax, [edx-4] ; SizeOfRawData PAGE:004F8FCC add eax, edi PAGE:004F8FCE add eax, [ebp+var_FileAlignment] PAGE:004F8FD4 not ebx PAGE:004F8FD6 and eax, ebx PAGE:004F8FD8 cmp eax, edi ; eax = EndingAddress PAGE:004F8FDA jb Error_36 PAGE:004F8FE0 mov edi, eax PAGE:004F8FE2 and eax, 1FFh ; MMSECTOR_MASK = 1ffh PAGE:004F8FE7 shl eax, 14h PAGE:004F8FEA shr edi, 9 PAGE:004F8FED mov dword ptr [esi+_SUBSECTION.u.Flags], eax PAGE:004F8FF0 sub edi, ecx PAGE:004F8FF2 mov [ebp+arg_pfobFile], eax PAGE:004F8FF5 mov eax, [ebp+arg_ppSegment] PAGE:004F8FF8 mov [esi+_SUBSECTION.SubsectionBase], eax PAGE:004F8FFB mov [esi+_SUBSECTION.NumberOfFullSectors], edi
各主要对象之间的关系:
参考资料: 作者:
加密与解密三 段钢(编著)
Windows 内核原理与实现 潘爱民
Windows 内核情景分析 毛德操
深入解析Windows操作系统 潘爱民(译)
一点心得体会:
内核中最重要的就是数据结构和数据关系,数据关系决定了数据结构的内容,所以分析内核首先要从数据结构和数据关系着手。
由于时间仓促,加之笔者水平有限,分析的只是一个梗概,还有很多相关的内容没有分析到,也恳请高手对文中的错漏给予指正和补充。
科锐5期学员 孙年忠