首页
社区
课程
招聘
[原创]Windows内核学习笔记之进程(下)
2021-12-16 16:20 17075

[原创]Windows内核学习笔记之进程(下)

2021-12-16 16:20
17075

上篇:Windows内核学习笔记之进程(上)

五.句柄与句柄表

1.句柄

Windows执行体实现了一套对象机制来管理各种资源或实体。每种对象都有一个类型对象,类型对象定义了该类对象的一些特性和方法。对象管理器也定义了一个全局名字空间,提供了根据名称来解析对象的同一机制。类型对象通过提供自定义的Parse方法可以扩展此名字空间。对象管理器中的对象是执行体对象,它位于系统空间,考虑到安全性,在进程空间不能直接通过地址来引用它们。

在Windows系统中需要使用到句柄(handle)来管理进程中的对象引用。当一个进程利用名称来创建或打开一个对象时,将获得一个句柄,该句柄指向所创建或打开的对象。以后,该进程无须使用名称来引用对象,使用此句柄即可访问。这样即保证了安全性,也提高了引用对象的效率。当两个应用程序以共享方式打开了同一个文件,那么,它们将分别得到各种的句柄,且都可以通过句柄操作该文件。尽管两个应用程序得到的句柄的值并不相同,但是这两个句柄所指的文件却是同一个。因此,句柄只是一个对象引用,同一个对象在不同的环境下可能有不同的引用(句柄)值。

在Windows系统中,句柄是进程范围内的对象引用,换句话说,句柄仅在一个进程范围内才有效。一个进程的句柄传递给另一个进程后,句柄值将不再有效

2.进程句柄表

实际上,Windows支持的句柄是一个索引,指向该进程句柄表中的一个表项。进程句柄表由EPROCESS结构中的ObjectTable域来指向。句柄表第一项索引是4,第二项索引是8,依次类推。一个进程的句柄表包含了所有已被该进程打开的对象的指针。ObjectTable的类型为HANDLE_TABLE,该结构定义如下:

kd> dt _HANDLE_TABLE
ntdll!_HANDLE_TABLE
   +0x000 TableCode        : Uint4B
   +0x004 QuotaProcess     : Ptr32 _EPROCESS
   +0x008 UniqueProcessId  : Ptr32 Void
   +0x00c HandleTableLock  : [4] _EX_PUSH_LOCK
   +0x01c HandleTableList  : _LIST_ENTRY
   +0x024 HandleContentionEvent : _EX_PUSH_LOCK
   +0x028 DebugInfo        : Ptr32 _HANDLE_TRACE_DEBUG_INFO
   +0x02c ExtraInfoPages   : Int4B
   +0x030 FirstFree        : Uint4B
   +0x034 LastFree         : Uint4B
   +0x038 NextHandleNeedingPool : Uint4B
   +0x03c HandleCount      : Int4B
   +0x040 Flags            : Uint4B
   +0x040 StrictFIFO       : Pos 0, 1 Bit
偏移名称作用
0x000TableCode指向句柄表的存储结构
0x004QuotaProcess句柄表的内存资源记录在此进程中
0x008UniqueProcessId创建进程的ID,用于回调函数
0x00CHandleTableLock句柄表锁,仅在句柄表扩展时使用
0x01CHandleTableList所有句柄表形成一个链表,聊表头为全局变量HandleTableListHead
0x024HandleContentionEvent若在访问句柄时发生竞争,则在此推锁上等待
0x02CDebugInfo调试信息,仅当调试句柄时才有意义
0x030ExtraInfoPages审计信息所占用的页面数量
0x034FirstFree空闲链表表头句柄索引
0x038
NextHandleNeedingPool下一次句柄表扩展的起始句柄索引
0x03C
HandleCount正在使用的句柄表项的数量
0x040
Flags标志域
0x040
StrictFIFO是否使用FIFO风格的重用,即先释放先重用

TableCode域是一个指针,指向句柄表最高层表项页面,它的低2位代表了当前句柄表的层数,具体情况如下

  • 0:句柄表只有一层,此时进程最多容纳512个句柄

  • 1:句柄表有两层,此时进程最多可容易512 * 1024 个句柄

  • 2:句柄表有三层,三层树结构最多可容纳的句柄数是512 * 1024 * 1024,但是Windows执行体限定每个进程的句柄数不得超过2^24=16777246

下图显示了这三种情形,实际上,在每个最底层句柄表页面中,第一个句柄表项都有特殊用途,真正供进程使用的句柄表项是511个

最低层句柄表每一项所指的都是一个句柄表项,句柄表项结构为HANDLE_TABLE_ENTRY,占8个字节,定义如下

kd> dt _HANDLE_TABLE_ENTRY
nt!_HANDLE_TABLE_ENTRY
   +0x000 Object           : Ptr32 Void
   +0x000 ObAttributes     : Uint4B
   +0x000 InfoTable        : Ptr32 _HANDLE_TABLE_ENTRY_INFO
   +0x000 Value            : Uint4B
   +0x004 GrantedAccess    : Uint4B
   +0x004 GrantedAccessIndex : Uint2B
   +0x006 CreatorBackTraceIndex : Uint2B
   +0x004 NextFreeTableEntry : Int4B

Object指针所指的就是句柄所代表的内核对象,它的最低3位有特殊的含义

位数名称含义
第0位OBJ_PROTECT_CLOSE表示调用者是否允许关闭该句柄
第1位OBJ_INHERIT指示该进程所创建的子进程是否可以继承该句柄
第2位OBJ_AUDIT_OBJECT_CLOSE指示关闭该对象时是否产生一个审计事件

因此,想要获得句柄对象的地址需要将Object的低3位清0,但是此时所得到的对象地址指向的是对象头,偏移0x18的地址才是对象的真正地址。

接下来通过WinDbg来查找句柄对象来验证上述内容,首先使用如下代码来创建test进程,而该进程则是要得到进程PID为1488的进程句柄

#include <cstdio>
#include <windows.h>
 
int main()
{
        HANDLE handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 1812);
 
        printf("%X\n", handle);

	       system("pause");
        return 0;
}

此时这个进程的PID是一个记事本进程的PID

在WinDbg中查找test进程的EPROCESS地址

通过该地址来获得ObjectTable的值,该值保存了此进程HANDLE_TABLE的地址

根据该值来获得TableCode

该值的低2位为0,所以这是一个单层句柄表结构,所以TableCode所指的地址保存了512个HADLE_TABLE_ENTRY。而要得知打开的记事本进程对象是在句柄表中的位置,就需要通过句柄值进行索引,下图可以知道此次的句柄值为0x7E8,由于句柄的索引是从4开始每次递增4,所以将7E8除以4得到1FA就是句柄表的索引

有了索引就可以在句柄表中查找对应的句柄对应的对象头地址

此时将低3位清0就得到了对象头地址,偏移0x18处就是句柄对应的内核对象

kd> dt 0x81bd3330 + 0x18 _EPROCESS
ntdll!_EPROCESS
   +0x000 Pcb              : _KPROCESS
   +0x06c ProcessLock      : _EX_PUSH_LOCK
   +0x070 CreateTime       : _LARGE_INTEGER 0x1d7f193`acd47dd4
   +0x078 ExitTime         : _LARGE_INTEGER 0x0
   +0x080 RundownProtect   : _EX_RUNDOWN_REF
   +0x084 UniqueProcessId  : 0x00000714 Void
   +0x088 ActiveProcessLinks : _LIST_ENTRY [ 0x81d5a6c0 - 0x81b41c10 ]
   +0x090 QuotaUsage       : [3] 0xa78
   +0x09c QuotaPeak        : [3] 0xaf0
   +0x0a8 CommitCharge     : 0x192
   +0x0ac PeakVirtualSize  : 0x2451000
   +0x0b0 VirtualSize      : 0x1fbe000
   +0x0b4 SessionProcessLinks : _LIST_ENTRY [ 0x81d5a6ec - 0x81b41c3c ]
   +0x0bc DebugPort        : (null) 
   +0x0c0 ExceptionPort    : 0xe138c5a8 Void
   +0x0c4 ObjectTable      : 0xe2e92558 _HANDLE_TABLE
   +0x0c8 Token            : _EX_FAST_REF
   +0x0cc WorkingSetLock   : _FAST_MUTEX
   +0x0ec WorkingSetPage   : 0x9cf3
   +0x0f0 AddressCreationLock : _FAST_MUTEX
   +0x110 HyperSpaceLock   : 0
   +0x114 ForkInProgress   : (null) 
   +0x118 HardwareTrigger  : 0
   +0x11c VadRoot          : 0x81b411b8 Void
   +0x120 VadHint          : 0x81953f68 Void
   +0x124 CloneRoot        : (null) 
   +0x128 NumberOfPrivatePages : 0xce
   +0x12c NumberOfLockedPages : 0
   +0x130 Win32Process     : 0xe3b2ce68 Void
   +0x134 Job              : (null) 
   +0x138 SectionObject    : 0xe31fe450 Void
   +0x13c SectionBaseAddress : 0x01000000 Void
   +0x140 QuotaBlock       : 0x819f39d0 _EPROCESS_QUOTA_BLOCK
   +0x144 WorkingSetWatch  : (null) 
   +0x148 Win32WindowStation : 0x0000003c Void
   +0x14c InheritedFromUniqueProcessId : 0x000004e0 Void
   +0x150 LdtInformation   : (null) 
   +0x154 VadFreeHint      : (null) 
   +0x158 VdmObjects       : (null) 
   +0x15c DeviceMap        : 0xe17ce150 Void
   +0x160 PhysicalVadList  : _LIST_ENTRY [ 0x81bd34a8 - 0x81bd34a8 ]
   +0x168 PageDirectoryPte : _HARDWARE_PTE_X86
   +0x168 Filler           : 0
   +0x170 Session          : 0xf89d3000 Void
   +0x174 ImageFileName    : [16]  "notepad.exe"

偏移0x174的地址保存了进程对象的名称为notepad.exe,证明上述内容正确。

3.创建句柄

从上面的内容可以知道,要为一个进程创建句柄并将其插入句柄表的时候,需要为句柄准备一个临时的HANDLE_TABLE_ENTRY数据结构,使其指向这个对象的头部,然后将该结构插入到相应的句柄表中。这里就有两个问题,一个是填充HANDLE_TABLE_ENTRY数据结构,用来表示要插入的对象,另一个是在相应的句柄表中将其插入。

实现句柄插创建的函数为ObpCreateHandle,在函数最开始就是取出对象的对象类型和对象头赋值到相应的变量

PAGE:00496238 ; int __stdcall ObpCreateHandle(ULONG OpenReason, PVOID Object, POBJECT_TYPE ExceptedObjectType, PACCESS_STATE AccessState, ULONG ObjectPointerBias, ULONG Attributes, BOOLEAN DirectoryLocked, KPROCESSOR_MODE AccessMode, PVOID *ReferencedNewObject, PHANDLE Handle)
PAGE:00496238 _ObpCreateHandle@40 proc near           ; CODE XREF: ExCreateCallback(x,x,x,x)-4EE8B↓p
PAGE:00496238                                         ; ObOpenObjectByName(x,x,x,x,x,x,x)+163↓p ...
PAGE:00496238
PAGE:00496238 var_30          = dword ptr -30h
PAGE:00496238 var_HandleTableEntry= dword ptr -18h
PAGE:00496238 var_14          = dword ptr -14h
PAGE:00496238 var_ObjectType  = dword ptr -10h
PAGE:00496238 var_ObjectHeader= dword ptr -0Ch
PAGE:00496238 var_8           = dword ptr -8
PAGE:00496238 var_KernelHandle= byte ptr -2
PAGE:00496238 var_1           = byte ptr -1
PAGE:00496238 OpenReason      = dword ptr  8
PAGE:00496238 Object          = dword ptr  0Ch
PAGE:00496238 ExceptedObjectType= dword ptr  10h
PAGE:00496238 AccessState     = dword ptr  14h
PAGE:00496238 ObjectPointerBias= dword ptr  18h
PAGE:00496238 HandleAttributes= dword ptr  1Ch
PAGE:00496238 DirectoryLocked = dword ptr  20h
PAGE:00496238 AccessMode      = byte ptr  24h
PAGE:00496238 ReferencedNewObject= dword ptr  28h
PAGE:00496238 Handle          = dword ptr  2Ch
PAGE:00496238                 mov     edi, edi
PAGE:0049623A                 push    ebp
PAGE:0049623B                 mov     ebp, esp
PAGE:0049623D                 sub     esp, 30h
PAGE:00496240                 mov     eax, [ebp+Object] ; 将对象地址赋给eax
PAGE:00496243                 push    ebx
PAGE:00496244                 mov     ebx, [eax-10h]  ; 获取对象头_OBJECT_TYPE类型的Type
PAGE:00496247                 push    esi
PAGE:00496248                 add     eax, 0FFFFFFE8h ; 减去0x18获得对象头
PAGE:0049624B                 push    edi
PAGE:0049624C                 xor     edi, edi
PAGE:0049624E                 cmp     [ebp+ExceptedObjectType], edi
PAGE:00496251                 mov     [ebp+var_1], 0
PAGE:00496255                 mov     [ebp+var_KernelHandle], 0
PAGE:00496259                 mov     [ebp+var_ObjectHeader], eax
PAGE:0049625C                 mov     [ebp+var_ObjectType], ebx

接下来会判断传入的属性是否具有内核标记并把对象头赋给局部变量,这样就构造好了要插入句柄表的句柄表项,接下来就是要选择合适的句柄表

PAGE:00496265                 mov     esi, [ebp+HandleAttributes] ; 是否具有OBJ_KERNEL_HANDLE标记
PAGE:00496268                 test    esi, OBJ_KERNEL_HANDLE
PAGE:0049626E                 mov     [ebp+var_HandleTableEntry], eax ; 将对象头赋给局部变量
PAGE:00496271                 jnz     loc_4A1E53

当不具有内核标记时候,会从当前进程中获取要插入的句柄表

PAGE:00496277                 mov     eax, large fs:124h ; 获得当前进程的ETHREAD赋给eax
PAGE:0049627D                 mov     eax, [eax+44h]  ; 获得当前进程的EPROCESS赋给eax
PAGE:00496280                 mov     eax, [eax+0C4h] ; 获取ObjectTable赋给eax
PAGE:00496286                 mov     [ebp+var_HandleTable], eax

如果具备内核标记,则选择内核句柄表作为要插入的句柄表,并且会判断当前进程是否是系统进程,如果不是则会调用函数KiStackAttachProcess附加上去

PAGE:004A1E53 loc_4A1E53:                           
PAGE:004A1E53                 mov     eax, _ObpKernelHandleTable ; 将内核句柄表地址赋给eax
PAGE:004A1E58                 mov     [ebp+var_HandleTable], eax ; 将内核句柄表地址作为要插入的句柄表
PAGE:004A1E5B                 mov     [ebp+var_KernelHandle], 1
PAGE:004A1E5F                 mov     eax, large fs:124h ; 取出当前线程的ETHREAD
PAGE:004A1E65                 mov     ecx, _PsInitialSystemProcess
PAGE:004A1E6B                 cmp     [eax+44h], ecx  ; 判断当前进程是否是系统进程
PAGE:004A1E6E                 jz      loc_496289
PAGE:004A1E74                 lea     eax, [ebp+var_30]
PAGE:004A1E77                 push    eax            
PAGE:004A1E78                 push    ecx          
PAGE:004A1E79                 call    _KeStackAttachProcess@8 ; 附加到系统进程上
PAGE:004A1E7E                 mov     [ebp+var_1], 1
PAGE:004A1E82                 jmp     loc_496289

选择了合适的句柄表以及构造好了句柄表项以后,就要调用ExCreateHandle来将句柄表项插入到句柄表中,该函数会将要插入的句柄表地址和句柄表项地址依次作为参数入栈,返回值为相应的句柄

PAGE:00496326                 lea     eax, [ebp+var_HandleTableEntry]
PAGE:00496329                 push    eax             ; 要插入的句柄表项地址
PAGE:0049632A                 push    [ebp+var_HandleTable] ; 要插入的句柄表地址
PAGE:0049632D                 call    _ExCreateHandle@8 
PAGE:00496332                 mov     ebx, eax
PAGE:00496334                 test    ebx, ebx
PAGE:00496336                 jz      loc_52879C

在ExCreateHandle中会调用ExpAllocateHandleTableEntry,该函数的第一个参数是要插入的句柄表项地址,第二个参数用来保存得到的句柄,返回值为新分配的句柄表项地址

PAGE:00498B7B ; __stdcall ExCreateHandle(x, x)
PAGE:00498B7B _ExCreateHandle@8 proc near             
PAGE:00498B7B                                         
PAGE:00498B7B
PAGE:00498B7B var_Handle      = dword ptr -4
PAGE:00498B7B arg_HandleTable = dword ptr  8
PAGE:00498B7B arg_HandleTableEntry= dword ptr  0Ch
PAGE:00498B7B                 mov     edi, edi
PAGE:00498B7D                 push    ebp
PAGE:00498B7E                 mov     ebp, esp
PAGE:00498B80                 push    ecx
PAGE:00498B81                 and     [ebp+var_Handle], 0
PAGE:00498B85                 push    ebx
PAGE:00498B86                 push    esi
PAGE:00498B87                 mov     esi, [ebp+arg_HandleTable]
PAGE:00498B8A                 lea     eax, [ebp+var_Handle]
PAGE:00498B8D                 push    eax
PAGE:00498B8E                 push    esi
PAGE:00498B8F                 call    _ExpAllocateHandleTableEntry@8 
PAGE:00498B94                 mov     ebx, eax        ; 将新分配的表项地址赋给ebx
PAGE:00498B96                 test    ebx, ebx        ; 是否分配成功
PAGE:00498B98                 jz      short loc_498BDE

如果分配成功,接下来就要把传入的句柄表项内容赋值到分配的句柄表项

PAGE:00498B9A                 push    edi
PAGE:00498B9B                 mov     eax, large fs:124h ; 获取当前进程的ETHREAD
PAGE:00498BA1                 mov     edi, eax        ; 当eax赋给edi
PAGE:00498BA3                 dec     dword ptr [edi+0D4h]
PAGE:00498BA9                 mov     eax, [ebp+arg_HandleTableEntry] ; 将HandleTableEntry地址赋给eax
PAGE:00498BAC                 mov     ecx, [eax]      ; 将八字节的HandleTableEntry赋值到分配的表项中
PAGE:00498BAE                 mov     [ebx], ecx
PAGE:00498BB0                 mov     eax, [eax+4]
PAGE:00498BB3                 mov     [ebx+4], eax

最后,函数将句柄作为返回值赋给eax

PAGE:00498BDE                 mov     eax, [ebp+var_Handle]

在进程创建的系统调用NtCreateProcess中,会调用ObInsertObject来将句柄插入到相应的句柄表中。而该函数首先是对参数进行各种检测,随后就是通过调用ObpCreateHandle来完成句柄的插入

4.全局句柄表

进程的句柄表是每个进程私有的,当前进程的句柄表中保存的句柄是无法被其他进程使用的。而系统同时维护了一张全局句柄表,该句柄表用来保存所有的进程以及线程。

在进程创建的系统调用中,会执行下面的代码来将新建的进程插入到全局句柄表中

PAGE:004B4797                 mov     [ebp+var_NewEProcess], ebx
PAGE:004B479A                 mov     [ebp+var_6C], esi
PAGE:004B479D                 lea     eax, [ebp+var_NewEProcess]
PAGE:004B47A0                 push    eax
PAGE:004B47A1                 push    _PspCidTable
PAGE:004B47A7                 call    _ExCreateHandle@8 ; ExCreateHandle(x,x)
PAGE:004B47AC                 mov     [ebx+84h], eax  ; 将返回值赋给新进程的UniqueProcessId

由上面的内容可以知道,此时插入的这张全局句柄表名称叫做PspCidTable,并且作为作为返回值的句柄被用作赋值进程的PID。可想而知,进程的PID此时就是全局句柄表的索引。

还需要注意的是,此时插入到全局句柄表中的就是进程内核对象本身,而不是对象头,所以在全局句柄表中获取到的地址就是进程内核对象的地址。

接下来依然通过实验验证上面内容,首先打开一个记事本进程并且获取进程的PID,此时PID为1736,除以4得到0x1B2就是句柄表的索引

接下来使用WinDbg获取全局句柄表的地址

通过该地址获取TableCode

低2位为0,说明是单句柄表结构。在根据索引,获取句柄表项


将Object的低3位清0,得到的就是进程的内核对象地址

kd> dt  0x819c9da0 _EPROCESS
nt!_EPROCESS
   +0x000 Pcb              : _KPROCESS
   +0x06c ProcessLock      : _EX_PUSH_LOCK
   +0x070 CreateTime       : _LARGE_INTEGER 0x1d7f228`59fd9a88
   +0x078 ExitTime         : _LARGE_INTEGER 0x0
   +0x080 RundownProtect   : _EX_RUNDOWN_REF
   +0x084 UniqueProcessId  : 0x000006c8 Void
   +0x088 ActiveProcessLinks : _LIST_ENTRY [ 0x81951a90 - 0x81d185b8 ]
   +0x090 QuotaUsage       : [3] 0xa50
   +0x09c QuotaPeak        : [3] 0xc20
   +0x0a8 CommitCharge     : 0x192
   +0x0ac PeakVirtualSize  : 0x2451000
   +0x0b0 VirtualSize      : 0x1f3e000
   +0x0b4 SessionProcessLinks : _LIST_ENTRY [ 0x81951abc - 0x81d185e4 ]
   +0x0bc DebugPort        : (null) 
   +0x0c0 ExceptionPort    : 0xe16609e8 Void
   +0x0c4 ObjectTable      : 0xe28ad618 _HANDLE_TABLE
   +0x0c8 Token            : _EX_FAST_REF
   +0x0cc WorkingSetLock   : _FAST_MUTEX
   +0x0ec WorkingSetPage   : 0x11940
   +0x0f0 AddressCreationLock : _FAST_MUTEX
   +0x110 HyperSpaceLock   : 0
   +0x114 ForkInProgress   : (null) 
   +0x118 HardwareTrigger  : 0
   +0x11c VadRoot          : 0x819caaa8 Void
   +0x120 VadHint          : 0x81aa3d60 Void
   +0x124 CloneRoot        : (null) 
   +0x128 NumberOfPrivatePages : 0xce
   +0x12c NumberOfLockedPages : 0
   +0x130 Win32Process     : 0xe1bbda30 Void
   +0x134 Job              : (null) 
   +0x138 SectionObject    : 0xe279a608 Void
   +0x13c SectionBaseAddress : 0x01000000 Void
   +0x140 QuotaBlock       : 0x81a6f930 _EPROCESS_QUOTA_BLOCK
   +0x144 WorkingSetWatch  : (null) 
   +0x148 Win32WindowStation : 0x0000003c Void
   +0x14c InheritedFromUniqueProcessId : 0x00000584 Void
   +0x150 LdtInformation   : (null) 
   +0x154 VadFreeHint      : (null) 
   +0x158 VdmObjects       : (null) 
   +0x15c DeviceMap        : 0xe16e9d80 Void
   +0x160 PhysicalVadList  : _LIST_ENTRY [ 0x819c9f00 - 0x819c9f00 ]
   +0x168 PageDirectoryPte : _HARDWARE_PTE
   +0x168 Filler           : 0
   +0x170 Session          : 0xf89d3000 Void
   +0x174 ImageFileName    : [16]  "notepad.exe"

根据偏移0x174中保存的进程名为notepad.exe可以得知上述结论正确。

以下的这些常用函数获取相应的进程或线程的方式就是通过全局句柄表来实现的

  • PsLookupProcessThreadByCid

  • PsLookupProcessByProcessId

  • PsLookupThreadByThreadId

六.进程的结束

进程可以调用ExitProcess函数,从而"优雅地"退出。对于大部分进程,当进程的第一个线程从其主函数返回时,该线程的进程启动代码会代表该进程调用ExitProcess。"优雅地"这个词意味着载入该进程的DLL将有机会在接获进程即将退出的通知后,使用DLL_PROCESS_DETACH调用字节的DllMain函数执行一些工作。

ExitProcess只能由字节要求退出的进程调用。但如果用TerminateProcess函数,也可也以"不优雅"的方式中止进程,该函数还可以从进程外部调用。TerminateProcess要求使用PROCESS_TERMINATE访问掩码打开一个到进程的句柄,该句柄可能被允许也可能被拒绝。这也是某些进程(如Csrss)很难终止的原因(发出请求的用户无法获得具备所需掩码的句柄)。

此处"不优雅"意味着DLL将没机会执行代码,并且所有线程会被突然终止。某些情况下导致数据丢失,例如客户端缓存没机会将其中的数据写回到目标文件。

无论哪种方式,最终在内执行体都是通过NtTerminateProcess来结束进程的,在该函数中首先会获取当前进程的EPROCESS和当前线程的ETHREAD,并且会判断是否传入的进程句柄,如果传入了则会将局部变量var_HasHandle赋值为1

PAGE:004B78D1 ; NTSTATUS __stdcall NtTerminateProcess(HANDLE ProcessHandle, NTSTATUS ExitStatus)
PAGE:004B78D1 _NtTerminateProcess@8 proc near         ; DATA XREF: .text:0040DC24↑o
PAGE:004B78D1
PAGE:004B78D1 var_CurEProcess = dword ptr -10h
PAGE:004B78D1 var_RundownProtect= dword ptr -0Ch
PAGE:004B78D1 Object          = byte ptr -8
PAGE:004B78D1 var_HasHandle   = byte ptr -1
PAGE:004B78D1 ProcessHandle   = dword ptr  8
PAGE:004B78D1 ExitStatus      = dword ptr  0Ch
PAGE:004B78D1
PAGE:004B78D1 ; FUNCTION CHUNK AT PAGE:004B7D87 SIZE 00000012 BYTES
PAGE:004B78D1 ; FUNCTION CHUNK AT PAGE:0052E62E SIZE 00000028 BYTES
PAGE:004B78D1 ; FUNCTION CHUNK AT PAGE:0052E65B SIZE 00000026 BYTES
PAGE:004B78D1
PAGE:004B78D1                 mov     edi, edi
PAGE:004B78D3                 push    ebp
PAGE:004B78D4                 mov     ebp, esp
PAGE:004B78D6                 sub     esp, 10h
PAGE:004B78D9                 push    ebx
PAGE:004B78DA                 push    esi
PAGE:004B78DB                 push    edi
PAGE:004B78DC                 mov     eax, large fs:124h ; 取出当初线程ETHRAED赋给eax
PAGE:004B78E2                 cmp     [ebp+ProcessHandle], 0 ; 判断进程句柄是否为NULL
PAGE:004B78E6                 mov     edi, eax        ; 将ETHREAD赋给edi
PAGE:004B78E8                 mov     eax, [edi+44h]  ; 取出当前进程的EPROCESS
PAGE:004B78EB                 mov     [ebp+var_CurEProcess], eax
PAGE:004B78EE                 jz      loc_4B7D7A
PAGE:004B78F4                 mov     [ebp+var_HasHandle], 1

如果没有则会将进程句柄赋值为当前进程的句柄并把局部变量var_HasHandle赋值为0

PAGE:004B7D7A loc_4B7D7A:                             ; 
PAGE:004B7D7A                 or      [ebp+ProcessHandle], 0FFFFFFFFh
PAGE:004B7D7E                 mov     [ebp+var_HasHandle], 0
PAGE:004B7D82                 jmp     loc_4B78F8

调用ObReferenceObjectByHandle来获取进程内核对象EPROCESS

PAGE:004B78F8 loc_4B78F8:                             
PAGE:004B78F8                 mov     al, [edi+140h]  ; 将FreezeCount赋给al
PAGE:004B78FE                 push    0               ; HandleInformation
PAGE:004B7900                 mov     [ebp+Object], al
PAGE:004B7903                 lea     eax, [ebp+Object]
PAGE:004B7906                 push    eax             ; Object
PAGE:004B7907                 push    dword ptr [ebp+Object] ; AccessMode
PAGE:004B790A                 push    _PsProcessType  ; ObjectType
PAGE:004B7910                 push    1               ; DesiredAccess
PAGE:004B7912                 push    [ebp+ProcessHandle] ; Handle
PAGE:004B7915                 call    _ObReferenceObjectByHandle@24 
PAGE:004B791A                 test    eax, eax
PAGE:004B791C                 mov     esi, dword ptr [ebp+Object] ; 将解析得到的对象地址赋给esi
PAGE:004B791F                 mov     ebx, esi        ; 将对象地址赋给ebx
PAGE:004B7921                 jl      loc_4B79DD

调用ExAcquireRundownProtection来获得进程销毁保护锁,并且会判断是否传入了进程句柄,如果传入了则会将句柄值与8或操作

PAGE:004B793A loc_4B793A:                             
PAGE:004B793A                 lea     ecx, [esi+80h]  ; 将EPROCESS的RundownProtect地址赋给ecx
PAGE:004B7940                 mov     [ebp+var_RundownProtect], ecx
PAGE:004B7943                 call    @ExAcquireRundownProtection@4
PAGE:004B7950                 cmp     [ebp+var_HasHandle], 0
PAGE:004B7954                 jz      short loc_4B795F
PAGE:004B7956                 mov     ecx, [ebp+ProcessHandle]
PAGE:004B7959                 push    8
PAGE:004B795B                 pop     eax
PAGE:004B795C                 lock or [ecx], eax

调用函数来获取进程的线程对象

PAGE:004B795F loc_4B795F:                             
PAGE:004B795F                 push    0               
PAGE:004B7961                 push    ebx             ; 要退出的进程的EPROCESS对象
PAGE:004B7962                 mov     [ebp+ProcessHandle], 122h
PAGE:004B7969                 call    _PsGetNextProcessThread@8 
PAGE:004B796E                 mov     esi, eax        ; 将得到的线程对象赋给esi
PAGE:004B7970                 test    esi, esi
PAGE:004B7972                 jz      short loc_4B7992
PAGE:004B7974                 and     [ebp+ProcessHandle], 0

调用函数PspTerminateThreadByPointer来关闭获得的线程对象

PAGE:004B7978 loc_4B7978:                          
PAGE:004B7978                 cmp     esi, edi
PAGE:004B797A                 jz      short loc_4B7985
PAGE:004B797C                 push    [ebp+ExitStatus]
PAGE:004B797F                 push    esi             ; 将获得的线程内核对象ETHREAD入栈
PAGE:004B7980                 call    _PspTerminateThreadByPointer@8

继续调用函数来获取进程中的线程对象,如果发现还有线程对象则会跳转到上面的代码调用函数关闭线程对象

PAGE:004B7985 loc_4B7985:                             
PAGE:004B7985                 push    esi           
PAGE:004B7986                 push    ebx             ; 要关闭的进程对象EPROCESS
PAGE:004B7987                 call    _PsGetNextProcessThread@8 
PAGE:004B798C                 mov     esi, eax
PAGE:004B798E                 test    esi, esi
PAGE:004B7990                 jnz     short loc_4B7978

调用函数来释放进程销毁保护锁

PAGE:004B7992 loc_4B7992:                             
PAGE:004B7992                 mov     ecx, [ebp+var_RundownProtect]
PAGE:004B7995                 call    @ExReleaseRundownProtection@4

如果终止的是当前的进程,则还会调用函数来关闭当前的线程,此此时函数调用以后不会返回

PAGE:004B79B0                 push    [ebp+ExitStatus]
PAGE:004B79B3                 push    edi             ; Response
PAGE:004B79B4                 call    _PspTerminateThreadByPointer@8

可以得出结论,所谓进程的销毁就是通过关闭进程中的所有线程来实现的。

七.系统初始化进程

内核的入口函数是KiSystemStartup,它调用KiInitializeKernel以执行内核层的初始化,此时要注意该函数的第一个参数是Idle进程的EPROCESS

INIT:005EBD7B                 push    0Eh
INIT:005EBD7D                 push    ds:_KeLoaderBlock ; int
INIT:005EBD83                 push    eax             ; char
INIT:005EBD84                 push    large dword ptr fs:20h ; DeferredContext
INIT:005EBD8B                 push    edx             ; int
INIT:005EBD8C                 push    ebx             ; Thread
INIT:005EBD8D                 push    offset _KiIdleProcess ; Process
INIT:005EBD92                 call    _KiInitializeKernel@24

在KiInitializeKernel函数中会调用KeInitializeProcess来完成Idle进程(进程PID=0的空闲进程)的初始化

INIT:005EC021                 push    esi
INIT:005EC022                 lea     eax, [ebp+var_2C]
INIT:005EC025                 push    eax
INIT:005EC026                 push    0FFFFFFFFh
INIT:005EC028                 push    esi
INIT:005EC029                 mov     edi, [ebp+Process]
INIT:005EC02C                 push    edi
INIT:005EC02D                 call    _KeInitializeProcess@20

PspInitPhase0是进程管理器的一部分,在该函数中会完成进程的回调设置,让进程在创建或结束的时候得到通知

INIT:005EF0D5                 push    8
INIT:005EF0D7                 mov     esi, offset _PspCreateProcessNotifyRoutine
INIT:005EF0DC                 pop     edi
INIT:005EF0DD
INIT:005EF0DD loc_5EF0DD:                             ; CODE XREF: PspInitPhase0(x)+58↓j
INIT:005EF0DD                 push    esi
INIT:005EF0DE                 call    _ExInitializeCallBack@4 ; ExInitializeCallBack(x)
INIT:005EF0E3                 add     esi, 4
INIT:005EF0E6                 dec     edi
INIT:005EF0E7                 jnz     short loc_5EF0DD

对Idle进程进行进一步的初始化

INIT:005EF18D                 mov     eax, large fs:124h
INIT:005EF193                 mov     eax, [eax+44h]
INIT:005EF196                 mov     ds:_PsIdleProcess, eax
INIT:005EF19B                 mov     [eax+6Ch], ebx
INIT:005EF19E                 mov     eax, ds:_PsIdleProcess
INIT:005EF1A3                 mov     [eax+80h], ebx
INIT:005EF1A9                 mov     eax, ds:_PsIdleProcess
INIT:005EF1AE                 add     eax, 190h
INIT:005EF1B3                 mov     [eax+4], eax
INIT:005EF1B6                 mov     [eax], eax
INIT:005EF1B8                 mov     eax, ds:_PsIdleProcess
INIT:005EF1BD                 mov     [eax+38h], ebx
INIT:005EF1C0                 mov     eax, ds:_PsIdleProcess
INIT:005EF1C5                 mov     [eax+38h], ebx

调用ObCreateObjectType来初始化进程对象类型

INIT:005EF1D2                 push    offset aProcess ; 指定创建的是进程对象类型
INIT:005EF1D7                 lea     eax, [ebp+DestinationString]
INIT:005EF1DA                 push    eax             ; DestinationString
INIT:005EF1DB                 mov     ds:_PspShutdownThread, ebx
INIT:005EF1E1                 mov     word ptr [ebp+var_74], 4Ch
INIT:005EF1E7                 mov     [ebp+var_58], 1
INIT:005EF1EB                 mov     [ebp+var_54], ebx
INIT:005EF1EE                 mov     [ebp+var_70], 0B0h
INIT:005EF1F5                 call    _RtlInitUnicodeString@8 ; RtlInitUnicodeString(x,x)
INIT:005EF1FA                 mov     [ebp+var_50], 1000h
INIT:005EF201                 mov     [ebp+var_4C], 260h
INIT:005EF208                 mov     [ebp+var_3C], offset _PspProcessDelete@4 ; PspProcessDelete(x)
INIT:005EF20F                 mov     [ebp+var_5C], 1F0FFFh
INIT:005EF216                 mov     esi, offset _PspProcessMapping
INIT:005EF21B                 lea     edi, [ebp+var_6C]
INIT:005EF21E                 movsd
INIT:005EF21F                 movsd
INIT:005EF220                 push    offset _PsProcessType ; int
INIT:005EF225                 push    ebx             ; int
INIT:005EF226                 lea     eax, [ebp+var_74]
INIT:005EF229                 movsd
INIT:005EF22A                 push    eax             ; int
INIT:005EF22B                 lea     eax, [ebp+DestinationString]
INIT:005EF22E                 push    eax             ; SourceString
INIT:005EF22F                 movsd
INIT:005EF230                 call    _ObCreateObjectType@16

调用ExCreateHandleTable函数来创建全局句柄表

INIT:005EF2FA                 push    ebx                    ; ebx此时为NULL
INIT:005EF32F                 call    _ExCreateHandleTable@4 ; ExCreateHandleTable(x)
INIT:005EF334                 cmp     eax, ebx
INIT:005EF336                 mov     ds:_PspCidTable, eax ; 创建的句柄表赋给PspCidTable
INIT:005EF33B                 jz      loc_5EF4AB

调用PspCreateProcess来创建System进程(进程PID等于4的系统进程)

INIT:005EF39D                 push    ebx             ; Flags
INIT:005EF39E                 and     eax, 0FFFFFFF8h
INIT:005EF3A1                 push    ebx             ; ParentProcess
INIT:005EF3A2                 mov     ds:_PspBootAccessToken, eax
INIT:005EF3A7                 lea     eax, [ebp+ObjectAttributes]
INIT:005EF3AA                 push    eax             ; ObjectAttributes
INIT:005EF3AB                 push    1F0FFFh         ; DesiredAccess
INIT:005EF3B0                 push    offset _PspInitialSystemProcessHandle ; ProcessHandle
INIT:005EF3B5                 mov     [ebp+ObjectAttributes.Length], 18h
INIT:005EF3BC                 mov     [ebp+ObjectAttributes.RootDirectory], ebx
INIT:005EF3BF                 mov     [ebp+ObjectAttributes.Attributes], ebx
INIT:005EF3C2                 mov     [ebp+ObjectAttributes.ObjectName], ebx
INIT:005EF3C5                 mov     [ebp+ObjectAttributes.SecurityDescriptor], ebx
INIT:005EF3C8                 mov     [ebp+ObjectAttributes.SecurityQualityOfService], ebx
INIT:005EF3CB                 call    _PspCreateProcess@3

八.参考资料

  • 《Windows内核原理与实现》

  • 《Windows内核情景分析》(上册)

  • 《软件调试(第二版)》卷2

  • 《深入解析Windows操作系统》(第七版)


[培训]《安卓高级研修班(网课)》月薪三万计划,掌 握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

最后于 2021-12-21 18:23 被1900编辑 ,原因:
收藏
点赞2
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回