句柄表
HWND和HANDLE不同,HWND与界面有关,下面只关心HANDLE句柄。
句柄表分为全局句柄表,和每个进程一个的自己的句柄表为私有句柄表。
当一个进程创建或者打开一个内核对象时,将获得一个句柄,通过这个句柄可以访问内核对象。
例:
HANDLE g_hMutex = ::CreateMutex(NULL, FALSE, "XYZ"); //创建一个有名或无名的互斥量对象
HANDLE g_hMutex = ::OpenMutex(MUTEX_ALL_ACCESS, FALSE, "XYZ");
HANDLE g_hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
HANDLE g_Thread = ::CreateThread(NULL, 0, Proc, NULL, 0, NULL);
当一个函数出现了 安全属性描述符(LPSECURITY_ATTRIBUTES) 这个参数,创建的对象都是内核对象。
只要创建就会增加内核对象,但是打开不会增加,私有的句柄表只保存当前进程创建和打开的句柄(自己的线程也属于),当前进程句柄保存在创建他的进程中(或者全局句柄表)。
内核对象:
进程:EPROCESS
线程:ETREAD
句柄存在的目的是为了避免在应用层直接修改内核对象。如果 CreateEvent 函数返回的句柄 g_hEvent 存储的就是EVENT内核对象的地址,那么就意味着我们可以在应用层修改这个地址,一旦指向了无效的内核内存地址就会产生蓝屏。
Windows为了隐藏内核对象的指针,在0环建立了一张句柄表:
0 0x80123456 → EVENT 事件内核对象
1 0x80123457
2 0x80123458 → ETHREAD 线程内核对象
3 0x80123459
所以,句柄就是一个索引,用来在句柄表中查找内核对象的地址。一个内核对象只有一个结构体实例,每次打开或创建只会增加计数器,但是句柄会创建多个,即在句柄表中会出现多个句柄索引是指向同一个地址。
句柄对应的索引 = 句柄值 / 4
内核对象地址 = 句柄表基址(清空后三位) + (句柄值 / 4) * 8
PS:句柄表中句柄索引对应对象以8个字节为单位。
句柄表位置:
kd> dt _EPROCESS
+0x0c4 ObjectTable : _HANDLE_TABLE
...
Kd> dt _HANDLE_TABLE
+0x000 TableCode //句柄表
+0x004 Quotaprocess
+0x008 UniqueProcessId
+0x00c HandleTableLock
+0x01c HandleTableList
...
8个字节的内核对象地址属性
全局内核句柄表和私有句柄表的内核对象属性是一样的。
bit63 bit47 bit31 bit15 bit0
(1) (2) (3) (4)
1)这一块共计两个字节,高位字节是给 SetHandleInformation 这个函数用的。
例:
SetHandleInformation(Handle, HANDLE_FLAG_PROTECT_FROM_CLOSE, HANDLE_FLAG_PROTECT_FROM_CLOSE)
此时这个位置将被写入0x02,HANDLE_FLAG_PROTECT_FROM_CLOSE 宏的值位0x00000002,取最低字节,最终(1)这个位置为 0x0200。
2)这块是访问掩码,是给 OpenProcess 函数使用的,具体的存的就是这个函数的第一个参数的值,即 dwDesiredAccess。
例:
OpenProcess(dwDesiredAccess, BinberitHandle, dwProcessId)
3)和 4)这两个块共计四个字节,其中bit0 - bit2存的是这个句柄的属性,其中bit2、bit0默认为0,1,bit1表示的是该句柄是否可继承,OpenProcess的第二个参数与bit1有关。
bit31 - bit3 则是存放的该内核对象在内核中的具体的地址。
_OBJECT_HEADER
kd> dt _OBJECT_HEADER
nt!_OBJECT_HEADER
+0x000 PointerCount : Int4B
+0x004 HandleCount : Int4B
+0x004 NextToFree : Ptr32 Void
+0x008 Lock : _EX_PUSH_LOCK
+0x00c TypeIndex : UChar
+0x00d TraceFlags : UChar
+0x00e InfoMask : UChar
+0x00f Flags : UChar
+0x010 ObjectCreateInfo : Ptr32 _OBJECT_CREATE_INFORMATION
+0x010 QuotaBlockCharged : Ptr32 Void
+0x014 SecurityDescriptor : Ptr32 Void
+0x018 Body : _QUAD
在我们进程和线程结构体 EPROCESS 和 ETHREAD 结构体之前有18个字节的 _OBJECT_HEADER结构体,也就说实际上 EPROCESS 和 ETHREAD是从_OBJECT_HEADER结构体18个字节之后开始的。
PS:一个完整的内核结构都是以一个_OBJECT_HEADER开始的,后面跟上对应的结构。如
_OBJECT_HEADER + ETHREAD
_OBJECT_HEADER + EPROCESS
注意:
我们当通过句柄表的内核对象查找EPROCESS 或者是 ETHREAD时,内核对象中的地址指向的是 _OBJECT_HEADER 这个结构体,所以
_OBJECT_HEADER + 0x18
的位置才是EPROCESS 或者 ETHREAD。
句柄表用于反调试
可以遍历其他进程的所有句柄表,查看是否存在自己进程结构体的地址,如果存在则说明我们当前进程至少被打开过。
全局句柄表(PspCidTable)
全局句柄表是给操作系统使用的,并不是针对某个进程或者线程的,全局句柄表存储所有进程和线程,不存储其他句柄,不论当前进程或线程是否是你打开的,只要其还在运行都保存再这个句柄表里。
1)所有的进程和线程无论是否打开,都在这个表中。
2)每个进程和线程都有一个唯一的编号:PID和CID 这两个值起始就是全局句柄表中的索引。
进程和线程的查询,主要是一下三个函数,按照给定的PID或CID从 PspCidTable(全局句柄表) 中查找对应的进程对象:
PsLookupProcessTreadByCid()
PsLookupProcessByProcessId()
PsLookupThreadByYhreadId()
在全局句柄表中,没有_OBJECT_HEADER这个结构体,直接指向ETHREAD或者EPROCESS,所以可以通过用保存的地址减去_OBJECT_HEADER的大小后,通过_OBJECT_HEADER中保存的类型去判断当前指向的到底是ETHREAD还是EPROCESS。
句柄表的结构
TableCode低2位为0:
一级
TableCode -> 0x0000
0x0000
0x0000
0x0000
...
句柄表为1级的,直接保存句柄值,可以存储512个句柄值。
TableCode低2位为1:
一级 二级
TableCode -> 0x0000 -> 0x0000
0x0000 0x0000
0x0000 ...
0x0000
0x0000
0x0000 -> 0x0000
... 0x0000
0x0000
...
句柄表为2级的,第一级可以存储1024个地址每个指向一个句柄表,第二级存储的才是句柄值,所以可以存储1024 * 512个句柄值。
TableCode低2位为2:
一级 二级 三级
TableCode -> 0x0000 -> 0x0000 -> 0x0000
0x0000 0x0000 0x0000
0x0000 ... 0x0000
0x0000 ....
0x0000
0x0000 -> 0x0000 -> 0x0000
... 0x0000 0x0000
0x0000 0x0000
... ...
句柄表为3级的,第一级和第二级存储的都是句柄表地址,则一共有1024 * 1024个句柄表,所以可以存储1024 * 1024 * 512个句柄值。
根据PID,在PspCidTable(全局句柄表)中找到内核对象
计算器 calc.exePID:1920
句柄表索引 = 1920 / 4 = 480 = 0x1E0
查看PspCidTable:
kd> dd PspCidTable
83f84f34 89001150 00000000 8000001c 00000101
83f84f44 800002e8 80000024 00000000 00000000
83f84f54 00000000 00000000 00000000 00000113
83f84f64 00000000 00000000 83f3535a 00000000
83f84f74 00000000 00000000 00000000 00000008
83f84f84 00000000 83f84f88 83f84f88 00000000
83f84f94 00000000 00000000 00000000 00000000
83f84fa4 00000000 807c8c38 807c4c38 00000000
89001150 这个地址是一个 _HANDLE_TABLE 结构体:
kd> dt _HANDLE_TABLE 89001150
ntdll!_HANDLE_TABLE
+0x000 TableCode : 0x89004000
+0x004 QuotaProcess : (null)
+0x008 UniqueProcessId : (null)
+0x00c HandleLock : _EX_PUSH_LOCK
+0x010 HandleTableList : _LIST_ENTRY [ 0x89001160 - 0x89001160 ]
+0x018 HandleContentionEvent : _EX_PUSH_LOCK
+0x01c DebugInfo : (null)
+0x020 ExtraInfoPages : 0n0
+0x024 Flags : 1
+0x024 StrictFIFO : 0y1
+0x028 FirstFreeHandle : 0x5f0
+0x02c LastFreeHandleEntry : 0x89004f38 _HANDLE_TABLE_ENTRY
+0x030 HandleCount : 0x1e6
+0x034 NextHandleNeedingPool : 0x800
+0x038 HandleCountHighWatermark : 0x1e8
此时 TableCode 才是句柄表,这个是全局的句柄表,因为 0x89004000 低2位为0,所以是一级的(会有多级的)。
根据句柄表所以查找内核对象(TableCode + 索引 * 8):
kd> dq 0x89004000 + 1E0 * 8
89004f00 00000000`85654d41 00000000`86b68351
89004f10 00000000`86982be1 00000000`86983d49
89004f20 00000000`86a1d341 00000000`86a5f031
89004f30 00000000`85853b51 00000000`00000000
89004f40 00000450`00000000 00000000`86b2a749
89004f50 00000000`857dd031 00000000`86b1c031
89004f60 000006b4`00000000 00000000`86a5a851
89004f70 00000000`86a5bd19 00000000`86b61411
00000000`85654d41 直接指向ETHREAD或者EPROCESS,内核对象地址为 bit31 - bit3,所以后三位清零
00000000`85654d41 = 85654d40
查看 _EPROCESS:
kd> dt _EPROCESS 85654d40
ntdll!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x098 ProcessLock : _EX_PUSH_LOCK
+0x0a0 CreateTime : _LARGE_INTEGER 0x01d76256`5e29a29d
+0x0a8 ExitTime : _LARGE_INTEGER 0x0
+0x0b0 RundownProtect : _EX_RUNDOWN_REF
+0x0b4 UniqueProcessId : 0x00000780 Void
+0x0b8 ActiveProcessLinks : _LIST_ENTRY [ 0x869be8e0 - 0x86b603a0 ]
+0x0c0 ProcessQuotaUsage : [2] 0x1dec
+0x0c8 ProcessQuotaPeak : [2] 0x1e70
+0x0d0 CommitCharge : 0x561
+0x0d4 QuotaBlock : 0x86890740 _EPROCESS_QUOTA_BLOCK
+0x0d8 CpuQuotaBlock : (null)
+0x0dc PeakVirtualSize : 0x492e000
+0x0e0 VirtualSize : 0x46e4000
+0x0e4 SessionProcessLinks : _LIST_ENTRY [ 0x869be90c - 0x85c1e214 ]
+0x0ec DebugPort : (null)
+0x0f0 ExceptionPortData : 0x85658648 Void
+0x0f0 ExceptionPortValue : 0x85658648
+0x0f0 ExceptionPortState : 0y000
+0x0f4 ObjectTable : 0x89682780 _HANDLE_TABLE
+0x0f8 Token : _EX_FAST_REF
+0x0fc WorkingSetPage : 0x81fd
+0x100 AddressCreationLock : _EX_PUSH_LOCK
+0x104 RotateInProgress : (null)
+0x108 ForkInProgress : (null)
+0x10c HardwareTrigger : 0
+0x110 PhysicalVadRoot : (null)
+0x114 CloneRoot : (null)
+0x118 NumberOfPrivatePages : 0x4b9
+0x11c NumberOfLockedPages : 0
+0x120 Win32Process : 0xffb0bbc0 Void
+0x124 Job : (null)
+0x128 SectionObject : 0x8b1344e8 Void
+0x12c SectionBaseAddress : 0x00da0000 Void
+0x130 Cookie : 0xae458780
+0x134 Spare8 : 0
+0x138 WorkingSetWatch : (null)
+0x13c Win32WindowStation : 0x00000030 Void
+0x140 InheritedFromUniqueProcessId : 0x0000011c Void
+0x144 LdtInformation : (null)
+0x148 VdmObjects : (null)
+0x14c ConsoleHostProcess : 0
+0x150 DeviceMap : 0x89621928 Void
+0x154 EtwDataSource : 0x867d4810 Void
+0x158 FreeTebHint : 0x7ffd9000 Void
+0x160 PageDirectoryPte : _HARDWARE_PTE_X86
+0x160 Filler : 0
+0x168 Session : 0x8b818000 Void
+0x16c ImageFileName : [15] "calc.exe" //计算器
+0x17b PriorityClass : 0x2 ''
+0x17c JobLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x184 LockedPagesList : (null)
+0x188 ThreadListHead : _LIST_ENTRY [ 0x85654298 - 0x86a7afb0 ]
+0x190 SecurityPort : (null)
+0x194 PaeTop : 0x860bb320 Void
+0x198 ActiveThreads : 7
+0x19c ImagePathHash : 0xac08706a
+0x1a0 DefaultHardErrorProcessing : 1
+0x1a4 LastThreadExitStatus : 0n0
+0x1a8 Peb : 0x7ffd6000 _PEB
+0x1ac PrefetchTrace : _EX_FAST_REF
+0x1b0 ReadOperationCount : _LARGE_INTEGER 0x1
+0x1b8 WriteOperationCount : _LARGE_INTEGER 0x0
+0x1c0 OtherOperationCount : _LARGE_INTEGER 0x85
+0x1c8 ReadTransferCount : _LARGE_INTEGER 0x3c
+0x1d0 WriteTransferCount : _LARGE_INTEGER 0x0
+0x1d8 OtherTransferCount : _LARGE_INTEGER 0x12e
+0x1e0 CommitChargeLimit : 0
+0x1e4 CommitChargePeak : 0x58e
+0x1e8 AweInfo : (null)
+0x1ec SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO
+0x1f0 Vm : _MMSUPPORT
+0x25c MmProcessLinks : _LIST_ENTRY [ 0x869bea84 - 0x86b60544 ]
+0x264 HighestUserAddress : 0x7fff0000 Void
+0x268 ModifiedPageCount : 0
+0x26c Flags2 : 0xd000
+0x26c JobNotReallyActive : 0y0
+0x26c AccountingFolded : 0y0
+0x26c NewProcessReported : 0y0
+0x26c ExitProcessReported : 0y0
+0x26c ReportCommitChanges : 0y0
+0x26c LastReportMemory : 0y0
+0x26c ReportPhysicalPageChanges : 0y0
+0x26c HandleTableRundown : 0y0
+0x26c NeedsHandleRundown : 0y0
+0x26c RefTraceEnabled : 0y0
+0x26c NumaAware : 0y0
+0x26c ProtectedProcess : 0y0
+0x26c DefaultPagePriority : 0y101
+0x26c PrimaryTokenFrozen : 0y1
+0x26c ProcessVerifierTarget : 0y0
+0x26c StackRandomizationDisabled : 0y0
+0x26c AffinityPermanent : 0y0
+0x26c AffinityUpdateEnable : 0y0
+0x26c PropagateNode : 0y0
+0x26c ExplicitAffinity : 0y0
+0x270 Flags : 0x944d0801
+0x270 CreateReported : 0y1
+0x270 NoDebugInherit : 0y0
+0x270 ProcessExiting : 0y0
+0x270 ProcessDelete : 0y0
+0x270 Wow64SplitPages : 0y0
+0x270 VmDeleted : 0y0
+0x270 OutswapEnabled : 0y0
+0x270 Outswapped : 0y0
+0x270 ForkFailed : 0y0
+0x270 Wow64VaSpace4Gb : 0y0
+0x270 AddressSpaceInitialized : 0y10
+0x270 SetTimerResolution : 0y0
+0x270 BreakOnTermination : 0y0
+0x270 DeprioritizeViews : 0y0
+0x270 WriteWatch : 0y0
+0x270 ProcessInSession : 0y1
+0x270 OverrideAddressSpace : 0y0
+0x270 HasAddressSpace : 0y1
+0x270 LaunchPrefetched : 0y1
+0x270 InjectInpageErrors : 0y0
+0x270 VmTopDown : 0y0
+0x270 ImageNotifyDone : 0y1
+0x270 PdeUpdateNeeded : 0y0
+0x270 VdmAllowed : 0y0
+0x270 CrossSessionCreate : 0y0
+0x270 ProcessInserted : 0y1
+0x270 DefaultIoPriority : 0y010
+0x270 ProcessSelfDelete : 0y0
+0x270 SetTimerResolutionLink : 0y1
+0x274 ExitStatus : 0n259
+0x278 VadRoot : _MM_AVL_TABLE
+0x298 AlpcContext : _ALPC_PROCESS_CONTEXT
+0x2a8 TimerResolutionLink : _LIST_ENTRY [ 0x869207d8 - 0x83f7e860 ]
+0x2b0 RequestedTimerResolution : 0
+0x2b4 ActiveThreadsHighWatermark : 7
+0x2b8 SmallestTimerResolution : 0x2710
+0x2bc TimerResolutionStackRecord : 0x8a07d250 _PO_DIAG_STACK_RECORD
查找多级
先将句柄索引除4:
ff0 / 4 = 3FC
将句柄值减去级数:
3FC - (n * 512) //512要转16进制
在EPROCESS中找到句柄表地址,清零后3位查看:
0x00000001 0x00000002 0x00000003
第一个是前512,后面依次,然后用句柄表地址索引加上地址查找值。
MmGetSystemRoutineAddress
MmGetSystemRoutineAddress 函数用来得到导出函数的地址,优点:
1.不会被 IAT HOOK 影响(从内核模块导出表中找函数地址的),MmGetSystemRoutineAddress会从ntoskrnl.exe的导出表查找,不会去当前exe的IAT表查找。
2.有些内核函数虽然导出了,但并没有函数说明,无法直接使用。