windows提权基础知识
调研调研,真的不想再调研了,淦!
HalDispatchTable#
HalDispatchTable作为内核中的一个表,在windows内核提权中十分有用
因为它是一个存在于内核态的系统调用表,当我们获得任意地址写的能力后,可以使用shellcode
地址覆盖偏移为4的函数HalQuerySystemInformation
,然后调用NtQueryIntervalProfile
函数,即可通过该表调用shellcode
,之所以选择覆盖这个函数,是因为它没有什么用,覆盖后不会使内核崩溃
当漏洞可以获得任意地址写任意值的能力时,可以利用HalDispatchTable
进行利用
利用过程:
- 找ntkrnlpa.exe地址:使用EnumDeviceDrivers函数枚举所有的设备驱动地址,找到名为ntkrnlpa.exe驱动的地址
- 计算HalDispatchTable:使用LoadLibraryExA函数加载ntkrnlpa.exe到内存,然后使用GetProcAddress函数获得HalDispatchTable的地址
- 覆盖HalQuerySystemInformation地址为shellcode地址
- 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,就获得了提权。
利用过程:
- 保存当前寄存器状态
- 找到当前进程的token并保存
- 利用system进程的pid找到system进程的token
- 用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内核利用中一个被经常应用到的结构
CreateBitMap创建了SURFACE OBJECT
,包含结构BASEOBJECT
和SURFOBJ
,变量pvScan0
,其中pvScan0
指向内核中的一块数据区域Pixel Data
其中Pixel Data
可以使用函数GetBitmapBits
和SetBitmapBits
进行控制
因此,如果能够篡改某个(术语 worker bitmap)bitmap
的pvScan0
的值为任意的值的话, 就能获取向任意地址读
和写
的权限.
利用流程:
- 调用 CreateBitmap 创建 bitmap 内核对象
- 通过 teb 获得 pvScan0 的地址
- fs 在用户模式下指向 teb,通过 NtCurrentTeb 来获得 teb 的基址
- teb 中的 ProcessEnvironmentBlock 指向线程所在的进程 peb
- peb 偏移 0x094 的地方(x86下)是一个指向 GDICELL 结构体数组的指针 GdiSharedHandleTableAddr
- 通过 CreateBitmap 返回的 handle 值可以得到该 bitmap 的 GDICELL 结构体的索引
DWORD32 pKernelAddress = GdiSharedHandleTableAddr + ((DWORD32) handle & 0xffff) * (x86:0x10,x64:0x18);
(只对windows 10 1607 前有效)pKernelAddress
指向BASEOBJECT
的第一个字节,因此pvScan0 = (PDWORD32)pKernelAddress + (x86:0x10,x64:0x18) + (x86:0x20,x64:0x38)
1 | /// 32bit size: 0x10 |
- 通过任意地址写漏洞改写 pvScan0 的值为想要读/写的地址
- 调用 GetBitmapBits /SetBitmapBits来读/写数据
一般通过两个BitMap进行利用:
- 创建两个 bitmap,分别为 hManager 和 hWorker
- 通过任意地址写漏洞改写 hManager 的 pvScan0 的值为 hWorker 的 pvScan0 的地址
- 结合 GetBitmapBits 和 SetBitmapBits,利用 hManager 和 hWorker 读取 system 的 token
- 把 system 的 token 赋予我们的当前进程
windows 10 1607 —— Accelerator table#
在
windows 10 1607
版本中,BitMap的问题被暂时缓解,无法通过之前的pKernelAddress
以及一系列偏移计算定位到pvScan0
的地址
可以想到的两个基本思路:
- 寻找其它方法来泄露 bitmap GDI 对象的地址
- 寻找 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 的方法来提高分配的稳定性
#
和之前一样,微软为了不让找到到
pvScan0
还是想办法去获得BitMap,禁用了使用USER_HANDLE_ENTRY中的pKernel进行索引的方法
lpszMenuName关联一个window的windows窗口对象
, 其在内核当中对应结构体对象为tagWND
tagCLS对应于windows窗口类,其中一个对象是lpszManuName
如果可以泄露lpszManuName
地址,就可以通过wndclass.lpszMenuName
控制对应内存的大小
tagWND
对应一个桌面堆
. 内核的桌面堆会映射到用户态去. HMValidateHandle
能够获取这个映射的地址. 在这个映射(head.pSelf)当中存储着当前tagWND
对象的内核地址. 而HMValidateHandle
函数的地址未导出, 不过在导出的IsMenu
函数有使用, 所以可以通过硬编码的形式找到它.
因此利用过程如下:
- pIsMenu --> pHMValidateHandle --> pWnd = HMValidateHandle(hWnd,1),返回tagWND对象指针,用户桌面堆地址
- pSelf=pWnd+0x20,得到内核桌面堆地址
- kernelTagCLS=pWnd+0xa8,得到内核TagCLS地址
- userTagCLS=kernelTagCLS-ulClientDelta,取得用户TagCLS地址,lpszMenuName位于0x90偏移处(该处指向paged pool)
- 配一个比较长的窗口窗口菜单名,再释放掉,然后再申请一个Bitmap将会从用刚才释放的块
Windows 10 1707 —— Palette#
在这个版本,微软意识到BitMap才是问题的根源,于是选择将Bitmap header与Bitmap data部分分离,无法再通过Bitmap header取得pvscan0指针的内核地址
通过分析,大哥们发现Palette的结构完全可以实现之前BitMap的效果,因此选择使用Pallette进行利用
1 | typedef struct _PALETTE64 |
- PALETTE偏移0x90处是PALETTEENTRY,相当于BitMap中的pixel Data
- 偏移0x80处的pFirstColor是一个指向PALETTEENTRY的指针,相当于BitMap中的pvScan0
利用:
- 在bitmap中GetBitmapBits、SetBitmapBits用来操纵Pixel Data
- 在Palette中,使用GetPaletteEntrie和SetPaletteEntrie操纵PALETTEENTRY
- 具体如下图
Pool风水#
有的时候对利用过程中对象的地址并非绝对掌握,但是通过堆风水,可以让这些对象的相对位置确定,通过在内存中布置大量的对象,提高利用的稳定性
具体阅读:https://xz.aliyun.com/t/3146
参考#
- https://www.cnblogs.com/weiym/p/3280313.html
- https://50u1w4y.github.io/site/HEVD/bitmap/
- https://paper.seebug.org/876/
- https://xz.aliyun.com/t/3430
- https://www.anquanke.com/post/id/168441
- https://bbs.pediy.com/thread-224158.htm
- https://xz.aliyun.com/t/3146
- https://bbs.pediy.com/user-home-530966.htm
- https://github.com/hatRiot/token-priv
- https://www.anquanke.com/post/id/168572
- https://50u1w4y.github.io/site/HEVD/bitmap/