-
-
[原创]Windows内核学习笔记之进程(下)
-
2021-12-16 16:20 17075
-
五.句柄与句柄表
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
偏移 | 名称 | 作用 |
0x000 | TableCode | 指向句柄表的存储结构 |
0x004 | QuotaProcess | 句柄表的内存资源记录在此进程中 |
0x008 | UniqueProcessId | 创建进程的ID,用于回调函数 |
0x00C | HandleTableLock | 句柄表锁,仅在句柄表扩展时使用 |
0x01C | HandleTableList | 所有句柄表形成一个链表,聊表头为全局变量HandleTableListHead |
0x024 | HandleContentionEvent | 若在访问句柄时发生竞争,则在此推锁上等待 |
0x02C | DebugInfo | 调试信息,仅当调试句柄时才有意义 |
0x030 | ExtraInfoPages | 审计信息所占用的页面数量 |
0x034 | FirstFree | 空闲链表表头句柄索引 |
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虚拟机自动化脱壳的方法