天天看点

分析句柄表

一、什么是句柄

当一个进程创建或者打开一个内核对象时,将获得一个句柄,通过这个句柄可以访问内核对象。

如:CreateMutex、CreateEvent 、CreateThread 等函数就会返回一个HANDLE类行值,这种就叫句柄,对应着一个内核对象。

调用CloseHandle 函数对应某个内核对象计数减一,当内核对象计数为0,这个对象就被销毁了。

内核对象在内核存储,直接把地址给3环用很不安全,所以微软设计了句柄(HANDLE)给3环使用,句柄是一个整数,它的值除以4是句柄表的下标,通过下标能找到存储在句柄表里的句柄表项,每个占8字节。

句柄存在的目的是为了避免在应用层直接修改内核对象。

HANDLE g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

如果g_hEvent存储的就是EVENT内核对象的地址,那么就意味着我们可以在应用层修改这个地址,一旦指向了无效的内核内存地址就会蓝屏。           

Windows的设计理念:

1) 隐藏内核对象指针

2) 句柄就是个索引

分析句柄表

二、句柄表,句柄表项

句柄表存储在 EPROCESS.ObjectTable.TableCode 里:

分析句柄表
分析句柄表

句柄表项每个占8字节,一个页4KB,所以一个页能存储512个句柄表项,当进程中的句柄数量超过512,句柄表就会以分级形式存储,最多三级:

分析句柄表

TableCode 的低2位,它表明了句柄表的结构,如果低2位是01,表示现在句柄表有两级, TableCode 指向的表存储了 4KB / 4 = 1024 个句柄表的地址,每个地址指向一个句柄表。

  1. 通过编程构造超过512个句柄,看看 TableCode 的低2位确实是01

#include "stdafx.h"

#include <windows.h>

int _tmain(int argc, _TCHAR* argv[])

{

       DWORD PID;

       HANDLE hPro = NULL;

       HWND hwnd = FindWindowA(NULL, "计算器");

       GetWindowThreadProcessId(hwnd, &PID);

       for (int i = 0; i < 600; i++)

       {

              //hPro = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, TRUE, PID);

              hPro = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, TRUE, PID);

              printf("句柄:%x\n", hPro);

       }

       SetHandleInformation(hPro, HANDLE_FLAG_PROTECT_FROM_CLOSE, HANDLE_FLAG_PROTECT_FROM_CLOSE);

       system("pause");

       return 0;

}

分析句柄表
分析句柄表
  1. 通过句柄表项找到内核对象(句柄数量少于512)

下面我们编写一个程序,打开计算器的进程句柄,然后在windbg里通过句柄表找到计算器的EPROCESS:

#include "stdafx.h"

#include <windows.h>

int _tmain(int argc, _TCHAR* argv[])

{

       DWORD PID;

       HANDLE hPro = NULL;

       HWND hwnd = FindWindowA(NULL, "计算器");

       GetWindowThreadProcessId(hwnd, &PID);

       for (int i = 0; i < 100; i++)

       {

              //hPro = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, TRUE, PID);

              hPro = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, TRUE, PID);

              printf("句柄:%x\n", hPro);

       }

       SetHandleInformation(hPro, HANDLE_FLAG_PROTECT_FROM_CLOSE, HANDLE_FLAG_PROTECT_FROM_CLOSE);

       getchar();

       return 0;

}

分析句柄表

0xe2032d18低2位是0,表示当前进程的句柄表只有一级,然后我们找一下下标为70的项:

分析句柄表

这里打印了下标70的项,同时,+8就是下标71的项,就是我们调用 SetHandleInformation 修改了句柄属性的最后一个项,观察它们的区别,最高字节不相同。

句柄表项包含64位,其中低32位的低3位清零后就是内核对象头(此处是EPROCESS)的虚拟地址,注意,指向内核对象头 OBJECT_HEADER,这个结构大小是 0x18,所以要加上 0x18 才能找到 EPROCESS.

分析句柄表