0803漏洞利用分析
作者对0803漏洞的利用工具进行改进使其运用在windows server2012系统环境中,已成功。这是对之前的分析做的小结。本文分享了有关该漏洞的触发原理及利用方法。
简要说明
cve-2019-0803
漏洞类型
UAF
时限
2019年4月公布的补丁
漏洞位置、应用环境
全覆盖
测试环境及场景:
Sever2008r2
Win7_64
server2012r2
2008r2_sp1
Win7_64_sp1
分析该漏洞的目的是想改进EXP使其能运用至server2012系统。最后一小节总结了需更修改的地方。
DDE通信时,client向server请求数据后,sever返回数据时,首先会调用用户层函数_clientCopyDDEIn1(usr32.dll中),该函数是将一些数据填充至tagINTDDEINFO结构,然后再调用hmgSetOwner函数(win32k.sys中),该函数会将tagINTDDEINFO中的成员hIndirect(GDI句柄)返回给client。
因此,利用思路是,先新建一个A进程并创建得到一个对象句柄X,然后修改用户层函数_clientCopyDDEIn1,将tagINTDDEINFO的成员hIndirect修改为X,这样返回给client的对象就是一个外部对象X。
Client结束后会释放X,然后再用事先构造好的Y占用该内存区域,那么A进程可以用控制X的函数(此处为SetDIBColorTable函数)修改Y。
UAF漏洞的利用,雷同double-free漏洞(之前分析的cve-2018-8639),这里也运用了简单的fengshui布局。此外,采用了任意地址读写技术,利用了tagWND结构中cbWNDExtra成员、strName成员、成员spwndParent。
double-free
图2
UAF
图3
由以上两图对比可知,UAF就是double-free的前两步,根据之前cve-2018-8639对double-free的原理分析可知,double-free中的对象B要满足:有相关的函数可以更改B对象的内容。
那么对于UAF中,存在漏洞的对象A需要满足该条件(本漏洞中就是SetDIBColorTable函数)。其实两个漏洞类型的利用原理是一致的。
对照图3及”漏洞原理”一节,存在漏洞的对象A是DIB(Device-independent bitmap)类型,然后在client释放A后,用B(构造好的HACCEL)占用A,最后用SetDIBColorTable函数(控制DIB对象)更改B的值。
关于对象B
1、B的大小与A相同
2、SetDIBColorTable函数是如何修改A的,从而构造B
3、如何通过一次修改B,可以实现任意读写内存地址
问题1:
通过读代码[2]可知A就是bitmap对象,其大小0x350(win7/server2008),在server2012系统下为0x370。
另外,构建A的过程并不是简单的createbitmap(大概与漏洞的触发有关,不是很懂),而是通过建一个"#32768"类的窗口,获得设备上下文的句柄hdc,最后通过hdc得到”OBJ_BITMAP”类型的对象的句柄即对象A。(PS:用gdiview等GDI对象查看的小工具无法看到A)。
对象B选取 accelerator table对象,一个ACCEL结构的数组。通过CreateAcceleratorTable函数新建对象,参数2确定了对象的大小。当为132时,对象大小为0x350,当为138时,对象大小为0x370。
问题2:
关于SetDIBColorTable(hdc,i, n, const RGBQUAD *prgbq)函数如何修改对象DIB,则需逆向该函数(参见“相关函数及术语”),同时还要了解_PALETTE64对象。
该函数是对hdc中的DIB对象(实际就是_PALETTE64对象)的i开始的n个PALETTENTRY结构进行写。对应到内存中具体的对象的操作就是对_PALETTE64对象的apalColors[]数组进行写操作。
需要说明的是对象的成员pFirstColor就指向该数组,因此构造一个_PALETTE64对象,使其成员pFirstColor不是指向位图对象的数组apalColors[],而是一个我们想更改的内存位置,那么再调用SetDIBColorTable函数时,令i为0,n为1,就更改了我们指定的内存。
(这一处没想通,需要好好分析下SetDIBColorTable的具体过程)
根据code,构造的ACELL数组是在第[15]号ACELL放入构造的_PALETTE64对象地址,然后构造的_PALETTE64对象的成员pFirstColor(偏移0x80处)放入待更改的内存地址xxxx。这样调用SetDIBColorTable函数后就能更改xxxx处的值。
问题3:
如何通过更改一个值,达到任意读写内存的目的。此处利用了tagWND结构中cbWNDExtra成员、strName成员、成员spwndParent。
创建两个icon类的窗口分别为hwnd1和hwnd2,然后设置xxxx为hwnd1的成员cbWNDExtra(偏移0xe8)的地址,通过UAF漏洞更改一次cbWNDExtra值,使窗口hwnd1可以越界写hwnd2(用函数SetWindowLong),然后越界写hwnd2的StrName中的buffer。
hwnd2就可以调用函数SetWindowText实现任意内存地址写。任意内存地址读:先通过越界写更改hwnd2的成员spwndParent,然后hwnd2通过调用GetAncestor函数实现任意内存地址读。
为了使A释放后,B能顺利占据其内存区域,对内核池进行布局(括号内表示server20012系统下),布局使用的对象为ACCEL。
1、因为A的大小为0x350(0x370),分配200个由132(138)个ACCEL组成的0x350(0x370)大小的ACCEL数组,占用内存空间中分散的0x350(0x370)大小的内存空洞。
2、分配1000个由533(527)个ACCEL组成的0xCB0(0xC90)大小的ACCEL数组,使新分配的内存页面留出0x350(0x370)的空间,方便A能分配至此.(0x350=0x1000-0xc90)。
3、分配400个bitmap对象。
4、创建对象A,占用之前构造的内存空洞。
5、再分配400个bitmap对象。
6、释放第三步分配的前300个bitmap对象。
释放A后
1、释放400个从第300到第700的bitmap。
2、分配2000个构建好的ACCEL数组,即用B占A释放后留下的内存空间。
__ClientCopyDDEIn1是用户层函数
目的:在call _ClientCopyDDEIn1指令后开始截持。
1、截持跳板
UCHAR HijackJmpShellcode[] = {
0x50, //push rax
0x48, 0xB8, //mov rax, 0xcccccccc
0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC,
0xFF, 0xE0 //jmp rax
};//13个字节
2、跳转后运行的程序
HijackTrampoFunc PROC
pushr8
lea rax,[rsp+50h]
mov r8,qword ptr g_BitMapAddr
mov qword ptr [rax+30h],r8
mov r8,qword ptr [rax+20h]
mov byte ptr [r8+2],2
pop r8
pop rax
xor r8d,r8d
mov eax,eax
lea rcx,[rsp+20h]
lea edx,[r8+18h]
jmp qword ptr ContinueAddr
HijackTrampoFunc ENDP
红色:关键的替换返回对象的代码
蓝色:堆栈平衡
绿色:原函数中被HijackJmpShellcode[](13个字节)覆盖的指令
黄色:跳转到覆盖后的指令,继续执行,ContinueAddr中存放mov[rsp+98h+var_78], r11,指令的地址
说明:以上为win7中的__ClientCopyDDEIn1函数,win2012中有细微差别。
3、跳转
HijackJmpShellcode[3-10]8个字节放入HijackTrampoFunc程序的地址。
截持后运行的红色代码是关键,这块代码的编写首先需要了解__ClientCopyDDEIn1的功能,然后要知道tagINTDDEINFO的结构信息(主要是成员hIndirect的偏移),最后要知道在这个函数中tagINTDDEINFO在内存中的哪个位置,最终才能将其成员hIndirect替换为我们的DIB对象的句柄。
_ClientCopyDDEIn1的功能
_ClientCopyDDEIn1(void*p1,structtagINFDDEINFO*p2)
把p1写入p2,p1是以外层函数__ClientCopyDDEIn1的参数(通过rcx寄存器传入)为内存地址中的值。P2是栈空间中的地址([rsp+40h][rbp-58h]),所以该函数就是复制p1处的数据填充至栈空间(以[rsp+40h]为首地址)。
tagINTDDEINFO的结构信息
根据code中的红色代码,猜测偏移40h为成员hIndirect,之后又更改了偏移30处的指针指向的第2字节的值为2(不知道是个啥)。
说明:好在这一点win server 2008和win server 2012没有不同,不然要到哪去分析tagINTDDEINFO的结构信息!!!!
token替换[4]
1、获得当前进程的_EPROCESS结构
2、通过_EPROCESS结构中的成员ActiveProcessLinks及成员UniqueProcessId(pid)遍历搜索出system进程(pid为4)
3、保存system进程的token值
4、覆盖当前进程的token
图1
说明:
1、对于第1步,之前算得了两个icon类窗口的地址即pwndIcon1和pwndIcon2,用其中的pwndIcon1即得到图中的tagWND的地址,通过图中的关系可以得到当前进程的_EPROCESS结构。
2、图中各结构中成员的偏移值在不同的系统中不相同,在server2008与server2012的具体值可通过windbg获得,在code中有标出。
3、覆盖token时又卡了。写内存时BOSD,一开始以为server12引进了什么安全机制不允许token替换,后来发现是写操作用的函数SetWindowText中的一些注意事项。参见“相关函数及术语”的“SetWindowText”。
Bitmap和DIB :https://www.xuebuyuan.com/1492074.html
BOOL SetWindowText(
HWND hwnd,
LPCTSTR lpString
);//hwnd
窗口的句柄,lpString指向一个空结束的字符串的指针
具体操作:
把窗口tagWND结构中的成员StrName中的Buffer成员指向的内存处写入lpString字符串。tagWND结构参考图1:
应用于提权中的写操作:更改Buffer值(即写地址),调用SetWindowText,对内存中任意地址进行写操作,但在运用中遇到一些问题:
1、lpString是以空结束的字符串,因此再写内存时,会多覆盖1字节的0,测试时导致BOSD(因为对当前进程的token值后的一字节也进行了修改,修改为0),因此,保存token后的若干字节(这些字节是以0结尾的),然后再覆盖token之后,对这些值再重写一下。
unsignedlonglong tmp = ReadPtrFromKernelMemory(MyEPROCESSAddr + OFFSET_SECTOKEN_WIN7+8);
wchar_t tmp2[5] = { 0x00 };
tmp2[3] = (tmp 48) & 0xFFFF;
tmp2[2] = (tmp >> 32) & 0xFFFF;
tmp2[1] = (tmp >> 16) & 0xFFFF;
tmp2[0] = (tmp >> 0) & 0xFFFF;
....
....
WriteKernelAddress(MyEPROCESSAddr + OFFSET_SECTOKEN_WIN7 + 8, tmp2);
2、当lpString字符串长度大于成员StrName的MaximumLength(该值在第一次调用SetWindowText时确定)时,会在Buffer指针指向的内存处写入任意值,最终导致BOSD(因为对当前进程的token值附近进行了修改)。
因此在第一次调用SetWindowText时(为了之后测试是否能写成功),尽量使StrName的MaximumLength大一些。
3、tagWND结构在server 2012系统中已经不公开。因此也花了些时间确定其成员StrName,cbwndExtra的偏移量及tagWND结构的长度。
UINT SetDIBColorTable(
HDC hdc,
UINT iStart,
UINT cEntries,
const RGBQUAD *prgbq
);
4 个参数
A. Gdi32.dll
SetDIBColorTable()->ZwGdiDoPalette()->系统调用号
图1
B. User32.dll
存在ZwGdiDoPalette()
C. Win32k.sys
NtGdiDoPalette()->GreSetDIBColorTable()->XEPALOBJ::vCopy_rgbquad
参数如图1中的ZwGdiDoPalette():
因此调用GreSetDIBColorTable()参数值同SetDIBColorTable()。
Surface_object = *(struct SURFACE **)(HDC_Copy2 + 0x1F8);
// surface object pointer from HDC+0x1F8[3]
....
v10 = *((_QWORD *)Surface_object + 15);
Obj_15 = v10;
//Obj_15就是PALETTE64对象,这里偏移了15
....
XEPALOBJ::vCopy_rgbquad((XEPALOBJ *)&Obj_15, rgb, start, numm);
//开始覆值
汇编
//r10是change_offset即PALETTEENTRY rdx是rgb即RGBQUAD
//就是对PALETTEENTRY覆值RGBQUAD
两个对象的结构
typedef struct tagRGBQUAD {
BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
} RGBQUAD;
typedef struct tagPALETTEENTRY {
BYTE peRed;
BYTE peGreen;
BYTE peBlue;
BYTE peFlags;
} PALETTEENTRY, *PPALETTEENTRY, *LPPALETTEENTRY;
1、由于_ClientCopyDDEIn1函数在不同系统中实现的细微差异,因此在截持时要做相应的修改,包括HijackTrampoFunc中绿色部分及黄色部分的继续跳转的地址ContinueAddr的值。
2、Fengshui布局中对象大小的设置。
3、Token获取时,内核结构在不同系统中,各成员的偏移位置的不同。
4、任意内存地址读写时,tagWND结构中关键成员偏移位置的确定及tagWND的长度。
5、内核函数的调用(Nt**)转换为调用用户层函数。因为不同系统中内核函数的调用号不同,代码中(win7场景)对内核函数的调用,用在win2012系统中不能成功,好在都有对应的用户层函数,直接替换就好。
- End -
看雪ID:Sebastianasd
https://bbs.pediy.com/user-816842.htm
本文由看雪论坛 Sebastianasd 原创
转载请注明来自看雪社区
﹀
﹀
﹀
京华结交尽奇士,意气相期矜豪纵。今夏与君相约看雪,把酒言欢,共话安全技术江湖梦。
往期热门回顾
2、APICloud解密本地资源到逆向APP算法到通用资源解密
↙点击下方“阅读原文”,查看更多干货