windows提权基础知识

调研调研,真的不想再调研了,淦!

HalDispatchTable#

HalDispatchTable作为内核中的一个表,在windows内核提权中十分有用

img

因为它是一个存在于内核态的系统调用表,当我们获得任意地址写的能力后,可以使用shellcode地址覆盖偏移为4的函数HalQuerySystemInformation,然后调用NtQueryIntervalProfile函数,即可通过该表调用shellcode,之所以选择覆盖这个函数,是因为它没有什么用,覆盖后不会使内核崩溃

当漏洞可以获得任意地址写任意值的能力时,可以利用HalDispatchTable进行利用

利用过程

  1. 找ntkrnlpa.exe地址:使用EnumDeviceDrivers函数枚举所有的设备驱动地址,找到名为ntkrnlpa.exe驱动的地址
  2. 计算HalDispatchTable:使用LoadLibraryExA函数加载ntkrnlpa.exe到内存,然后使用GetProcAddress函数获得HalDispatchTable的地址
  3. 覆盖HalQuerySystemInformation地址为shellcode地址
  4. shellcode替换windows token提权

windows token#

Windows token是windows下控制进程、线程权限的重要标志

Token和进程相关联, 每个进程创建时都会根据Logon Session权限由LSA(Local Security Authority)分配一个Token(如果CreaeProcess时自己指定了Token, LSA会用该Token, 否则就用父进程Token的一份拷贝,由大部分进程是由Explorer.exe创建, 所以我们大部分时候都复制了explorer.exe的Token), 里面含有该进程的安全信息,包括用户帐号, 组信息, 权限信息和默认安全描述符(Security Descriptor)等, 我们可以通过GetTokenInformation查询某个Token的详细情况。

因此,如果我们可以替换当前进程的token为系统权限级token,就获得了提权。

利用过程

  1. 保存当前寄存器状态
  2. 找到当前进程的token并保存
  3. 利用system进程的pid找到system进程的token
  4. 用system进程的token替换当前进程的token

具体替换代码可参考payload.c

一些相关的函数

  • CreateProcess:使用当前登录用户的token创建进程
  • CreateProcessAsUser,CreateProcessWithTokenW:以某用户的token创建进程,具体token可以通过LogonUser登录用户账户获得
  • OpenProcessToken、 OpenThreadToken:得到当前进程、线程的token
  • GetTokenInformation:获取token中的信息
  • AdjustTokenPrivileges:修改token改变权限

具体见微软文档:https://docs.microsoft.com/zh-cn/windows/win32/secauthz/access-tokens?redirectedfrom=MSDN

windows 10 1607之前 —— BitMap#

BitMap是windows内核利用中一个被经常应用到的结构

alt 2

CreateBitMap创建了SURFACE OBJECT,包含结构BASEOBJECTSURFOBJ,变量pvScan0,其中pvScan0指向内核中的一块数据区域Pixel Data

其中Pixel Data可以使用函数GetBitmapBitsSetBitmapBits进行控制

因此,如果能够篡改某个(术语 worker bitmap)bitmappvScan0的值为任意的值的话, 就能获取向任意地址的权限.

利用流程:

  1. 调用 CreateBitmap 创建 bitmap 内核对象
  2. 通过 teb 获得 pvScan0 的地址
    1. fs 在用户模式下指向 teb,通过 NtCurrentTeb 来获得 teb 的基址
    2. teb 中的 ProcessEnvironmentBlock 指向线程所在的进程 peb
    3. peb 偏移 0x094 的地方(x86下)是一个指向 GDICELL 结构体数组的指针 GdiSharedHandleTableAddr
    4. 通过 CreateBitmap 返回的 handle 值可以得到该 bitmap 的 GDICELL 结构体的索引
    5. DWORD32 pKernelAddress = GdiSharedHandleTableAddr + ((DWORD32) handle & 0xffff) * (x86:0x10,x64:0x18);(只对windows 10 1607 前有效)
    6. pKernelAddress指向BASEOBJECT的第一个字节,因此pvScan0 = (PDWORD32)pKernelAddress + (x86:0x10,x64:0x18) + (x86:0x20,x64:0x38)
1
2
3
4
5
6
7
8
9
10
11
/// 32bit size: 0x10
/// 64bit size: 0x18
typedef struct _GDI_CELL
{
IntPtr pKernelAddress;
UInt16 wProcessId;
UInt16 wCount;
UInt16 wUpper;
UInt16 wType;
IntPtr pUserAddress;
}
  1. 通过任意地址写漏洞改写 pvScan0 的值为想要读/写的地址
  2. 调用 GetBitmapBits /SetBitmapBits来读/写数据

alt 9

一般通过两个BitMap进行利用:

  1. 创建两个 bitmap,分别为 hManager 和 hWorker
  2. 通过任意地址写漏洞改写 hManager 的 pvScan0 的值为 hWorker 的 pvScan0 的地址
  3. 结合 GetBitmapBits 和 SetBitmapBits,利用 hManager 和 hWorker 读取 system 的 token
  4. 把 system 的 token 赋予我们的当前进程

windows 10 1607 —— Accelerator table#

windows 10 1607版本中,BitMap的问题被暂时缓解,无法通过之前的pKernelAddress以及一系列偏移计算定位到pvScan0的地址

可以想到的两个基本思路

  1. 寻找其它方法来泄露 bitmap GDI 对象的地址
  2. 寻找 bitmap 的替代方案

Windows 中存在着 3 种类型的对象,分别为 user object、GDI object、Kernel object,一共有 40 多种对象

区别:

  • Event 对象属于 Kernel object,存在于非换页池中
  • bitmap 对象属于 GDI object,存在于换页会话池中
  • Accelerator table 对象,属于 user object,也存在于换页会话池中

由于bitmap对象和Accelerator table都存在于换页会话池,因此可以考虑用Bitmap对象重用故意释放的Accelerator table的空间,也就等同于获得了Bitmap对象的地址

获得Accelerator table的地址

  • User.dll模块中存在全局变量 gSharedInfo
  • gSharedInfo的第二个变量 aheList 指向USER_HANDLE_ENTRY表对象
  • USER_HANDLE_ENTRY表对象第一个参数pKernel ,指向该 user object 在内核空间中的位置
  • Accelerator table属于 user object ,因此可以找到对应的地址

让BitMap获得地址

  • 第一种:在池中释放掉一块空间,并且之后分配同等大小的空间时,内存管理器是倾向于分配同一块空间给它
  • 第二种:通过 pool fengshui 的方法来提高分配的稳定性

windows 10 1703 —— lpszMenuName#

和之前一样,微软为了不让找到到pvScan0还是想办法去获得BitMap,禁用了使用USER_HANDLE_ENTRY中的pKernel进行索引的方法

lpszMenuName关联一个window的windows窗口对象, 其在内核当中对应结构体对象为tagWND

tagCLS对应于windows窗口类,其中一个对象是lpszManuName

如果可以泄露lpszManuName地址,就可以通过wndclass.lpszMenuName控制对应内存的大小

img

tagWND对应一个桌面堆. 内核的桌面堆会映射到用户态去. HMValidateHandle能够获取这个映射的地址. 在这个映射(head.pSelf)当中存储着当前tagWND对象的内核地址. 而HMValidateHandle函数的地址未导出, 不过在导出的IsMenu函数有使用, 所以可以通过硬编码的形式找到它.

因此利用过程如下:

  1. pIsMenu --> pHMValidateHandle --> pWnd = HMValidateHandle(hWnd,1),返回tagWND对象指针,用户桌面堆地址
  2. pSelf=pWnd+0x20,得到内核桌面堆地址
  3. kernelTagCLS=pWnd+0xa8,得到内核TagCLS地址
  4. userTagCLS=kernelTagCLS-ulClientDelta,取得用户TagCLS地址,lpszMenuName位于0x90偏移处(该处指向paged pool)
  5. 配一个比较长的窗口窗口菜单名,再释放掉,然后再申请一个Bitmap将会从用刚才释放的块

Windows 10 1707 —— Palette#

在这个版本,微软意识到BitMap才是问题的根源,于是选择将Bitmap header与Bitmap data部分分离,无法再通过Bitmap header取得pvscan0指针的内核地址

通过分析,大哥们发现Palette的结构完全可以实现之前BitMap的效果,因此选择使用Pallette进行利用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
typedef struct _PALETTE64
{
BASEOBJECT64 BaseObject; // 0x00
FLONG flPal; // 0x18
ULONG32 cEntries; // 0x1C
ULONG32 ulTime; // 0x20
HDC hdcHead; // 0x24
ULONG64 hSelected; // 0x28,
ULONG64 cRefhpal; // 0x30
ULONG64 cRefRegular; // 0x34
ULONG64 ptransFore; // 0x3c
ULONG64 ptransCurrent; // 0x44
ULONG64 ptransOld; // 0x4C
ULONG32 unk_038; // 0x38
ULONG64 pfnGetNearest; // 0x3c
ULONG64 pfnGetMatch; // 0x40
ULONG64 ulRGBTime; // 0x44
ULONG64 pRGBXlate; // 0x48
PALETTEENTRY *pFirstColor; // 0x80
struct _PALETTE *ppalThis; // 0x88
PALETTEENTRY apalColors[3]; // 0x90
}
  • PALETTE偏移0x90处是PALETTEENTRY,相当于BitMap中的pixel Data
  • 偏移0x80处的pFirstColor是一个指向PALETTEENTRY的指针,相当于BitMap中的pvScan0

利用

  • 在bitmap中GetBitmapBits、SetBitmapBits用来操纵Pixel Data
  • 在Palette中,使用GetPaletteEntrie和SetPaletteEntrie操纵PALETTEENTRY
  • 具体如下图

img

Pool风水#

有的时候对利用过程中对象的地址并非绝对掌握,但是通过堆风水,可以让这些对象的相对位置确定,通过在内存中布置大量的对象,提高利用的稳定性

具体阅读:https://xz.aliyun.com/t/3146

参考#

评论