CVE-2018-8120分析与利用
2020-12-21 11:16:54 Author: xz.aliyun.com(查看原文) 阅读量:226 收藏

介绍

CVE-2018-8120漏洞是Windows 7 及 Windows Server 2008系列中的一个经典的提权漏洞,这篇文章中我们将一步步分析漏洞成因,并构造利用POC。

复现环境

Win7 x86虚拟机(Vmware)

漏洞点

​ 该漏洞存在于SetImeInfoEx函数中;在一处访问内核对象的数据时,没有判断是否合法即进行访问。

回溯分析

​ 交叉引用查找发现,只有在系统调用函数NtUserSetImeInfoEx中调用该函数

​ 查看这部分代码,可以知道SetImeInfoE函数的第一个参数来源是GetProcessWindowsStation

​ 想要搞清楚pWinStation的结构,需要找到GetProcessWindowStation函数原型,在MSDN上对该函数的定义如下:

HWINSTA GetProcessWindowStation();
If the function succeeds, the return value is a handle to the window station.

​ 这样,我们就知道了pWinStation是Window Station的Handle。在Windows中Handle实际也就是内核的对象引用,这里是WindowsStation对象。在Windbg中查看得到该对象的信息

​ 对比漏洞触发时引用的位置v3[5]实际是WindowStation结构偏移0x14的数据对象即spklList;而spklList默认为NULL!

另外在查询该API时,同是也能查到关于Window Station其余的API信息,其中关系最紧密的就是

//CreateWindowStation创建一个Window Staion
HWINSTA CreateWindowStationA(
 LPCSTR        lpwinsta,
 DWORD         dwFlags,
 ACCESS_MASK      dwDesiredAccess,
 LPSECURITY_ATTRIBUTES lpsa
);

//SetProcessWindowStation 为当前进程设置Window Station

BOOL SetProcessWindowStation(
 HWINSTA hWinSta
);

验证POC

根据上面的漏洞点、溯源成因分析。我们可以用相关的API接口构造一个简单的POC。

其中NtUserSetImeInfoEX函数属于系统调用,没有直接的导出函数可以调用,所以需要根据调用号自定义实现。

该函数的定义在Win7上没有找到(在Win10后win32k.sys的系统调用可以在win32u.dll中找到)但是有公开的文档

在这里可以找到Windows系统调用在各个版本号上的系统调用号。

还可以在ntdll.dll中找到系统调用如何自定义实现

(其中mov eax, 0x….是系统调用号)

据此我们可以实现自定义的NtUserSetImeInfoEx函数

接下来我们需要编译并在目标Windows7上运行。

运行之后windbg收到异常,断下状态如下

继续运行,目标系统蓝屏

深入分析可利用性

​ 根据上述内容,我们知道SetImeInfoEx函数的第一个参数是pWinStation,第二个参数实际就是NtUserSetImeInfoEx的参数(char*)的前0x15c的内容。也即都是可控的。

而我们发现在SetImeInfoEx中,最终将执行qmemcpy操作,而且目的地址、源地址在某种程度都是可控的。只要在访问NULL时不会触发异常即可!

这一部分代码还可以从Windows泄漏的部分源码中查到,可以辅助分析。win2000源码

探索Windows 7 内核分配NULL空间地址

​ 在用户层应用程序中,NULL引用可能是无法导致代码执行的。但是在内核态,NULL引用却有意向不到的效果(Win7上)

​ 这里不得不提Windows的一个未公开API(在ntdll.dll中)

NTSTATUS NtAllocateVirtualMemory(
 _In_  HANDLE   ProcessHandle,
 _Inout_ PVOID   *BaseAddress,
 _In_  ULONG_PTR ZeroBits,
 _Inout_ PSIZE_T  RegionSize,
 _In_  ULONG   AllocationType,
 _In_  ULONG   Protect
);

​ 该函数可以在指定进程内分配一块指定大小的空间;同时第二个参数BaseAddress可以指定为优先选择的分配的空间(当该处有足够的大小空闲时,就直接分配该地址【页对齐】)。这就意味着,我们有机会分配一块包含NULL的地址,并向其中写入构造的数据。

​ 如下利用NtAllocateVirtualMemory分配包含NULL的地址空间

​ 在windbg调试器中查看效果

任意地址写

​ 通过上述 分析过程,我们发现可以在NULL分配空间,填充合适的结构,最终利用qmemcpy实现任意地址写。

​ 构造如下的数据,用于最终执行到qmemcpy

最终调试到qmemcpy处状态,可以造成任意地址写

这里发现虽然可以任意地址写,但是注意到目的地址(edi)偏移0x18的位置需要为0。

从有限制的任意地址写到提权的思路

​ 到目前我们拥有了任意地址写的能力(内核或用户态),我们需要把写的能力转为权限的提升。在Windows上,所有的权限控制都有一个存在Kernel中的Token对象来控制。每个登入系统的用户会生成一个User Token,所有启动的进程会继承用户的Token,子进程继承父进程Token。更多详细的介绍建议查阅MSDN文档。

下面我们在Windbg里查看下不同进程的Token

Lsass.exe进程(SYSTEM权限)

在看一下cmd.exe的Token(普通用户)

可以看到Token在_EPROCESS结构偏移0xf8的位置,且最终向下8byte对齐。

我们用lsass.exe的Token复写cmd.exe 的Token,看下情况会如何

不出意外的cmd.exe将拥有SYSTEM的权限!

​ 根据这种思路,我们可以利用任意地址写漏洞修改当前进程Token。但是需要直到SYSTEM的Token,但是普通用户没有SYSTEM进程的访问权限。

另一种关于任意地址写漏洞(www)的利用方式中,在Windows上用的最为经典的就是Bitmap的滥用,也称“PvScan0 Technique” ,该方法可以将任意地址写扩展到任意地址读以泄漏信息。

什么是Bitmap

这里建议查阅Window MSDN官方文档(与Windows 绘图相关),与之相关的API重点有

// 创建Bitmap
HBITMAP CreateBitmap(
 int    nWidth,
 int    nHeight,
 UINT    nPlanes,
 UINT    nBitCount,
 const VOID *lpBits
);

// 将bitmap bits拷贝到指定缓冲区
LONG GetBitmapBits(
 HBITMAP hbit,
 LONG  cb,
 LPVOID lpvBits
);  

// 设置bitmap的bits
LONG SetBitmapBits(
 HBITMAP  hbm,
 DWORD   cb,
 const VOID *pvBits
);

CreateBitMap创建的结构SURFACE OBJECT

头部是一个BASEOBJECT结构(Windows 未公开,但是Reactos有文档BASEOBJECT

接下来是_SRFOBJ MSDN的定义如下:

这其中的pvScan0指向bitmap的Pixel Data数据区,也即是GetBitmapBits和SetBitsMaps直接操作的数据区。

我们在windbg中观察下这里有什么特别之处:

在进程PEB中有一个结构GdiSharedHandleTable保存着GDICELL结构数组(该结构并没有公开)

其中的pKernelAddress指向SURFACEOBJECT结构(BASEOBJECT首字节)

这几个结构体之间的关联,下面这幅图可以阐述:

在windbg中查看(标出了pKernelAddress和wType)

通过上面的结构我们可以获知

PEB.GdiSharedHandleObejct = 0x00300000 
Gdiaddr = 0x003000a0
BASEOBJECT中的hMgr = 0x0104000a(CreateBitMap返回值)

这三者之间的关系:

gdiCell_Addr = PEB.GdiSharedHandleObejct + (hMgr & 0xffff) * sizeof(GDICELL)

其中PEB.GdiSharedHandleObject的地址用户层可以得到,hMgr是CreateBitMap返回的Handle,也就是我们可以计算得到GdiCell的位置,进而可以得到pKernelAddress。

尽管我们在用户态无法访问SURFOBJ的成员,但是由关系图2- 知道pvScan0和pKernelAddress之间的Offset是固定的:

pvScan0_Offset = pKernelAddress + 0x10 + 0x1c

​ 这样,我们就可以根据CreateBitMap返回值得到pvScan0的地址。

pvScan0 = *( PEB.GdiSharedHandleObejct + (hMgr & 0xffff) * sizeof(GDICELL)) + 0x2C

下面的代码可以用来测试该推理:

Windbg调试过程如下

​ 到这里我们已经有了一个内核态的地址pvScan0,pvScan0处的Value就是用户可以直接读写的Pixel Data;通过GetBitmapBits和SetBitMaps,我们可以对该地址读写操作。

结合任意地址写漏洞,我们修改pvScan0,就可以真正达到准确的任意地址写!

BitMap利用流程

​ 在上一环节,我们可以在用户层获取内核地址的读写(通过pvScan0),这里详细介绍如何结合BitMap利用任意地址写漏洞

(1) 创建2个bitmaps(Manager/Worker)

(2) 使用CreateBitMap返回的handle获取pvScan0的地址

(3) 使用任意地址写漏洞将Worker的pvScan0地址写入Manager的PvScan0(作为Value)

(4) 对Manager使用SetBitmapBits ,也就是改写Woker的pvScan0的Value为读/写的任意地址。

(5) 对Worker使用GetBitmapBits/SetBitmapBits,以对第四步设置的地址任意读写!

下面的图示可以很好的表示上述攻击主要流程3、4:

代码实现Attack 3(只需要修改之前 2.5.4 任意地址写的POC)

Windbg调试,修改前Manager pvScan0

顺利执行到任意地址写漏洞(v4+0x18为0 的限制,pvScan0是满足的)

修改之后的mgr_pvScan0

Attack4 就是执行SetBitMapBits修改manager的PvScan值指向的内容(也就是Worker的PvScan0的值),写入目的地址。

例如下面的代码使得Worker的PvScan0指向0xdeadbeef

Windbg调试,修改前Worker PvScan0

修改后

​ 此时Worker的pvScan0值已经指向了我们的目标地址,通过SetBitMapBits即可写入任意内容。

代码执行

​ 当有任意地址写的能力的时候,最好的代码执行思路就是重写一个函数指针指向Shellcode,在shellcode中实现Token的重写!而Windows也确实有类似的指针(一个函数分发表),在hal模块下(该模块是Windows系统启动时加载的第一个模块)有很多这样的函数指针,我们取其中一个测试下

​ 上图中显示了函数分发表中的函数,在偏移0x4的位置hal!HaliQuerySystemInformation是Windows API NtQueryIntervalProfile运行时调用的函数,如果重写这个地址,就可以调用触发执行Shellcode。

​ 这里的HalDispatchTable的地址我们可以直接由ntoskrnl.exe模块中查询得到,但是由于ALSR的存在,得到的地址并不是真实的地址,我们只用这个计算得到偏移,之后再使用。某些系统需要加上一个Detect Offset,调试时自行修改。

获取HalDispatchTable地址

之后可以在2.5.6的基础上修改HalDispatchTable中的函数指针

修改之后的HalDispatchTable

可以发现HalDispatchTable偏移+0x4的函数指针已经被修改为Shellcode地址。

提权Shellcode

​ 对于Windows内核的漏洞,一般思路就是达到提权的目的。提权,就和权限相关,建议阅读Windows有关权限控制的官方文档。

​ 提权Shellcode 本质也是一段Shellcode,不过和一般的Shellcode实现的功能不同,一般的Shellcode在于返回一个执行命令的Shell。而提权Shellcode致力于修改当前进程的Token,使其权限更高,甚至于SYSTEM权限!

​ 这里给大家展示下Windbg遍历系统进程Token的过程

首先,在Windows内核中,fs寄存器指向nt!_KPCR结构

上图中,通过fs指向的KPCR结构找到偏移0x120处的KPRCB结构,在KPRCB偏移0x4的位置是CurrentThread的KTHREAD结构。

上述图展示了由KTHREAD获取PID、Token、FLINK的方法,在Win7 32上得到的偏移如下

​ 网上有公开的Windows提权Shellcode,我这里只是对Shellcode的一个分析,可以尝试自己动手实现。

​ 将提权Shellcode在修改HalDispatchTable的基础上,即可以提权!为了显示提权成功,可以在触发提权后,启动测试程序的cmd拥有了SYSTEM权限。

​ 提权的触发路径

提权成功

参考链接


文章来源: http://xz.aliyun.com/t/8667
如有侵权请联系:admin#unsafe.sh