想当年 初学核编 , 阅读第三章的内核对象的时候跟看天书没 什么感觉 死命在想到底内核对象 , 句柄是个什么东西 干嘛用的 于是我们工作室的老大就对我说 这篇看过就过了 学到后面你自然会明白的
我想也是 , 很多时候感觉学东西的确是这样 暂时看不懂的先放着 过段时间再看回来就恍然大悟了 . 我前段时间又看了下核编的第三章 唯一的收获就是能够大概了解到 hanle 这个所谓的索引的作用了 . 我也跟工作室的小学弟讲过我理解的句柄 ( 但是讲完看到他们茫然的表情 ~ 郁闷了 ~ 没办法 可能知识面 实践经验等还不足以理解我说的东西吧 或者是被我们可爱的鼠仙传染了 也开始学着他讲天书了 ~)
以下讲的都是我所理解的东东 , 有错误的话也在所难免了
首先说说 HANDLE 这个是个什么东西 在 WinNT.h 里面查到其定义如下
typedef void *HANDLE;
哈 ~ 这下子明白了 原来 HANDLE 就是一个无类型指针 , 只是充当内核对象在进程句柄表里面的索 引 , 相当于在进程句柄表内的一个 ID 号 .
进程句柄表 个人理解就是存放这个进程所创建的内核 对象在内核地址空间位置的一个表 ( 你可以理解为数组 ), 那么什么是内核对象呢 ? 内核对象只不过是系统资源的一种抽象 , 我新建了个进程 , 那么系统内部就会为这个进程分配资源 , 然后创建一个内核对象 , 返回一个句柄 通过这个句柄我们可以找到进程在内核空 间的位置 , 其实也就是进程对应的内核对象的位置 , 在内核里面 , 内核对象就是一堆数据结构 , 只不过数据结构很大 , 里面存放着这个进程的信息 , 这样系统只要改变这个结构里面的数值就 能操作进程 , 如此而已 ~
嘿嘿 消化一下上面说的 接下来就说进程句柄表了 , 进程句柄表是保存在进程的内核对象里面 的 进程的内核对象就是个 EPROCESS 的结构 结构原型如下 :
kd> dt _EPROCESS
ntdll!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x06c ProcessLock : _EX_PUSH_LOCK
+0x070 CreateTime : _LARGE_INTEGER
+0x078 ExitTime : _LARGE_INTEGER
+0x080 RundownProtect : _EX_RUNDOWN_REF
+0x084 UniqueProcessId : Ptr32 Void
+0x088 ActiveProcessLinks : _LIST_ENTRY
+0x090 QuotaUsage : [3] Uint4B
+0x09c QuotaPeak : [3] Uint4B
+0x0a8 CommitCharge : Uint4B
+0x0ac PeakVirtualSize : Uint4B
+0x0b0 VirtualSize : Uint4B
+0x0b4 SessionProcessLinks : _LIST_ENTRY
+0x0bc DebugPort : Ptr32 Void
+0x0c0 ExceptionPort : Ptr32 Void
+0x0c4 ObjectTable : Ptr32 _HANDLE_TABLE
+0x0c8 Token : _EX_FAST_REF
+0x0cc WorkingSetLock : _FAST_MUTEX
+0x0ec WorkingSetPage : Uint4B
+0x0f0 AddressCreationLock : _FAST_MUTEX
+0x110 HyperSpaceLock : Uint4B
+0x114 ForkInProgress : Ptr32 _ETHREAD
+0x118 HardwareTrigger : Uint4B
+0x11c VadRoot : Ptr32 Void
+0x120 VadHint : Ptr32 Void
+0x124 CloneRoot : Ptr32 Void
+0x128 NumberOfPrivatePages : Uint4B
+0x12c NumberOfLockedPages : Uint4B
+0x130 Win32Process : Ptr32 Void
+0x134 Job : Ptr32 _EJOB
+0x138 SectionObject : Ptr32 Void
+0x13c SectionBaseAddress : Ptr32 Void
+0x140 QuotaBlock : Ptr32 _EPROCESS_QUOTA_BLOCK
+0x144 WorkingSetWatch : Ptr32 _PAGEFAULT_HISTORY
+0x148 Win32WindowStation : Ptr32 Void
+0x14c InheritedFromUniqueProcessId : Ptr32 Void
+0x150 LdtInformation : Ptr32 Void
+0x154 VadFreeHint : Ptr32 Void
+0x158 VdmObjects : Ptr32 Void
+0x15c DeviceMap : Ptr32 Void
+0x160 PhysicalVadList : _LIST_ENTRY
+0x168 PageDirectoryPte : _HARDWARE_PTE_X86
+0x168 Filler : Uint8B
+0x170 Session : Ptr32 Void
+0x174 ImageFileName : [16] UChar
+0x184 JobLinks : _LIST_ENTRY
+0x18c LockedPagesList : Ptr32 Void
+0x190 ThreadListHead : _LIST_ENTRY
+0x198 SecurityPort : Ptr32 Void
+0x19c PaeTop : Ptr32 Void
+0x1a0 ActiveThreads : Uint4B
+0x1a4 GrantedAccess : Uint4B
+0x1a8 DefaultHardErrorProcessing : Uint4B
+0x1ac LastThreadExitStatus : Int4B
+0x1b0 Peb : Ptr32 _PEB
+0x1b4 PrefetchTrace : _EX_FAST_REF
+0x1b8 ReadOperationCount : _LARGE_INTEGER
+0x1c0 WriteOperationCount : _LARGE_INTEGER
+0x1c8 OtherOperationCount : _LARGE_INTEGER
+0x1d0 ReadTransferCount : _LARGE_INTEGER
+0x1d8 WriteTransferCount : _LARGE_INTEGER
+0x1e0 OtherTransferCount : _LARGE_INTEGER
+0x1e8 CommitChargeLimit : Uint4B
+0x1ec CommitChargePeak : Uint4B
+0x1f0 AweInfo : Ptr32 Void
+0x1f4 SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO
+0x1f8 Vm : _MMSUPPORT
+0x238 LastFaultCount : Uint4B
+0x23c ModifiedPageCount : Uint4B
+0x240 NumberOfVads : Uint4B
+0x244 JobStatus : Uint4B
+0x248 Flags : Uint4B
+0x248 CreateReported : Pos 0, 1 Bit
+0x248 NoDebugInherit : Pos 1, 1 Bit
+0x248 ProcessExiting : Pos 2, 1 Bit
+0x248 ProcessDelete : Pos 3, 1 Bit
+0x248 Wow64SplitPages : Pos 4, 1 Bit
+0x248 VmDeleted : Pos 5, 1 Bit
+0x248 OutswapEnabled : Pos 6, 1 Bit
+0x248 Outswapped : Pos 7, 1 Bit
+0x248 ForkFailed : Pos 8, 1 Bit
+0x248 HasPhysicalVad : Pos 9, 1 Bit
+0x248 AddressSpaceInitialized : Pos 10, 2 Bits
+0x248 SetTimerResolution : Pos 12, 1 Bit
+0x248 BreakOnTermination : Pos 13, 1 Bit
+0x248 SessionCreationUnderway : Pos 14, 1 Bit
+0x248 WriteWatch : Pos 15, 1 Bit
+0x248 ProcessInSession : Pos 16, 1 Bit
+0x248 OverrideAddressSpace : Pos 17, 1 Bit
+0x248 HasAddressSpace : Pos 18, 1 Bit
+0x248 LaunchPrefetched : Pos 19, 1 Bit
+0x248 InjectInpageErrors : Pos 20, 1 Bit
+0x248 VmTopDown : Pos 21, 1 Bit
+0x248 Unused3 : Pos 22, 1 Bit
+0x248 Unused4 : Pos 23, 1 Bit
+0x248 VdmAllowed : Pos 24, 1 Bit
+0x248 Unused : Pos 25, 5 Bits
+0x248 Unused1 : Pos 30, 1 Bit
+0x248 Unused2 : Pos 31, 1 Bit
+0x24c ExitStatus : Int4B
+0x250 NextPageColor : Uint2B
+0x252 SubSystemMinorVersion : UChar
+0x253 SubSystemMajorVersion : UChar
+0x252 SubSystemVersion : Uint2B
+0x254 PriorityClass : UChar
+0x255 WorkingSetAcquiredUnsafe : UChar
+0x258 Cookie : Uint4B
看 ~ 变态的长 ~ 我们可以看到红色标注的那个结构成员 , 这个就是进程句柄表的地址了 , 它是一个 handle_table 结构的东东 ~HOHO~ 看看这个是什么结构
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
呵呵 结构大小尚可接受 ~ 那么在这个句柄表内 ~ 我们的句柄信息到底藏在哪里咧 ? 就是在结构变量 TableCode 这里找 , 怎么找咧 ? 看了下网上的 WRK 代码 大概明白他们的思路 原来 TableCode 是个 4 字节的数值 , 这个数值的低 2 位记录着句柄表的级数 , 什么意思咧 ? 一会下面会讲到 , 通过句柄值我们就可以在句柄表内查到所 谓的内核对象了 不过这里面又有好多细节的东西 ~ 句柄需要转换才能查到内核对象地址 , 那么我们就边说边做的 涉及到的东西我在补充
我们先用 windbg 来看看进程信息 ( 以下涉及到的 WINDBG 命令请到学习笔记 一 二 文章去看 ~)
kd> !handle 0 2 6e8
processor number 0, process 000006e8
Searching for Process with Cid == 6e8
PROCESS 812e9408 SessionId: 0 Cid: 06e8 Peb: 7ffd5000 ParentCid: 05e0
DirBase: 039c0200 ObjectTable: e190e928 HandleCount: 69.
Image: ctfmon.exe
Handle table at e18c3000 with 69 Entries in use
………………….
0114 : Object: e1688498 GrantedAccess: 00000002 Entry: e18c3228
Object: e1688498 Type: (81592560) Section
ObjectHeader: e1688480 (old version)
………………….
嘿嘿 6e8 是这个进程的 PID 内核对象的句柄值是 0114 其他参数不说明了 自己看帮助吧
我们可以看到 PID 为 6e8 的进程的一些详细信息 还有这个进程下的 n 多内核对象 我就显示了 1 个 , 省的看的眼花
那么我们就开始用 windbg 研究这个句柄表吧 首先 我们从上面信息知道 ctfmon.exe 这个进程的内核对象地址是在 812e9408
kd> dt _eprocess 812e9408
ntdll!_EPROCESS
……
+0x0c4 ObjectTable : 0xe190e928 _HANDLE_TABLE
……
+0x174 ImageFileName : [16] "ctfmon.exe"
……
省略了 N 多内容 只把重要内容显示出来了 可以看到 这个进程名的确是 ctfmon.exe 那么它所对应的句柄表的位置在这里 :0xe190e928 走到这里瞧下
kd> dt _handle_table 0xe190e928
ntdll!_HANDLE_TABLE
+0x000 TableCode : 0xe18c3000
+0x004 QuotaProcess : 0x812e9408 _EPROCESS
+0x008 UniqueProcessId : 0x000006e8
+0x00c HandleTableLock : [4] _EX_PUSH_LOCK
+0x01c HandleTableList : _LIST_ENTRY [ 0xe189aa0c - 0xe1753414 ]
+0x024 HandleContentionEvent : _EX_PUSH_LOCK
+0x028 DebugInfo : (null)
+0x02c ExtraInfoPages : 0
+0x030 FirstFree : 0x118
+0x034 LastFree : 0
+0x038 NextHandleNeedingPool : 0x800
+0x03c HandleCount : 69
+0x040 Flags : 0
+0x040 StrictFIFO : 0y0
可以看到 TableCode 的地址在 0xe18c3000 .
这里就得说明下句柄表 , 句柄表分三层 顶层句柄表大小 1K 可存放 256 个元素 , 每个元素占 4bit , 中层表大小 1K 可存放 256 个元素 , 每个元素占 4bit , 下层表大小 2K 可放 256 个元素 每个元素 8bit . 我们可以看到 TableCode 的低 2 位是 0 表示这个句柄表只有 1 级 , 那么只要句柄值的低 10 位乘以 2 就是该内核对象的指针在句柄表里面的位 置了 .
kd> dd e18c3000+(114*2)
e18c3228 e1688481 00000002 00000000 0000011c
e18c3238 00000000 00000120 00000000 00000124
看到 e1688481 了吧 ? 这个就是内核对象头的地址 , 由于对象头和对象体偏移量是 0x18, 所以加上 0x18 就可以找到对应的内核对象了
kd> !object e1688481+17
Object: e1688498 Type: (81592560) Section
ObjectHeader: e1688480 (old version)
HandleCount: 9 PointerCount: 10
Directory Object: e1432248 Name: ShimSharedMemory
呃 …. 这个是系统是 sp3 的 , 好像跟 sp2 的有点出入 . 偏移量加 0x17 才是真正的对象头 , 而不是加 0x18….. 麻烦哪位高手解释一下
对比刚才上面那个对象信息
0114 : Object: e1688498 GrantedAccess: 00000002 Entry: e18c3228
Object: e1688498 Type: (81592560) Section
ObjectHeader: e1688480 (old version)
一样的 呵呵 ~~ 刚才大概把用内核句柄查找内核对象的过 程演示了一下 , 反正大概操作系统也是这么利用句柄找内 核对象的 , 但是 ….. 巨多的疑问随之产生 ….
<!--[if !supportLists]--> 1. <!--[endif]--> 对象头跟对象体到底是个什么结构 ?
<!--[if !supportLists]--> 2. <!--[endif]--> 咋个 sp3 下 , 对象头和对象体的偏移确实是 0x17 但是我根据句柄找到的对象头的地址跟实际内核的对象头的地址竟然相差 1? 到底是我哪里做错了 ?
<!--[if !supportLists]--> 3. <!--[endif]--> 其实刚才只是演示了句柄表级数为 0 时的情况 , 还有句柄表为 1 级 (TableCode 低 2 位为 01),2 级的 (TableCode 低 3 位 为 10) 的情况 , 但是这个我倒是疑惑不少 , 到底系统是怎么管理这张系统表的 ?
个人理解是这样的 , 如果刚开始分配的的是 0 级表 , 那么可以存放 512 个对象指针 , 但是如果内核对象多出了 512 系统会分配个中层表 , 但是我看到的中层表计算偏移的公式是这 样的 TableCode 地址 + 中层偏移 *4+ 低层偏移 *2 那么当内核对象多出 512 的时候内存有是如何分配的呢 ? 貌似跟 2k 的分配方式不同了 ~