转载

IE安全系列之——RES Protocol与打印预览(II)

0x00 简介

事情得从一个报告说起,有人说网页map.yahoo.co.jp,在IE中进行打印预览时,使用倍率666%会让IE崩溃(至少2016-5-28为止还是这样),为什么IE中会出现这么6的问题呢?从打印预览这个功能看起吧。

IE安全系列之——RES Protocol与打印预览(II)

0x01 打印预览(Web)

从网页的角度来看,负责打印预览的相关内容位于 res://ieframe.dll/preview.dlgres://ieframe.dll/preview.js 。IE自5.5开始引入了打印预览以及自定义的打印预览功能。而微软的早期文档内容十分丰富,可以参考到很多有用信息。

在雅虎网站造成IE的崩溃中,一步不可缺少的操作是——设置缩放比例。所以,我们的关注点可以集中到缩放比率上。

在MSDN介绍自定义打印预览模板时,微软提到:“推荐使用一个DIV作为主容器”。在preview.dlg的LN253-256,可以看到MasterContainer,是一个DIV,这也与微软的推荐做法一致。

#!html <div id="MasterContainer" tabindex="0" style="width:100%; position:absolute;" >     <!-- Pages go here -->     <div id="EmptyPage" class="divPage" style="position:absolute; left:0px; top:0px; display:none;"><div class="page"> </div></div> </div> 

另外,还“推荐使用CSS的zoom属性来为主容器设置百分比的缩放”,这在preview.js中得到了良好的体现:

#!js function PositionPages(nDispPage) { ……         MasterContainer.style.zoom = g_nZoomLevel + "%"; …… 

与:

#!js function ChangeZoom() {     MasterContainer.style.zoom = g_nZoomLevel + "%";     PositionPages(g_nDispPage);     return g_nZoomLevel; } 

在了解了缩放功能进行缩放的方法之后,让我们再看一看页面是如何被转入预览中的。

在preview.js的CPrintDoc_AddPage()函数中,可以看到如下的代码注入到了MasterContainer的beforeEnd处。

#!js newHTM = "<DIV class=divPage><IE:DeviceRect media=/"print/" class=page id=mDiv" + this._nStatus + this._strDoc + "p" + aPage.length + ">"; newHTM += "<IE:LAYOUTRECT id=mRect" + this._nStatus + this._strDoc + "p" + aRect.length; newHTM += " class='" + classLayoutRect + "' nextRect=mRect" + this._nStatus + this._strDoc + "p" + (aRect.length + 1); newHTM += " onlayoutcomplete=/"OnRectComplete('" + this._strDoc + "', " + g_ObsoleteBar + ")/""; newHTM += " tabindex=-1 onbeforefocusenter='event.returnValue=false;' "; newHTM += " />"; newHTM += "<DIV class='" + classHeader + "' id=header>"; newHTM += HeadFoot.HtmlHead; newHTM += "</DIV>"; newHTM += "<DIV class='" + classFooter + "' id=footer>"; newHTM += HeadFoot.HtmlFoot; newHTM += "</DIV>"; newHTM += "</IE:DeviceRect></DIV>"; MasterContainer.insertAdjacentHTML("beforeEnd", newHTM); 

在上面的代码中可以看到一些特殊的元素。比如 IE:DEVICERECTIE:LAYOUTRECT 。DeviceRect、LayoutRect两个元素用来组合展示页面。每个DeviceRect代表一个页面,

LayoutRect为DeviceRect的子元素,IE在它里面显示页面预览。因为preview.dlg指定了XML Namespace( <HTML XMLNS:IE> )所以元素前带有IE:前缀。

与其他元素不同的是,LayoutRect似乎并没有innerHTML属性。IE只是在里面展示页面的预览,页面并不能操作它。那么问题来了,IE怎么产生的预览?

0x02 打印预览窗口的创建

要知道这个,让我们先完整地跟踪整个流程。首先,因为打印预览是在res: protocol下的,之前的文章我们介绍了CResProtocol是处理Res Protocol的类,在DoParseAndBind上下断点可以观察到此处完整的调用(无关的栈已经删除):

#!bash 0:058> bp MSHTML!CResProtocol::DoParseAndBind 0:牌058> g Breakpoint 0 hit eax=14061280 ebx=140611fc ecx=08101bd4 edx=1406127c esi=76c2f1fc edi=120ae270 eip=6474f8dd esp=120ae258 ebp=120ae290 iopl=0         nv up ei pl zr na pe nc cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246 MSHTML!CResProtocol::DoParseAndBind: 6474f8dd 8bff            mov     edi,edi 0:058> kvn  # ChildEBP RetAddr  Args to Child               00 120ae254 647504d6 14061280 14061284 14061274 MSHTML!CResProtocol::DoParseAndBind (FPO: [4,163,4]) 01 120ae268 64750495 120ae2b0 64750410 140611e0 MSHTML!CResProtocol::ParseAndBind+0x26 (FPO: [0,0,0]) 02 120ae290 76c63067 140611fc 06e8c9f0 08109f38 MSHTML!CResProtocol::Start+0x88 (FPO: [6,5,4]) ………………………… 10 120aeeb4 6467517a 120aeed0 00000000 00000000 MSHTML!CMarkup::Load+0x228 (FPO: [1,71,4]) 11 120af26c 6479f0a1 120af2a0 00000000 00000000 MSHTML!CMarkup::LoadFromInfo+0xb07 (FPO: [Non-Fpo]) 12 120af3b0 6479ebf4 120af3d8 00000000 120af490 MSHTML!CDoc::LoadFromInfo+0x48d (FPO: [Non-Fpo]) 13 120af474 65075c07 03ea5400 00000001 081d4de0 MSHTML!CDoc::Load+0xd7 (FPO: [5,41,4]) 14 120af5ec 650772d6 120af678 120af668 080940c8 MSHTML!CHTMLDlg::Create+0x869 (FPO: [Non-Fpo]) 15 120af65c 6507e39d 00000000 10908ed0 00000000 MSHTML!InternalShowModalDialog+0x1c1 (FPO: [Non-Fpo]) 16 120af718 6507e54d 120af770 6629d470 0000000b MSHTML!ModelessThreadInit+0x12f (FPO: [Non-Fpo]) …………………… 

根据15层栈可以知道该窗口是一个Modeless Window,那么这个调用是谁发起的呢?观察其他线程,可以发现下列调用栈(无关栈已经删除):

#!bash 43  Id: 5404.3b18 Suspend: 1 Teb: 7ef7e000 Unfrozen # ChildEBP ………………………… 06 10908f58 6507a498 1449d3c0 054b8f94 081a995c MSHTML!InternalModelessDialog+0x431 (FPO: [Non-Fpo]) 07 10908fe8 6655f9b0 00a62528 081d4de0 000003e0 MSHTML!ShowHTMLDialogEx+0xa8 (FPO: [6,32,0]) 08 109095b4 6649aafe 10909728 00000000 00000001 IEFRAME!CDocHostUIHandler::DoTemplatePrinting+0x31d (FPO: [Non-Fpo]) 09 10909688 650587fb 08260e7c 6464d428 00000007 IEFRAME!`Microsoft::WRL::Module<1,Microsoft::WRL::Details::DefaultModule<1> >::Create'::`2'::`dynamic atexit destructor for 'module''+0x64146 0a 10909b94 648662de 0a4d2b40 00000000 00000000 MSHTML!CDoc::PrintHandler+0x73a (FPO: [Non-Fpo]) 0b 1090a7a8 6464dea1 00000000 663d9884 000007d3 MSHTML!`CBackgroundInfo::Property<CBackgroundImage>'::`7'::`dynamic atexit destructor for 'fieldDefaultValue''+0xbdfbd 0c 1090a7c8 66562101 03ea3f00 663d9884 000007d3 MSHTML!CDoc::Exec+0x21 (FPO: [6,0,0]) 0d 1090ba08 66568f04 080fd650 00000000 1090ba70 IEFRAME!CDocHostUIHandler::ShowContextMenu+0x98d (FPO: [5,1157,4]) 0e 1090ba3c 64f4aaac 004bc898 00000000 1090ba70 IEFRAME!CDocObjectHost::ShowContextMenu+0xd4 (FPO: [Non-Fpo]) 0f 1090ba80 64f7ed1f 00000123 000000d7 00000000 MSHTML!CDoc::ShowContextMenu+0x137 (FPO: [4,7,4]) 10 1090ba9c 64f7dbe6 00000123 000000d7 00000000 MSHTML!CElement::ShowContextMenu+0x1f (FPO: [Non-Fpo]) 11 1090bc00 64eb5e79 1090bd78 00000000 64647180 MSHTML!CElement::OnContextMenu+0x17b (FPO: [2,81,4]) 12 1090bc50 64bb244c 0a58da40 1090bd78 1090bd78 MSHTML!`CBackgroundInfo::Property<CBackgroundImage>'::`7'::`dynamic atexit destructor for 'fieldDefaultValue''+0xb62fa 13 1090bc70 645c789e 0a58da40 1090bd78 03ea3f00 MSHTML!CElement::HandleMessage+0xc2 (FPO: [Non-Fpo]) 14 1090bc90 645c7430 14054840 0000007b 03ea3f00 MSHTML!CElement::HandleWindowMessage+0x6b (FPO: [Non-Fpo]) 15 1090bd1c 647e7558 1090bd78 14054840 00000000 MSHTML!CDoc::PumpMessage+0x638 (FPO: [3,29,4]) 16 1090be94 64cee296 0000007b 00a62528 00d70123 MSHTML!CDoc::OnMouseMessage+0x2b4 (FPO: [7,85,4]) ………………………… 

可以发现

  1. MSHTML!CDoc::PrintHandler处理了相关的打印请求;
  2. 最终页面启动了一个新IE窗口用来加载res://ieframe.dll/preview.dlg(IEFRAME!CDocHostUIHandler::DoTemplatePrinting → MSHTML!ShowHTMLDialogEx)。

0x03 CDoc::PrintHandler

有了之前的结论之后,首先来观察MSHTML!CDoc::PrintHandler。在IDA中打开MSHTML.DLL,定位到CDoc::PrintHandler后查看伪代码(下为Win7+IE11的结果)。

首先CDoc::PrintHandler检查指定的CLSID是否被支持。下伪代码实际含义pCommandTarget->Exec(&CGID_DocHostCommandHandler, a10(即:guid), a7, &varIn (即:v73), &varOut (即:v67)); 。

#!cpp if ( pCommandTarget && a7 != 2 && !(*(_DWORD *)(v10 + 3584) & 0x400000) ) {     hr = (*(int (__stdcall **)(int, const GUID *, int, int, int, int *))(*(_DWORD *)pCommandTarget + 16))(             pCommandTarget,             &CGID_DocHostCommandHandler,             (a10 != 0) + 6,             a7,             v73,             v67);     v11 = hr;     if ( hr != OLECMDERR_E_NOTSUPPORTED && hr != OLECMDERR_E_UNKNOWNGROUP && hr != OLECMDERR_E_DISABLED)       goto Cleanup;     v12 = v72; } 

接着,程序寻找是否有alternative print source,也即由 <link> 元素的 REL=alternate MEDIA=print 指定的其他可选打印源。如果没有,做一些简单的字符串处理工作:

#!cpp if ( !CDoc::GetAlternatePrintDoc((const unsigned __int16 *)v71, v12, (unsigned __int16 *)&v78, v12) )   {     v16 = &v78;     do     {       v17 = *(_WORD *)v16;       v16 += 2;     }     while ( v17 != (_WORD)v69 ); //wcslen     if ( (signed int)(v16 - (char *)&v79) >> 1 )     {       v15 = (int)&v78;       v71 = (int)&v78; LABEL_14:       if ( !RatingEnabledQuery() )       {         v11 = 0x80004005u;         goto Cleanup;       }       goto LABEL_16;     }   } 

再接下来是一个比较长的函数段。首先程序判断是否有可打印的plugin site。例如acrobat pdf都算是这种。然后发送“Print”请求。MSDN解释是“用默认模版或自定义模版”来打印( https://msdn.microsoft.com/en-us/library/aa769937(v=vs.85).aspx )。

#!cpp v73 = 0; if( !CDoc::GetPlugInSiteForPrinting(v72, &v73) ) {     v18 = v73;     if ( a10 )     {       v11 = -2147467259;     }     else     {     memset(&v50, 0, 0x20u);     v69 = 0;     Cookie = 0;     //发送IDM_PRINT请求     v11 = (*(int (__stdcall **)(int, GUID *, wchar_t **, signed int, LCID, ULONG_PTR *))(*(_DWORD *)v73 + 20))(             v73,             &GUID_NULL,             &off_6449DFC0, //"Print"             1,             g_lcidUserDefault,             &Cookie);     if ( v11 )     {         FreeEXCEPINFO(v46);         goto LABEL_70;     }     VariantInit(&pvarg);     v55 = 0;     v53 = 0;     v54 = 0;     //     v11 = (*(int (__stdcall **)(int, ULONG_PTR, GUID *, LCID, signed int, char *, VARIANTARG *, char *, int *))(*(_DWORD *)v18 + 24))(             v18,             Cookie,             &GUID_NULL,             g_lcidUserDefault,             1,             &v52,             &pvarg,             &v50,             &v69);     VariantClear(&pvarg);     FreeEXCEPINFO(v46);     } LABEL_69:     ReleaseInterface((struct IUnknown *)v46);     goto LABEL_70; } 

然后,可以看到PrintHandler将当前页面保存到了临时目录下。

#!cpp CDoc::PrintHandler(CDocument *,ushort const *,ushort const *,ulong,tagSAFEARRAY *,ulong,tagVARIANT *,tagVARIANT *,int)  {   ......   if ( !v71 )   {     CDoc::SetTempFileTracking(1);     Cookie = CDoc::HasTextSelection(v10);     CDoc::SaveToTempFileForPrint(       v72,       &v78,       260u,       Cookie != 0 ? (int)&v77 : 0,       Cookie != 0 ? 0x104 : 0,       Cookie != 0 ? (int)&v77 : 0);     CDoc::TransferTempFileList(&v56);     CDoc::SetTempFileTracking(0);     v71 = (int)&v78;   }   ...... 

随后,SetPrintCommandParameters、SetPrintManagerCommandParameters用来设置打印参数,先跳过不看,之后有一个关键操作CreateHTMLDocSource,则是负责弹出打印窗口的主人公。

#!cpp v11 = CreateHTMLDocSource(v61, v39, v40, v38, (struct IInspectable **)v48, v49); 

0x04 CreateHTMLDocSource

CreateHTMLDocSource代码简单明了,列举如下:

#!cpp HRESULT __fastcall CreateHTMLDocSource(int a1, int a2, int a3, struct IWebPlatformHostSecurityManagerFactory *a4, struct IInspectable **a5, bool a6) {   HRESULT v6; // [email protected]

int v8; // [sp+4h] [bp-4h]@1 v8 = 0; v6 = HTMLDocumentSource::Create((int)&v8, a1, a2, (char)a4); if ( v6 >= 0 ) v6 = HTMLDocumentSource::QueryInterface(v8, &_GUID_af86e2e0_b12d_4c6a_9c5a_d7aa65101e90, a3); TSmartPointer<HTMLDocumentSource>::_TSmartPointer<HTMLDocumentSource>(&v8); return v6; }

调用HTMLDocumentSource::Create并QueryInterface并返回在参数a3中。查看HTMLDocumentSource::Create的代码,也是一个短小的函数:

#!cpp HRESULT __fastcall HTMLDocumentSource::Create(int a1, int a2, int a3, char a4) {   int v4; // [email protected]
int v5; //[email protected] LPVOID v6; //[email protected] int v7; //[email protected] HRESULT v8; // [email protected]

int v10; // [sp+Ch] [bp-4h]@4 v4 = a2; v5 = a1; v6 = HeapAlloc(g_hProcessHeap, 0, 0x60u); if ( v6 ) v7 = HTMLDocumentSource::HTMLDocumentSource(v6); else v7 = 0; v10 = v7; if ( v7 ) { v8 = HTMLDocumentSource::_Initialize((void *)v7, v4, a3, a4); if ( v8 >= 0 ) { v10 = 0; *(_DWORD *)v5 = v7; } } else { v8 = -2147024882; } TSmartPointer<HTMLDocumentSource>::_TSmartPointer<HTMLDocumentSource>(&v10); return v8; }

函数创建HTMLDocumentSource类,并调用_Initialize函数进行初始化。构造函数的代码简单易懂,全部都是赋初值的,跳过不叙述了。接下来查看_Initialize。

_Initialize是一个较长的函数,但是逻辑较为清晰,我把所有IDA没有识别出的guid等全部以注释的形式标记了。该函数先获取了IHTMLEventObj2接口,然后识别环境并设置浏览模式为 __IE_Immersive (Win8 Immersive Mode),若Immersive Mode生效,再设置成 __IE_ShrinkToFit (Shrink to fit模式,该模式下浏览器自动将页面收缩成方便打印的大小)模式。

IE安全系列之——RES Protocol与打印预览(II)

然后,调用PrintManagerOptions::Create。这个操作将创建一个PrintManagerOptions对象,并调用其_Initialize方法,干的事情也就是拿到它的IUnkonwn接口,所以这块我们也跳过。

接下来,IE试图打开“res://ieframe.dll/preview.dlg”,很熟悉的字眼。告知该处理程序临时文件位置等设置。

#!cpp HRESULT __thiscall HTMLDocumentSource::_Initialize(void *this, int a2, int a3, char a4) {   int v4; // [email protected]
LPVOID *v5; //[email protected] HRESULT v6; //[email protected] int v7; //[email protected] IUnknown *v8; //[email protected] LPUNKNOWN *v9; //[email protected] int v10; //[email protected] int v11; //[email protected] int v12; //[email protected] const WCHAR *v13; //[email protected] BSTR v14; //[email protected] LPMONIKER v15; //[email protected] struct IMonikerVtbl *v16; // [email protected]

LONG v18; // [sp-4h] [bp-130h]@23 struct HTMLDLGINFO *v19; // [sp+0h] [bp-12Ch]@24 int v20; // [sp+4h] [bp-128h]@24 int Dst; // [sp+10h] [bp-11Ch]@24 LPMONIKER v22; // [sp+14h] [bp-118h]@24 VARIANTARG *v23; // [sp+20h] [bp-10Ch]@24 char v24; // [sp+28h] [bp-104h]@26 int *v25; // [sp+2Ch] [bp-100h]@24 int v26; // [sp+30h] [bp-FCh]@24 char v27; // [sp+38h] [bp-F4h]@26 int v28; // [sp+48h] [bp-E4h]@24 int v29; // [sp+60h] [bp-CCh]@24 int v30; // [sp+70h] [bp-BCh]@24 int v31; // [sp+74h] [bp-B8h]@24 int v32; // [sp+78h] [bp-B4h]@24 int v33; // [sp+7Ch] [bp-B0h]@24 BSTR v34; // [sp+80h] [bp-ACh]@24 int v35; // [sp+84h] [bp-A8h]@24 VARIANTARG pvargSrc; // [sp+88h] [bp-A4h]@14 int v37; // [sp+98h] [bp-94h]@5 int v38; // [sp+9Ch] [bp-90h]@5 int v39; // [sp+A0h] [bp-8Ch]@5 int v40; // [sp+A4h] [bp-88h]@5 int v41; // [sp+A8h] [bp-84h]@14 int v42; // [sp+ACh] [bp-80h]@14 LPCWSTR szURL; // [sp+B0h] [bp-7Ch]@14 int v44; // [sp+B4h] [bp-78h]@14 int *v45; // [sp+B8h] [bp-74h]@13 int v46; // [sp+BCh] [bp-70h]@1 VARIANTARG pvarg; // [sp+C0h] [bp-6Ch]@8 LPUNKNOWN punkOuter; // [sp+D4h] [bp-58h]@1 void *v49; // [sp+D8h] [bp-54h]@1 LPMONIKER ppmk; // [sp+DCh] [bp-50h]@21 LONG v51; // [sp+E0h] [bp-4Ch]@4 char v52; // [sp+E7h] [bp-45h]@5 char v53; // [sp+E8h] [bp-44h]@14 unsigned int v54; // [sp+124h] [bp-8h]@1 int v55; // [sp+12Ch] [bp+0h]@1 v54 = (unsigned int)&v55 ^ __security_cookie; punkOuter = 0; v4 = (int)this; v46 = a3; v49 = this; v5 = (LPVOID *)TSmartPointer<CDCompLayer>::operator_((char *)this + 52); v6 = CoCreateInstance(&CLSID_StdGlobalInterfaceTable, 0, 1u, &_GUID_00000146_0000_0000_c000_000000000046, v5); //guid of IGlobalInterfaceTable if ( v6 >= 0 ) { v7 = TSmartPointer<CDCompLayer>::operator_(&punkOuter); v6 = HTMLDocumentSource::QueryInterface(v4, &_GUID_00000000_0000_0000_c000_000000000046, v7); //querying IID_IUnknown if ( v6 >= 0 ) { v8 = punkOuter; v9 = (LPUNKNOWN *)TSmartPointer<CDCompLayer>::operator_(v4 + 56); v6 = CoCreateFreeThreadedMarshaler(v8, v9); if ( v6 >= 0 ) { v51 = 0; v10 = TSmartPointer<IDispEffectConvolveMatrix>::operator_(&v51); v6 = (**(int (__stdcall ***)(int, GUID *, int))a2)(a2, &_GUID_3050f48b_98b5_11cf_bb82_00aa00bdce0b, v10); //getting IHTMLEventObj2 if ( v6 >= 0 ) { v52 = 1; v37 = 0; v38 = 0; v39 = 0; v40 = 0; if ( (*(int (__stdcall **)(LONG, _DWORD, _DWORD, int *))(*(_DWORD *)v51 + 32))( v51, L"__IE_Immersive", 0, &v37) >= 0 && (_WORD)v37 == 11 && -1 == (_WORD)v39 ) { *(_QWORD *)&pvarg.vt = 0i64; *(_QWORD *)&pvarg.lVal = 0i64; if ( (*(int (__stdcall **)(LONG, _DWORD, _DWORD, VARIANTARG *))(*(_DWORD *)v51 + 32))( v51, L"__IE_ShrinkToFit", 0, &pvarg) < 0 || pvarg.vt != 11 || (v52 = 1, -1 != LOWORD(pvarg.lVal)) ) v52 = 0; VariantClear(&pvarg); } v45 = (int *)((char *)v49 + 60); v11 = TSmartPointer<PrintManagerOptions>::operator_((char *)v49 + 60); LOBYTE(v12) = v52; v6 = PrintManagerOptions::Create(v12, v11); if ( v6 >= 0 ) { v41 = 0; v42 = 0; szURL = 0; v44 = 0; memcpy(&v53, L"res://ieframe.dll/preview.dlg", 0x3Cu); *(_QWORD *)&pvargSrc.vt = 0i64; *(_QWORD *)&pvargSrc.lVal = 0i64; if ( (*(int (__stdcall **)(LONG, _DWORD, _DWORD, VARIANTARG *))(*(_DWORD *)v51 + 32))( v51, L"__IE_TemporaryFiles", 0, &pvargSrc) >= 0 && pvargSrc.vt == 8200 ) VariantCopy((VARIANTARG *)((char *)v49 + 72), &pvargSrc); if ( (*(int (__stdcall **)(_DWORD, _DWORD, _DWORD, int *))(*(_DWORD *)v51 + 32))( v51, L"__IE_TemplateUrl", 0, &v41) < 0 || (_WORD)v41 != 8 || (v13 = szURL) == 0 ) v13 = (const WCHAR *)&v53; ppmk = 0; v6 = CreateURLMonikerEx(0, v13, &ppmk, 1u); if ( v6 >= 0 ) { *(_QWORD *)&pvarg.vt = 0i64; *(_QWORD *)&pvarg.lVal = 0i64; pvarg.vt = 13; v6 = PrintManagerOptions::QueryInterface( *v45, &_GUID_00000000_0000_0000_c000_000000000046, (int)&pvarg.lVal); if ( v6 >= 0 ) { v18 = 0; v6 = (*(int (__stdcall **)(LONG, _DWORD, _DWORD, _DWORD, LONG, _DWORD, _DWORD))(*(_DWORD *)v51 + 28))( v51, L"__PE_PrintManagerOptions", *(_DWORD *)&pvarg, *(_DWORD *)&pvarg.wReserved2, pvarg.lVal, HIDWORD(pvarg.dblVal), 0); if ( v6 >= 0 ) { VariantClear(&pvarg); pvarg.vt = 13; pvarg.lVal = v51; v18 = v51; (*(void (__stdcall **)(LONG))(*(_DWORD *)v51 + 4))(v51); HTMLDLGINFO::HTMLDLGINFO(&Dst); v32 = 0; v33 = 0; v34 = 0; v35 = 0; LOWORD(v32) = 8; v14 = SysAllocString(0); Dst = 0; v29 = 0; v34 = v14; v22 = ppmk; v23 = &pvarg; v25 = &v32; v28 = 720; v26 = 1; v30 = 1; v31 = v46; v6 = InternalModelessDialog(v19, v20); if ( v6 >= 0 ) *((_BYTE *)v49 + 88) = a4; VariantClear((VARIANTARG *)&v32); VariantClear((VARIANTARG *)&v27); CStr::_Free(&v24); } } VariantClear(&pvarg); } v15 = ppmk; ppmk = 0; if ( v15 ) { v16 = v15->lpVtbl; v18 = (LONG)v15; v16->Release(v15); } VariantClear(&pvargSrc); VariantClear((VARIANTARG *)&v41); } VariantClear((VARIANTARG *)&v37); } TSmartPointer<IMFGetService>::_TSmartPointer<IMFGetService>(&v51); } } } TSmartPointer<IMFGetService>::_TSmartPointer<IMFGetService>(&punkOuter); return v6; }

最后,通过InternalModelessDialog启动该窗口实现预览

#!cpp HRESULT __usercall InternalModelessDialog<eax>(int a1<edx>, int a2<ecx>, HRESULT a3<esi>) 

那么这个预览窗口中显示的文档和其他的到底有什么不同呢?根据MSDN文档的意思是:有点像禁止了Script的WebBrowser。代码将Layout当作一个容器来显示预览后的内容。在Modeless Window中,我们不能F12,不能呼出右键菜单,怎么办?我们有另一种方法——通过IE暴露的接口来取得整个窗口的DOM内容。

0x05 使用C++获取打印预览窗口的body.outerHTML

这个逻辑中,预览的窗口标题为“打印预览”。可以使用FindWindow找到对应的窗口并枚举出正确的Server窗口,或是使用Spy++找到窗口的HWND(因为这里我们并不是写一个通用的工具,所以怎么方便怎么来)。

注意目标窗口是Internet Explorer_Server而不是外层的Internet Explorer_TridentDlgFrame。这里使用的代码如下:

#!cpp // prjFindPrintview.cpp : 定义控制台应用程序的入口点。 //  #include "stdafx.h" #include <Windows.h> #include <mshtml.h> #include <Exdisp.h> #include <atlbase.h> #include <SHLGUID.h> #include <oleacc.h> #include <comdef.h> #include <tchar.h> #pragma comment(lib, "Oleacc.lib")  int _tmain(int argc, _TCHAR* argv[]) {     ::CoInitialize(NULL);      HRESULT hr = S_OK;      UINT nMsg = ::RegisterWindowMessage(_T("WM_HTML_GETOBJECT"));     LRESULT lRes = 0;     ::SendMessageTimeout((HWND)0x000711FA, nMsg, 0L, 0L, SMTO_ABORTIFHUNG, 1000, (PDWORD)&lRes);      CComPtr<IHTMLDocument2> spDoc;     hr = ObjectFromLresult(lRes, IID_IHTMLDocument2, 0, (void**)&spDoc);     if (FAILED(hr))          return -1;      CComPtr<IHTMLElementCollection> spElementCollection;     hr = spDoc->get_all(&spElementCollection);     if (FAILED(hr))          return -2;      long lElementCount;     hr = spElementCollection->get_length(&lElementCount);     if (FAILED(hr))          return -3;      VARIANT vIndex; vIndex.vt = VT_I4;     VARIANT vSubIndex; vSubIndex.vt = VT_I4; vSubIndex.lVal = 0;     for (vIndex.lVal = 0; vIndex.lVal < lElementCount; vIndex.lVal++)     {         CComPtr<IDispatch> spDispatchElement;         if (FAILED(spElementCollection->item(vIndex, vSubIndex, &spDispatchElement)))             continue;         CComPtr<IHTMLElement> spElement;         if (FAILED(spDispatchElement->QueryInterface(IID_IHTMLElement, (void**)&spElement)))             continue;         CComBSTR outerHTML;         spElement->get_outerHTML(&outerHTML);         OutputDebugStringW(outerHTML);       }      ::CoUninitialize();     return 0; } 

取得窗口的DOM如下:

IE安全系列之——RES Protocol与打印预览(II)

将其输出到文件,自行添上 </BODY></HTML> 即为完整的内容。不过需要记得的是Layout等元素只在打印预览中有效,所以单独提取出来崩溃不了也不要太疑惑,这里只是为了方便了解预览中最终的DOM分布情况而已。

接下来,让我们复现崩溃吧。打开map.yahoo.co.jp,访问出现崩溃的代码,右键预览。只不过这次我们使用缓存文件的方式来精简代码。预览后,从%TEMP%下复制走所有新生成的HTM文件,然后调节打印预览的比例为666%。在确定仍然可以崩溃之后,让我们开始删除无用数据,记下异常偏移e9b102(在我的机器上是这个数字,因为有些崩溃在精简代码时可能变成其他的崩溃,所以最好记下偏移地址,以免中途不知道从什么位置产生了新问题)。多次精简之后,我们留下了下列代码:

#!html <!DOCTYPE HTML> <HTML><HEAD> </HEAD>  <BODY> <DIV style="left: -5000px; top: -5000px; width: 10000px; height: 10000px; text-align: justify; position: absolute; z-index: 0;"><svg  xmlns="http://www.w3.org/2000/svg" style="position: relative;" viewBox="0 0 10000 10000"  width="10000" height="10000" /> </DIV> </BODY></HTML> 

而且精简到这一步就可以发现,只要发起打印预览,IE就会直接崩溃。所以,为了让IE自动触发崩溃,我们手写一下调用print();即可。

#!html <!DOCTYPE HTML> <HTML><HEAD> </HEAD>  <BODY> <DIV style="left: -5000px; top: -5000px; width: 10000px; height: 10000px; text-align: justify; position: absolute; z-index: 0;"><svg  xmlns="http://www.w3.org/2000/svg" style="position: relative;" viewBox="0 0 10000 10000"  width="10000" height="10000" /> </DIV> <script>print();</script> </BODY></HTML> 

跟随调试可以发现,崩溃位置维持不变:

#!bash 0:040> g (24c8.2960): Access violation - code c0000005 (!!! second chance !!!) eax=00000000 ebx=00000000 ecx=00000000 edx=00000000 esi=045c9220 edi=0a68eeac eip=6537b102 esp=0a68eea4 ebp=0a68eeb8 iopl=0         nv up ei pl zr na pe nc cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246 MSHTML!CDispParentNode::ComputeVisibleBoundsOnDirtySVGSubtree+0x16: 6537b102 8b33            mov     esi,dword ptr [ebx]  ds:002b:00000000=???????? 

接下来让我们看看打印预览窗口的DOM结构。因为DIV的STYLE属性是触发崩溃的关键条件之一,但是我们又想拿到预览窗口的DOM,所以我们得做一些改动,先手动删除部分STYLE,然后预览,这时,便可使用我们之前的程序抓出DOM。

篇幅考虑我就不扯淡再去看这个DOM了,直接以上述精简后的代码开始分析工作。

0x06 崩溃分析

在崩溃后查看崩溃栈如下:

#!bash 0:033> kvn  # ChildEBP RetAddr  Args to Child               00 0b2df530 64c7b6b7 0b2df75c 0b2df75c 64c7b63a MSHTML!CDispParentNode::ComputeVisibleBoundsOnDirtySVGSubtree+0x16 (FPO: [Non-Fpo]) 01 0b2df774 645de05b 0b2df7bc 0b2df79a 0b2dfb3c MSHTML!`CBackgroundInfo::Property<CBackgroundImage>'::`7'::`dynamic atexit destructor for 'fieldDefaultValue''+0x2b2eb 02 0b2df860 64b4110c 0b2dfb3c 0b2dfb3c 0b2dfb3c MSHTML!Layout::FlowBoxBuilder::BuildBoxItem+0x89 (FPO: [1,51,4]) 03 0b2df87c 64b410d7 0b2dfb3c 0d82d0e0 0b2df8e4 MSHTML!Layout::LayoutBuilder::BuildBoxItem+0x2e (FPO: [Non-Fpo]) 04 0b2df88c 64b40193 0b2dfb3c 0b2df908 64e657d0 MSHTML!Layout::LayoutBuilder::Move+0x57 (FPO: [Non-Fpo]) 05 0b2df8e4 65358d8d 0d83264c 00000000 0d83264c MSHTML!Layout::LayoutBuilderDriver::BuildElementLayout+0xce (FPO: [Non-Fpo]) 06 0b2df974 65359137 ffffffff 00000001 04c29ab4 MSHTML!Layout::MultiFragmentBoxBuilder::BuildCrossFragmentPositionedElement+0x1c3 (FPO: [Non-Fpo]) 07 0b2df9d4 64ec4753 0b2dfa24 0d82d020 0d82cff0 MSHTML!Layout::MultiFragmentBoxBuilder::PositionAndArrangeCrossFragmentAbsolutePositionedElement+0x17e (FPO: [Non-Fpo]) 08 0b2dfa2c 645d06b0 0d7e8ba0 04c29ab4 0b2dfb3c MSHTML!`CBackgroundInfo::Property<CBackgroundImage>'::`7'::`dynamic atexit destructor for 'fieldDefaultValue''+0xce2e3 09 0b2dfb14 645d72b9 0b2dfb3c 0b2dfb48 645d6ae0 MSHTML!Layout::PageCollection::LayoutPagesCore+0x37e (FPO: [Non-Fpo]) 0a 0b2dfb40 65340900 65340760 0b2dfb88 0d832648 MSHTML!Layout::PageCollection::LayoutPages+0xca (FPO: [Non-Fpo]) 0b 0b2dfb7c 64a9ec7e 00100000 04c29500 00000000 MSHTML!Layout::PageCollection::DoLayout+0x1a0 (FPO: [1,9,4]) 0c 0b2dfbcc 64592161 00100000 64a7d7f0 00000000 MSHTML!CView::ExecuteLayoutTasks+0x159 (FPO: [Non-Fpo]) 0d 0b2dfc24 64a7d840 00000000 64a7d7f0 0b2dfc54 MSHTML!CView::EnsureView+0x3bb (FPO: [1,15,4]) 0e 0b2dfc44 64585813 04c29b38 00000000 00000001 MSHTML!CView::EnsureViewCallback+0x50 (FPO: [Non-Fpo]) 0f 0b2dfc8c 6456d52c 3f72ceb5 00000000 6456cc90 MSHTML!GlobalWndOnMethodCall+0x17b (FPO: [Non-Fpo]) 10 0b2dfce0 769f62fa 000825e0 00008002 00000000 MSHTML!GlobalWndProc+0x103 (FPO: [Non-Fpo]) 11 0b2dfd0c 769f6d3a 6456cc90 000825e0 00008002 USER32!InternalCallWinProc+0x23 12 0b2dfd84 769f77c4 0539315c 6456cc90 000825e0 USER32!UserCallWinProcCheckWow+0x109 (FPO: [Non-Fpo]) 13 0b2dfde4 769f788a 6456cc90 00000000 0b2dfe40 USER32!DispatchMessageWorker+0x3bc (FPO: [Non-Fpo]) 14 0b2dfdf4 6507e640 0b2dfe24 0b2dfe4c 6629d470 USER32!DispatchMessageW+0xf (FPO: [Non-Fpo]) 15 0b2dfe40 6ed93a31 05c7ab90 00000000 00000000 MSHTML!ModelessThreadProc+0x1a0 (FPO: [1,13,4]) 

MSHTML!CDispParentNode::ComputeVisibleBoundsOnDirtySVGSubtree是一个Thiscall,代码十分简单,就是调用自己一个成员函数:

#!cpp int __thiscall CDispParentNode::ComputeVisibleBoundsOnDirtySVGSubtree(void *this, int a2, int a3, int a4, float a5) {   return (*(int (__stdcall **)(_DWORD, int, _DWORD, _DWORD, _DWORD, _DWORD))(*(_DWORD *)this + 216))(            0,            a3,            0,            0,            LODWORD(a5),            0); } 

查看函数代码,很明显,this指针为null。

#!bash 0:033> uf . MSHTML!CDispParentNode::ComputeVisibleBoundsOnDirtySVGSubtree: 6537b0ec 8bff            mov     edi,edi 6537b0ee 55              push    ebp 6537b0ef 8bec            mov     ebp,esp 6537b0f1 d94514          fld     dword ptr [ebp+14h] 6537b0f4 33c0            xor     eax,eax 6537b0f6 53              push    ebx 6537b0f7 56              push    esi 6537b0f8 57              push    edi 6537b0f9 8bfc            mov     edi,esp 6537b0fb 8bd9            mov     ebx,ecx    ; ebx = ecx = this 6537b0fd 50              push    eax 6537b0fe 51              push    ecx 6537b0ff d91c24          fstp    dword ptr [esp] 6537b102 8b33            mov     esi,dword ptr [ebx]  ; esi = *ebx 

查看上一层

#!bash 0:033> ub . MSHTML!`CBackgroundInfo::Property<CBackgroundImage>'::`7'::`dynamic atexit destructor for 'fieldDefaultValue''+0x2b2d4: 64c7b6a0 83ec08          sub     esp,8 64c7b6a3 d95c2404        fstp    dword ptr [esp+4] 64c7b6a7 51              push    ecx 64c7b6a8 51              push    ecx 64c7b6a9 8b08            mov     ecx,dword ptr [eax] 64c7b6ab e8307699ff      call    MSHTML!Layout::LayoutBox::GetDisplayNodeAsParent (64612ce0) 64c7b6b0 8bc8            mov     ecx,eax 64c7b6b2 e835fa6f00      call    MSHTML!CDispParentNode::ComputeVisibleBoundsOnDirtySVGSubtree (6537b0ec) 

很明显MSHTML!Layout::LayoutBox::GetDisplayNodeAsParent返回了NULL。

#!cpp int __thiscall Layout::LayoutBox::GetDisplayNodeAsParent(void *this) {   int pThisVar; // [email protected]
int nResult; //[email protected] int v3; //[email protected] int v4; //[email protected] int v5; // [email protected]

pThisVar = (*(int (**)(void))(*(_DWORD *)this + 788))(); nResult = pThisVar; if ( !pThisVar ) goto Cleanup; v3 = *(_DWORD *)(pThisVar + 28); v4 = pThisVar; if ( *(_BYTE *)(v3 + 56) & 1 && !*(_BYTE *)(*(_DWORD *)(__readfsdword(44) + 4 * LODWORD(_tls_index)) + 36) && *(_DWORD *)(v3 + 4) != *(_DWORD *)(pThisVar + 8) ) { v5 = *(_DWORD *)(pThisVar + 4); if ( !v5 ) v5 = nResult; if ( v5 != nResult ) v4 = v5; } if ( !(*(_BYTE *)(v4 + 21) & 0x20) ) { Cleanup: nResult = NULL; } return nResult; }

可以简单跟踪一下代码看看到底是哪儿出现了问题。为了避免重复断到断点上(Layout的调用是十分频繁的),可以

bp MSHTML!ModelessThreadProc  

触发后

bc 0 bp MSHTML!Layout::FlowBoxBuilder::BuildBoxItem 

触发后

bc 0 bp MSHTML!Layout::LayoutBox::GetDisplayNodeAsParent 

这样就没问题了

#!bash 0:031> g Breakpoint 1 hit eax=05618f80 ebx=0c07f060 ecx=0eebc4e0 edx=00000002 esi=0ee946e0 edi=0ee5ecb0 eip=64612ce0 esp=0c07f04c ebp=0c07f28c iopl=0         nv up ei pl nz ac po nc cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000212 MSHTML!Layout::LayoutBox::GetDisplayNodeAsParent: 64612ce0 8bff            mov     edi,edi 0:031> k ChildEBP RetAddr   0c07f048 64c7b6b0 MSHTML!Layout::LayoutBox::GetDisplayNodeAsParent 0c07f28c 645de05b MSHTML!`CBackgroundInfo::Property<CBackgroundImage>'::`7'::`dynamic atexit destructor for 'fieldDefaultValue''+0x2b2e4 0c07f378 64b4110c MSHTML!Layout::FlowBoxBuilder::BuildBoxItem+0x89 0c07f394 64b410d7 MSHTML!Layout::LayoutBuilder::BuildBoxItem+0x2e 0c07f3a4 64b40193 MSHTML!Layout::LayoutBuilder::Move+0x57 0c07f3fc 65358d8d MSHTML!Layout::LayoutBuilderDriver::BuildElementLayout+0xce 

再度跟踪可以发现是

#!cpp pThisVar = (*(int (**)(void))(*(_DWORD *)this + 788))();// MSHTML!Layout::SvgBox::GetDisplayNode //returns null nResult = pThisVar; if ( !pThisVar )     goto Cleanup; //return NULL(0); 

查看GetDisplayNode,很明显,校验失败,返回了0.

#!cpp int __thiscall Layout::SvgBox::GetDisplayNode(int this) {   int result; // [email protected]

if ( !(*(_BYTE *)(this + 56) & 4) || *(_BYTE *)(*(_DWORD *)(__readfsdword(44) + 4 * LODWORD(_tls_index)) + 36) ) result = *(_DWORD *)(this + 16); else result = 0; return result; }

加上对内存的跟踪,我们可以断定这是一个空指针问题。不过这倒也给了一个提醒——是否一些在网页内不能触发的问题,通过打印预览就又可以做了呢?Fuzzer看来也可以加上这个策略,试一试打印预览是否健壮。

原文  http://drops.wooyun.org/tips/16275
正文到此结束
Loading...