当前位置: 首页 > 科技观察

高手浅谈:IE和Windows两个0-day漏洞分析

时间:2023-03-21 20:20:54 科技观察

0x00概述2020年5月,卡巴斯基成功防御了针对韩国公司的InternetExplorer恶意脚本攻击。进一步分析显示,该工具使用了一条此前未知的完整利用链,包括两个零日漏洞:InternetExplorer远程代码执行漏洞和Windows提权漏洞。与我们在WizardOpium活动中看到的之前的攻击链不同,新的攻击链可以针对最新版本的Windows10。测试表明,该漏洞可以在InternetExplorer11和Windows10x64版本18363上可靠地被利用。2020年6月8日,我们向微软报告了我们的发现,微软确认了该漏洞。在撰写我们的报告时,Microsoft的安全团队已经发布了CVE-2020-0986漏洞的补丁,修复了这个提权零日漏洞。但是,在我们发现之前,该漏洞的可利用性被评估为“不太可能”。2020年6月9日发布了针对CVE-2020-0986的修复程序。Microsoft将编号CVE-2020-1380分配给了JScriptUse-After-Free漏洞,并于2020年8月11日发布了补丁。我们参考这一系列攻击称为PowerFall战役。目前,我们无法明确地将恶意活动与任何已知的威胁行为者联系起来,但根据其与之前发现的漏洞的相似性,我们认为DarkHotel可能是此次攻击的幕后黑手。卡巴斯基产品目前将PowerFall攻击检测为“PDM:Exploit.Win32.Generic”。0x01InternetExplorer11远程代码执行漏洞最新野外发现的InternetExplorer0-day攻击利用旧版本JavaScript引擎jscript中的漏洞CVE-2020-0674、CVE-2019-1429、CVE-2019-0676和CVE.dll-2018-8653。其中,CVE-2020-1380是jscript9.dll中的一个漏洞,自InternetExplorer9开始就存在,因此微软推荐的缓解措施(限制jscript.dll的使用)无法实现针对该漏洞的防护。CVE-2020-1380是一个释放后使用(Use-After-Free)漏洞,是由于在JIT优化过程中对JIT编译后的代码缺少必要的检查造成的。触发漏洞的PoC如下所示:functionfunc(O,A,F,O2){arguments.push=Array.prototype.push;O=1;arguments.length=0;arguments.push(O2);if(F==1){O=2;}//executeabp.valueOf()andwritebydanglingpointerA[5]=O;};//prepareobjectsvaran=newArrayBuffer(0x8c);varfa=newFloat32Array(an);//compilefuncfunc(1,fa,1,{});for(vari=0;i<0x10000;i++){func(1,fa,1,1);}varabp={};abp.valueOf=function(){//freeworker=newWorker('worker.js');worker.postMessage(an,[an]);worker.terminate();worker=null;//sleepvarstart=Date.now();while(Date.now()-start<200){}//TODO:reclaimfreedmemoryreturn0};try{func(1,fa,0,abp);}catch(e){reload()}为了理解这个漏洞,我们先看看func()是如何执行的。在这里,重要的是要了解要为A[5]设置什么值。根据代码,应该有一个参数O与之关联。在函数开始时,参数O被重新赋值为1,但随后函数参数长度被设置为0。这个操作并没有清除函数参数(通常,常规数组会这样做),但允许放置参数O2在参数列表1中索引为0,这意味着O=O2。除此之外,如果参数F等于1,则O会再次被重新赋值,但这次是整数2。这意味着,根据参数F的值,O参数将等于O2的值参数或整数2。参数A是一个32位浮点数的数组,值先转换为浮点数,然后赋值给数组的索引5。将整数转换为浮点数的过程比较简单,但是如果要将对象转换为浮点数,过程就没那么简单了。该漏洞利用在重载方法valueOf()中使用abp对象。该方法是在对象转为float时执行的,但在其内部包含了释放ArrayBuffer的代码,该ArrayBuffer被Float32Array查看,并在其中设置返回值。为了防止在释放对象的内存中存储值,JavaScript引擎需要在将值存储到对象之前首先检查对象的状态。为了安全地转换和存储浮点值,JScript9.dll使用函数Js::TypedArray::BaseTypedDirectSetItem()。下面是这个函数的反编译代码:intJs::TypedArray::BaseTypedDirectSetItem(Js::TypedArray*this,unsignedintindex,void*object,intreserved){Js::JavascriptConversion::ToNumber(object,this->type->library->context);if(LOBYTE(this->view[0]->unusable))Js::JavascriptError::ThrowTypeError(this->type->library->context,0x800A15E4,0);if(indexcount){*(float*)&this->buffer[4*index]=Js::JavascriptConversion::ToNumber(object,this->type->library->context);}return1;}doubleJs::JavascriptConversion::ToNumber(void*object,structJs::ScriptContext*context){if((unsignedchar)object&1)return(double)((int)object>>1);if(*(void**)object==VirtualTableInfo::Address[0])return*((double*)object+1);returnJs::JavascriptConversion::ToNumber_Full(object,context);}这个函数检查float数组的view[0]->unusable和count字段。valueOf()方法执行过程中,当ArrayBuffer被释放时,这两个检查都会失败,因为此时view[0]->unusable为1,第一次调用Js::JavascriptConversion::ToNumber()计数为0。问题在于Js::TypedArray::BaseTypedDirectSetItem()函数仅在解释模式下使用。当函数func()被即时编译时,JavaScript引擎将使用以下易受攻击的代码:if(!((unsignedchar)floatArray&1)&&*(void*)floatArray==&Js::TypedArray::vftable){if(floatArray->count>index){buffer=floatArray->buffer+4*index;if(object&1){*(float*)buffer=(double)(object>>1);}else{if(*(void*)object!=&Js::JavascriptNumber::vftable){Js::JavascriptConversion::ToFloat_Helper(object,(float*)buffer,context);}else{*(float*)buffer=*(double*)(object->value);}}}}这是Js::JavascriptConversion::ToFloat_Helper()函数的代码:voidJs::JavascriptConversion::ToFloat_Helper(void*object,float*buffer,structJs::ScriptContext*context){*buffer=Js::JavascriptConversion::ToNumber_Full(object,context);}正如我们所见,与解释模式不同,在即时编译代码中,ArrayBuffer的生命周期没有被检查并且它的内存可以被释放,然后在调用valueOf()函数时回收。此外,攻击者可以控制将返回值写入哪个索引。但是,在arguments.length=0的情况下;和arguments.push(O2);,PoC会用arguments[0]=O2;替换它,所以Js::JavascriptConversion::ToFloat_Helper()不会触发这个Bug,因为隐式调用将被禁用,调用valueOf()函数将不会被执行。为确保函数func()的即时编译,漏洞利用执行函数0x10000次,进行无害的整数转换,只有在再次执行func()后,才会触发漏洞。为了释放ArrayBuffer,该漏洞使用了一种滥用WebWorkersAPI的常见技术。postMessage()函数可用于将对象序列化为消息并将其发送给worker。然而,这里的一个副作用是被转移的对象被释放并且在当前脚本上下文中变得不可用。释放ArrayBuffer后,利用程序通过模拟Sleep()函数使用的代码来触发垃圾回收。这是一个while循环,用于检查Date.now()和先前存储的值之间的时间间隔。完成后,该漏洞利用整数数组回收内存。for(vari=0;i