Internet Explorer/wmf图像解析分析

逆向Internet Explorer/Edge的代码,找找思路。初步每周1篇。

Edge里面并不支持wmf,所以这次就光IE了,IE版本:IE 11.0.9600.18499

Windows Metafile (WMF) is a graphics file format on Microsoft Windows systems, originally designed in the early 1990s. Windows Metafiles are intended to be portable between applications and may contain both vector graphics and bitmap components.

Essentially, a WMF file stores a list of function calls that have to be issued to the Windows Graphics Device Interface (GDI) layer in order to display an image on screen

In 2007 Enhanced Metafile (EMF) a newer 32-bit version with additional commands appeared. EMF is also used as a graphics language for printer drivers. The last(?) version of EMF, 4.0, appeared in 2008.

With the release of Windows XP, the Enhanced Metafile Format Plus Extensions (EMF+) format was introduced. EMF+ provides a way to serialize calls to the GDI+ API in the same way that WMF/EMF stores calls to GDI.
  1. WMF Parser

IE中加载wmf后,会经由下列栈走到解析类CImgTaskWmf::ReadImage()中。

0:023> bp mshtml!CImgTaskWmf::ReadImage
0:023> g
Breakpoint 0 hit
eax=00000000 ebx=0a370a00 ecx=0a370a00 edx=02d7e000 esi=0a370a00 edi=0fb8fbc0
eip=5bd84df1 esp=0fb8fb8c ebp=0fb8fbb4 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!CImgTaskWmf::ReadImage:
5bd84df1 8bff            mov     edi,edi
0:023> kvn
 # ChildEBP RetAddr  Args to Child              
00 0fb8fb88 5bd84da2 5bd84d10 00000016 9ac6cdd7 MSHTML!CImgTaskWmf::ReadImage (FPO: [Non-Fpo])
01 0fb8fbb4 5b9d9639 0fb8fbd8 0a39af00 0a370a00 MSHTML!CImgTaskWmf::Decode+0x92 (FPO: [Non-Fpo])
02 0fb8fbf8 5b9d94f4 00000000 5b9d94d0 0fb8fc44 MSHTML!CImgTask::Exec+0x120 (FPO: [Non-Fpo])
03 0fb8fc08 74d259d8 0a39af00 5225dee8 00000000 MSHTML!CImgTaskExec::FiberProc+0x24 (FPO: [1,0,0])
04 0fb8fc44 74d25986 ffffffff 77562f03 00000000 KERNELBASE!_BaseFiberStart+0x49 (FPO: [Non-Fpo])
05 0fb8fc54 00000000 00000000 00000000 00000000 KERNELBASE!BaseFiberStart+0x16 (FPO: [Non-Fpo])

让我们先从这一小截来。IE在加载图片时,CImgHelper会调用void WINAPI CImgTaskExec::FiberProc(void * pv),这个FiberProc相当于做了一个异步加载的工作。传进来的指针pv为PFIBERINFO,FiberProc创建一个CImgTask对象,随即将这个PFIBERINFO中的“ImgTask”的成员传给CImgTask,并调用其Exec函数。

CImgTask::Exec调用Decode。这个Decode是一个虚函数,根据不同的TaskType走。比如这里是Wmf的Task,就会走到CImgTaskWmf::Decode中。

让我们看一看CImgTaskWmf::Decode。以下是几乎没有处理的源代码。

__int32 __thiscall CImgTaskWmf::Decode(CImgTaskWmf *this, int *a2)
{
  CImgTaskWmf *pImgTaskWmf; // esi@1
  int val1; // eax@3
  int v4; // ecx@3
  int v5; // ST04_4@3
  int v6; // ST00_4@3
  __int32 result; // eax@3
  int v8; // [sp+4h] [bp-20h]@1
  char v9; // [sp+8h] [bp-1Ch]@1
  __int16 v10; // [sp+Eh] [bp-16h]@3
  __int16 v11; // [sp+10h] [bp-14h]@3
  __int16 v12; // [sp+12h] [bp-12h]@3
  __int16 v13; // [sp+14h] [bp-10h]@3
  unsigned __int16 v14; // [sp+16h] [bp-Eh]@3

  v8 = 0;
  *a2 = 1;
  pImgTaskWmf = this;
  if ( CImgTask::Read(this, &v9, 0x16u, (unsigned __int32 *)&v8, (unsigned __int32)this) < 0 || v8 != 22 )
  {
    result = 0x80004005;
  }
  else
  {
    val1 = MulDiv(v12 - v10, 96, v14);
    v4 = v13;
    *((_DWORD *)pImgTaskWmf + 29) = abs(val1);
    v5 = abs(MulDiv(v4 - v11, 96, v14));
    v6 = *((_DWORD *)pImgTaskWmf + 29);
    *((_DWORD *)pImgTaskWmf + 30) = v5;
    CImgTask::OnSize(pImgTaskWmf, v6, v5, 0, -1);
    result = CImgTaskWmf::ReadImage(pImgTaskWmf);
  }
  return result;
}

先不要管,因为这里有一个重要的CImgTask::Read不知道是在Read什么,先看看CImgTask::Read

__int32 __thiscall CImgTask::Read(CImgTask *this, void *Dst, unsigned __int32 a3, unsigned __int32 *a4, unsigned __int32 a5)
{
  CDwnMemStream *v5; // ecx@1
  __int32 result; // eax@1
  unsigned __int32 v7; // [sp+4h] [bp-4h]@1

  v5 = (CDwnMemStream *)*((_DWORD *)this + 301);
  v7 = 0;
  result = CDwnMemStream::Read(v5, Dst, a3, a3, &v7);
  if ( a4 )
    *a4 = v7;
  return result;
}

又引入了一个CDwnMemStream::Read,分析就是这样,总是引入新的……不过看到CDwn,可以基本知道快“到头”了。

unsigned __int32 __thiscall CDwnMemStream::Read(CDwnMemStream *this, void *Dst, unsigned __int32 a3, unsigned __int32 a4, unsigned __int32 *a5)
{
  int v5; // ebx@1
  CDwnMemStream *v6; // esi@1
  unsigned __int32 v7; // edi@1
  int v8; // edx@1
  int v9; // eax@1
  int v10; // ecx@2
  int v11; // eax@3
  unsigned __int32 v12; // edi@4
  bool v13; // zf@7
  unsigned __int32 v15; // ecx@13
  int v16; // [sp+Ch] [bp-Ch]@1
  unsigned __int32 v17; // [sp+10h] [bp-8h]@6
  int v18; // [sp+14h] [bp-4h]@3

  v5 = a3;
  v6 = this;
  v7 = 0;
  v8 = *((_DWORD *)this + 3);
  v9 = *((_DWORD *)this + 5) << 14;
  v16 = v8 + v9;
  if ( *((_BYTE *)this + 24)
    || (v15 = *((_DWORD *)this + 8) - v8 - v9, a3 <= v15)
    || a4 <= v15
    || (v17 = 0,
        v7 = CImgTask::ReadIntoStream(*((CImgTask **)v6 + 9), v6, a3 - v15, a4 - v15, &v17),
        (v7 & 0x80000000) == 0) )
  {
    v10 = *((_DWORD *)v6 + 5) << 14;
    if ( a3 >= *((_DWORD *)v6 + 8) - v10 - *((_DWORD *)v6 + 3) )
      v5 = *((_DWORD *)v6 + 8) - v10 - *((_DWORD *)v6 + 3);
    v11 = v5;
    v18 = v5;
    if ( v5 )
    {
      while ( 1 )
      {
        v12 = 0x4000 - *((_DWORD *)v6 + 3);
        if ( v11 < v12 )
          v12 = v11;
        v17 = CMemPage::Read(
                *(CMemPage **)(*(_DWORD *)(*((_DWORD *)v6 + 7) + 4) + 4 * *((_DWORD *)v6 + 5)),
                Dst,
                *((_DWORD *)v6 + 3),
                v12);
        if ( (v17 & 0x80000000) != 0 )
          break;
        v13 = (((_WORD)v12 + (unsigned __int16)*((_DWORD *)v6 + 3)) & 0x3FFF) == 0;
        *((_DWORD *)v6 + 3) = ((_WORD)v12 + (unsigned __int16)*((_DWORD *)v6 + 3)) & 0x3FFF;
        if ( v13 )
          ++*((_DWORD *)v6 + 5);
        Dst = (char *)Dst + v12;
        v11 = v18 - v12;
        v18 = v11;
        if ( !v11 )
        {
          v7 = v17;
          goto LABEL_10;
        }
      }
      CDwnMemStream::SetReadPosition(v6, v16);
      v7 = v17;
    }
    else
    {
LABEL_10:
      if ( a5 )
        *a5 = v5;
    }
  }
  return v7;
}

CDwnMemStream::Read代码如上,可以分成几个小部分来看。

A.首先是这一连串的||

  if ( *((_BYTE *)this + 24)
    || (v15 = *((_DWORD *)this + 8) - v8 - v9, cb <= v15)
    || pcbRead <= v15
    || (v17 = 0,
        v7 = CImgTask::ReadIntoStream(*((CImgTask **)_this + 9), _this, cb - v15, pcbRead - v15, &v17),
        (v7 & 0x80000000) == 0) )

IDA的转换有些蛋疼,|| 是遇到真短路的。但是遇到这种复杂的IDA很可能出错,所以相信自己为好,直接从汇编语句看吧。

I.

mov     esi, ecx         ;ESI = this
push    edi
xor     edi, edi         ;EDI = 0
mov     eax, [esi+14h]   ;eax = *(this+0x14)
mov     edx, [esi+0Ch]   ;edx = *(this+0xc)
shl     eax, 0Eh         ;eax <<= 0xe.   eax is some value..
cmp     byte ptr [esi+18h], 0 ; if(this + 0x18 == 0)
lea     ecx, [edx+eax]   ;ecx = *(*(this+0xc) + eax);  so edx is a pointer value.
mov     [ebp+var_C], ecx ; some variant = ecx;
jz      loc_638F41B2     ; if zero then jump

II.

loc_638F41B2: (true)
mov     ecx, [esi+20h]   ; ecx = *(this + 0x20)
sub     ecx, edx         ; ecx -= *(this+0xc)    so this+0x20 is a pointer. pointer - pointer = delta 
sub     ecx, eax         ; ecx -= eax      another value - valuee
cmp     ebx, ecx         ; if(arg2 >= ecx)     [note: ebx is arg2 ]
jbe     loc_638F412D

III.

(false)
mov     eax, [ebp+arg3] ; eax = arg3;
cmp     eax, ecx        ; if (arg3 >= ecx)
jbe     loc_638F412D

IV.

(false)
sub     eax, ecx         ; arg3 -= ecx
mov     [ebp+var_8], edi ; some variant 2 = 0  [ note edi == 0 now]
lea     edx, [ebp+var_8] ; edx = &some variant2
push    edx              
push    eax              ; eax now is original arg3.
mov     eax, ebx         ; eax = arg2
sub     eax, ecx         ; eax -= ecx;
mov     ecx, [esi+24h]   ; ecx = this+0x24   [!!]
push    eax             
push    esi              ; struct CDwnMemStream *
call    ?ReadIntoStream@CImgTask@@QAEJAAVCDwnMemStream@@KKPAK@Z ; CImgTask::ReadIntoStream(CDwnMemStream &,ulong,ulong,ulong *)
mov     edi, eax          ; edi = return value
test    edi, edi          ; if (retValue != 0)
jns     loc_638F412D

关键点在CImgTask::ReadIntoStreamCImgTask::ReadIntoStream是一个thiscall,因此ecx为“this”(CImgTask*)。所以mov ecx, [esi+24h]这里this+0x24就是CImgTask*。也就是 CDwnMemStream* + 0x24 = CImgTask*

CImgTask::ReadIntoStream第二个参数为CDwnMemStream &类型,因此esi,也即this是CDwnMemStream*,这个没有疑问。

第三个参数是什么?这得再进CImgTask::ReadIntoStream,分析CImgTask::ReadIntoStream的代码可以得知,第三个参数是还需要获取的字节数,命名为cbReq。我是从这段代码段得知它的作用的:

while(not_finished)
{
    ...
    cbPos += cbGot;
    cbReq -= cbGot;
    if ( !cbReq )
      goto Finish;
    ...
}

第三个参数由push eax传入,鉴于上面的eax = arg2; eax -= ecx; 我们大概清楚了 arg2就是资源大小,也命名为cbReq。 ecx是已获得大小,命名 cbGot。

第四个参数呢,push eax ,这里的eax还是传入的原始arg3。再去CImgTask::ReadIntoStream看一眼,原来arg3是“每次最小需要获取的量”,命名为cbMin。我是从这段代码得知的:

...

DoTimeOut:
      if ( CImgTask::IsDwnBindEof(_pBindData) || !cbGot && cbTot >= a4 )
        goto Finish;
...

第五个参数,是一个DWORD的指针,按照编程人员的习惯,感觉我们已经初步给它命名DWORD* ccbGot了。不过还是去CImgTask::ReadIntoStream一探究竟。

Wow,就是想象的那样。那就这么给它命名好了。

  _ccbGot = ccbGot;
  if ( CImgTask::IsDwnBindEof(this) )
  {
    *ccbGot = 0;

    ……

   *_ccbGot = cbTot;
  return result_1;

附上完整的CImgTask::ReadIntoStream

signed int __thiscall CImgTask::ReadIntoStream(CImgTask *this, struct CDwnMemStream *pDwnMemStream, unsigned __int32 cbReq, unsigned __int32 cbMin, unsigned __int32 *ccbGot)
{
  CImgTask *_pBindData; // esi@1
  signed int maxSize; // edi@5
  __int32 result_1; // ebx@6
  unsigned __int32 cbGot; // edi@8
  unsigned __int32 cch; // [sp+Ch] [bp-2014h]@7
  unsigned __int32 cbTot; // [sp+10h] [bp-2010h]@3
  unsigned __int32 *_ccbGot; // [sp+14h] [bp-200Ch]@1
  char Src; // [sp+18h] [bp-2008h]@7

  _pBindData = this;
  _ccbGot = ccbGot;
  if ( CImgTask::IsDwnBindEof(this) )
  {
    *ccbGot = 0;
    CDwnMemStream::FinalizeContent(*((CDwnMemStream **)_pBindData + 301));
    return 0;
  }
  if ( pDwnMemStream != *((struct CDwnMemStream **)_pBindData + 301) )
    return 0x80004005;
  cbTot = 0;
  while ( !*((_DWORD *)_pBindData + 25) )
  {
    maxSize = cbReq;
    if ( cbReq > 0x2000 )
      maxSize = 0x2000;
    result_1 = CDwnMemStream::EnsureCanAppend(*((CDwnMemStream **)_pBindData + 301), maxSize);
    if ( result_1 < 0 )
      goto Finish;
    result_1 = CDwnBindData::Read(*((CDwnBindData **)_pBindData + 23), &Src, maxSize, &cch);
    if ( result_1 < 0 )
      goto Finish;
    cbGot = cch;
    if ( cch )
    {
      result_1 = CDwnMemStream::Append(*((CDwnMemStream **)_pBindData + 301), &Src, cch);
      if ( result_1 < 0 )
        goto Finish;
      cbGot = cch;
    }
    cbTot += cbGot;
    cbReq -= cbGot;
    if ( !cbReq )
      goto Finish;
    if ( cbGot )
    {
      if ( CDwnTask::IsTimeout(_pBindData) )
      {
        cbGot = cch;
        goto DoTimeOut;
      }
    }
    else
    {
DoTimeOut:
      if ( CImgTask::IsDwnBindEof(_pBindData) || !cbGot && cbTot >= cbMin )
        goto Finish;
      if ( Microsoft_IEEnableBits & 2 )
      {
        Template_pt(Microsoft_IEHandle, dword_64751CDC, cbGot);
        cbGot = cch;
      }
      CImgTaskExec::YieldTask(*((CImgTaskExec **)_pBindData + 4), _pBindData, cbGot == 0);
      if ( Microsoft_IEEnableBits & 2 )
        Template_pt(Microsoft_IEHandle, dword_64751CDC, cch);
    }
  }
  result_1 = 0x80004004;
Finish:
  if ( CImgTask::IsDwnBindEof(_pBindData) )
    CDwnMemStream::FinalizeContent(*((CDwnMemStream **)_pBindData + 301));
  *_ccbGot = cbTot;
  return result_1;
}

B. 回到上一层CDwnMemStream::Read,我们重新命名它,得到完整代码:

unsigned __int32 __thiscall CDwnMemStream::Read(CDwnMemStream *this, void *dataDestination, unsigned __int32 cbReq_1, unsigned __int32 cbMinReq, unsigned __int32 *ccbGot)
{
  int cbReq; // ebx@1
  CDwnMemStream *_this; // esi@1
  unsigned __int32 SubResult; // edi@1
  int someValue; // edx@1
  int v9; // eax@1
  int dwSomeValue; // ecx@2
  int cbRemain; // eax@3
  unsigned __int32 stepSize; // edi@4
  bool v13; // zf@7
  unsigned __int32 v15; // ecx@13
  int pos; // [sp+Ch] [bp-Ch]@1
  unsigned __int32 ReadResult; // [sp+10h] [bp-8h]@6
  int cbFullReqValue; // [sp+14h] [bp-4h]@3

  cbReq = cbReq_1;
  _this = this;
  SubResult = 0;
  someValue = *((_DWORD *)this + 3);
  v9 = *((_DWORD *)this + 5) << 14;
  pos = someValue + v9;
  if ( *((_BYTE *)this + 24)
    || (v15 = *((_DWORD *)this + 8) - someValue - v9, cbReq_1 <= v15)
    || cbMinReq <= v15
    || (ReadResult = 0,
        SubResult = CImgTask::ReadIntoStream(
                      *((CImgTask **)_this + 9),
                      _this,
                      cbReq_1 - v15,
                      cbMinReq - v15,
                      &ReadResult),
        (SubResult & 0x80000000) == 0) )
  {
    dwSomeValue = *((_DWORD *)_this + 5) << 14;
    if ( cbReq_1 >= *((_DWORD *)_this + 8) - dwSomeValue - *((_DWORD *)_this + 3) )
      cbReq = *((_DWORD *)_this + 8) - dwSomeValue - *((_DWORD *)_this + 3);
    cbRemain = cbReq;
    cbFullReqValue = cbReq;
    if ( cbReq )
    {
      while ( 1 )
      {
        stepSize = 0x4000 - *((_DWORD *)_this + 3);
        if ( cbRemain < stepSize )
          stepSize = cbRemain;
        ReadResult = CMemPage::Read(
                       *(CMemPage **)(*(_DWORD *)(*((_DWORD *)_this + 7) + 4) + 4 * *((_DWORD *)_this + 5)),
                       dataDestination,
                       *((_DWORD *)_this + 3),
                       stepSize);
        if ( (ReadResult & 0x80000000) != 0 )   // SUCCEED(...)   marco actually..
          break;
        v13 = (((_WORD)stepSize + (unsigned __int16)*((_DWORD *)_this + 3)) & 0x3FFF) == 0;
        *((_DWORD *)_this + 3) = ((_WORD)stepSize + (unsigned __int16)*((_DWORD *)_this + 3)) & 0x3FFF;
        if ( v13 )
          ++*((_DWORD *)_this + 5);
        dataDestination = (char *)dataDestination + stepSize;
        cbRemain = cbFullReqValue - stepSize;
        cbFullReqValue = cbRemain;
        if ( !cbRemain )
        {
          SubResult = ReadResult;
          goto Exit;
        }
      }
      CDwnMemStream::SetReadPosition(_this, pos);
      SubResult = ReadResult;
    }
    else
    {
Exit:
      if ( ccbGot )
        *ccbGot = cbReq;
    }
  }
  return SubResult;
}

C. 我们基本可以理解这个函数的作用:从流中按照分片大小依次读入。


所以,Decode的第一部分知道了。看看Read返回后的部分,两个MulDiv计算高宽,然后触发OnSize,再进入ReadImage。看看ReadImage。

int __thiscall CImgTaskWmf::Decode(CImgTaskWmf *this, int *a2)
{
  CImgTaskWmf *pThis; // esi@1
  int v3; // eax@3
  int v4; // ecx@3
  int v5; // ST04_4@3
  int v6; // ST00_4@3
  int result; // eax@3
  int bytesRead; // [sp+4h] [bp-20h]@1
  char v9; // [sp+8h] [bp-1Ch]@1
  __int16 v10; // [sp+Eh] [bp-16h]@3
  __int16 v11; // [sp+10h] [bp-14h]@3
  __int16 v12; // [sp+12h] [bp-12h]@3
  __int16 v13; // [sp+14h] [bp-10h]@3
  unsigned __int16 v14; // [sp+16h] [bp-Eh]@3

  bytesRead = 0;
  *a2 = 1;
  pThis = this;
  if ( CImgTask::Read(this, &v9, 0x16u, (unsigned __int32 *)&bytesRead, (unsigned __int32)this) < 0 || bytesRead != 0x16 )// read fail or read size not != 0x16
  {
    result = 0x80004005;
  }
  else
  {
    v3 = MulDiv(v12 - v10, 0x60, v14);
    v4 = v13;
    *((_DWORD *)pThis + 29) = abs(v3);
    v5 = abs(MulDiv(v4 - v11, 0x60, v14));
    v6 = *((_DWORD *)pThis + 29);
    *((_DWORD *)pThis + 30) = v5;
    CImgTask::OnSize(pThis, v6, v5, 0, -1);
    result = CImgTaskWmf::ReadImage(pThis);
  }
  return result;
}

ReadImage前面也是有很复杂的if判断,一样拆开。因为函数是一个thiscall,所以:

I.

mov     esi, ecx                        ;esi = `this`
lea     eax, [ebp+bytesRead]            ;eax is variant bytesRead
xor     ecx, ecx            ;ecx = 0;
mov     [ebp+var_438], esi      ;somevariant1 = `this`
push    ecx
push    eax
push    12h
lea     eax, [ebp+Src]          ;eax is somevariant2
mov     [ebp+var_428], ecx      ;somevariant3 = 0
mov     [ebp+var_424], ecx      ;somevariant4 = 0
mov     edi, ecx            ;edi = 0
mov     [ebp+hmf], ecx          ;hMf = 0.   handle to metafile
mov     [ebp+h], ecx            ;h ? = 0.
mov     [ebp+bytesRead], ecx        ;bytesRead = 0
mov     ecx, esi            ;ecx = `this`
push    eax             ;push somevariant2
call    ?Read@CImgTask@@IAEJPAXKPAKK@Z ; CImgTask::Read(void *,ulong,ulong *,ulong)
test    eax, eax            ;if(eax < 0) // if(!SUCCEED(eax))
js      loc_64151BA6

CImgTask::Read的四个参数含义分别是this(ecx)、buf、cbMin、cbGot。这个我们之前已经分析好了。

__int32 __thiscall CImgTask::Read(CImgTask *this, char *buf, unsigned int cbMin, unsigned __int32 *_cbGot)
{
  CDwnMemStream *v5; // ecx@1
  __int32 result; // eax@1
  unsigned __int32 cbGot; // [sp+4h] [bp-4h]@1

  v5 = (CDwnMemStream *)*((_DWORD *)this + 301);
  cbGot = 0;
  result = CDwnMemStream::Read(v5, buf, cbMin, cbMin, &cbGot);
  if ( _cbGot )
    *_cbGot = cbGot;
  return result;
}

重新整理一下上面的代码。

mov     esi, ecx                        ;esi = `this`
lea     eax, [ebp+bytesRead]            ;eax is variant bytesRead
xor     ecx, ecx            ;ecx = 0;
mov     [ebp+var_438], esi      ;somevariant1 = `this`
push    ecx             ;0
push    eax             ;buffer
push    12h             ;cbMin(cbReq)
lea     eax, [ebp+Src]          ;eax is somevariant2 --> cbGot
mov     [ebp+var_428], ecx      ;somevariant3 = 0
mov     [ebp+var_424], ecx      ;somevariant4 = 0
mov     edi, ecx            ;edi = 0
mov     [ebp+hmf], ecx          ;hMf = 0.   handle to metafile
mov     [ebp+h], ecx            ;h ? = 0.
mov     [ebp+bytesRead], ecx        ;bytesRead = 0
mov     ecx, esi            ;ecx = `this`
push    eax             ;push somevariant2 --> cbGot
call    ?Read@CImgTask@@IAEJPAXKPAKK@Z ; CImgTask::Read(void *,ulong,ulong *,ulong)
test    eax, eax            ;if(eax < 0) // if(!SUCCEED(eax))
js      loc_64151BA6

II.

(false)
cmp     [ebp+bytesRead], 12h
jnz     loc_64151BA6

判断读取到的数据是否为0x12,这是一个header的长度。如果不对,肯定有问题。

III.

(false)
mov     ecx, [ebp+var_412]
xor     eax, eax
shld    eax, ecx, 1
add     ecx, ecx
push    eax
push    ecx
lea     ecx, [ebp+bufSize]
call    _ULongLongToULong@12 ; ULongLongToULong(x,x,x)
test    eax, eax
js      loc_64151BA6

将bufsize从ULONGLONG 转为ULONG并判断是否成功以及溢出

IV.

(false)
mov     eax, [ebp+bufSize]
cmp     eax, 12h
jb      loc_64151BA6

判断bufSize是否<0x12。

V.

(false)
mov     ecx, _g_hProcessHeap
mov     edx, eax
call    ??$HeapAllocClear@$00@MemoryProtection@@YGPAXPAXI@Z ; MemoryProtection::HeapAllocClear<1>(void *,uint)
mov     ebx, eax
test    ebx, ebx
jz      loc_64151BA6

如果bufSize<0x12,则在Process Heap分配一块内存,并判断是否成功。

作者:blast
涞源:nul.pw/2017/04/18/218.html

VI.

(false)
push    12h             ; MaxCount
lea     eax, [ebp+buf]
push    eax             ; Src
push    [ebp+bufSize]   ; DstSize
push    ebx             ; Dst
call    ds:__imp__memcpy_s ;将读出的数据写入Buffer,不知是否可能出现问题,可以再看看。

MetaFile Header的处理到此结束。下面开始处理数据部分

mov     eax, [ebp+bufSize]
lea     ecx, [ebp+bytesRead]
and     [ebp+bytesRead], edi
add     esp, 0Ch
add     eax, 0FFFFFFEEh    ;实际上是-0x12,也就是去掉了刚刚读取的头,剩余的部分再Read一次。
mov     [ebp+var_42C], eax 
push    ecx
push    eax
lea     eax, [ebx+12h]
mov     ecx, esi
push    eax
call    ?Read@CImgTask@@IAEJPAXKPAKK@Z ; CImgTask::Read(void *,ulong,ulong *,ulong)
test    eax, eax
js      loc_64151B60

后面的就是处理Meta File自身的逻辑了。

这一片逻辑整理下来为:

 if ( CImgTask::Read(this, &buf, 0x12u, (unsigned __int32 *)&bytesRead, 0) < 0// if not Read Error then 
    || bytesRead != 0x12                        // judge Read byte is 0x12
    || ULongLongToULong(2 * v32, (unsigned __int64)v32 >> 31) < 0// ULONGLONG -> LONGLONG, judge if there's any interger overflow
    || bufSize < 0x12                           // judge if header part is illegal
    || (pMem = MemoryProtection::HeapAllocClear<1>(g_hProcessHeap, bufSize)) == 0 )// judge if Heap allocating is succeed
  {
    result_1 = 0x80004005;                      // any fail will make this func return 0x8004005
    goto LABEL_28;
  }
  _memcpy_s((void *)pMem, bufSize, &buf, 0x12u);
  bytesRead = 0;
  remaining = bufSize - 0x12;
  if ( CImgTask::Read(v1, (char *)(pMem + 18), bufSize - 0x12, (unsigned __int32 *)&bytesRead, (unsigned __int32)v19) < 0
    || bytesRead != remaining )
  {
    result_1 = 0x80004005;
    goto LABEL_19;
  }

接下来,绘制Meta File。伪代码如下,又是一长串

 hmf = SetMetaFileBitsEx(bufSize, (const BYTE *)pMem);
  if ( hmf )
  {
    MemoryProtection::HeapFree(v20, v21, v22);
    pMem = 0;
    bufSize = 0;
    v5 = (struct ColorPalette **)TSmartPointer<ColorPalette>::operator&(&bufSize);
    if ( ColorPaletteInternal::GetColorPalette(v6, v5) < 0 )
    {
      result_1 = 0x80004005;
      TSmartPointer<ColorPalette>::~TSmartPointer<ColorPalette>(&bufSize);
      goto LABEL_23;
    }
    v7 = (const void *)(bufSize + 1036);
    CopyColorsFromPaletteEntries((struct tagRGBQUAD *)0x100, (const struct tagPALETTEENTRY *)v20, (unsigned int)v21);
    v8 = v23;
    _memcpy_s((char *)v23 + 152, 0x400u, v7, 0x400u);
    TSmartPointer<ColorPalette>::~TSmartPointer<ColorPalette>(&bufSize);
    LOBYTE(remaining) = *((_BYTE *)v23 + 112);
    v19 = (void *)TSmartPointer<IWICProgressiveLevelControl>::operator&(&v27);
    v9 = TSmartPointer<CDCompLayer>::operator&(&v28);
    LOBYTE(v10) = 1;
    result_1 = CImgCacheEntry::Create(
                 v10,
                 8,
                 *((_DWORD *)v8 + 29),
                 *((_DWORD *)v8 + 30),
                 &v33,
                 256,
                 1,
                 255,
                 0,
                 remaining,
                 0,
                 0,
                 v9,
                 v19);
    if ( result_1 < 0 )
    {
LABEL_23:
      if ( hmf )
        DeleteMetaFile(hmf);
      goto LABEL_25;
    }
    v11 = v28;
    *((_DWORD *)v23 + 33) = 255;
    *((_DWORD *)v8 + 32) = 1;
    v12 = (int)v11 + 48;
    v19 = (void *)(*((_DWORD *)v8 + 30) * CImgBits::CbLine(v11));
    v18 = *((_BYTE *)v8 + 132);
    v13 = *(_DWORD *)v12;
    v14 = *(int (**)(void))(*(_DWORD *)v12 + 16);
    __guard_check_icall_fptr(*(_DWORD *)(v13 + 16));
    v15 = (void *)v14();
    if ( &v18 != &v18 )
      __fastfail(4u);
    memset(v15, v18, (size_t)v19);
    v2 = GetMemoryDC();
    if ( !v2 )
    {
      result_1 = 0x80004005;
      goto LABEL_23;
    }
    v19 = (void *)*((_DWORD *)v28 + 19);
    *((_DWORD *)v28 + 23) |= 2u;
    h = SelectObject(v2, v19);
    SaveDC(v2);
    SetMapMode(v2, 8);
    v16 = v23;
    v19 = 0;
    SetViewportExtEx(v2, *((_DWORD *)v23 + 29), *((_DWORD *)v23 + 30), 0);
    PlayMetaFile(v2, hmf);
    RestoreDC(v2, -1);
    TSmartPointer<IWICBitmapSource>::operator=<IWICFormatConverter>(&v27);
    *((_DWORD *)v16 + 31) = -1;
    LogSqmIncrement(0x7Au, 1u);
    result_1 = 0;
LABEL_19:
    if ( h )
      SelectObject(v2, h);
    if ( v2 )
      DeleteDC(v2);
    goto LABEL_23;
  }

我打算先丢掉一半的代码,原因是,这些是GDI32.DLL提供的API,如果有空,我之后再跟踪GDI32.DLL的实现。

这一串代码为:

hmf = SetMetaFileBitsEx(dwSize, pBuf); 
if (!hmf) ...

CopyColorsFromPaletteEntries(arg, globalArg, 256); 
memcpy(...); 

// create memory dc and rendering meta file into this bitmap.     
hdc = GetMemoryDC(); 

SaveDC(SelectObject(hdc, ...); 

SetMapMode(hdc, MM_ANISOTROPIC); 
SetViewportExtEx(hdc, width, height, NULL); 
PlayMetaFile(hdc, hmf); 
RestoreDC(hdc, -1); 

这里面没有“...”的我们就先不看了,关注几个:

I.

dwhcb = (void *)(*((_DWORD *)v8 + 30) * CImgBits::CbLine(v11));// height * cbLine actually..
dwTrans = *((_BYTE *)v8 + 132);
v13 = *(_DWORD *)v12;
getBits = *(int (**)(void))(*(_DWORD *)v12 + 16);
__guard_check_icall_fptr(*(_DWORD *)(v13 + 16));
bits = (void *)getBits();
if ( &dwTrans != &dwTrans )
  __fastfail(4u);
memset(bits, dwTrans, (size_t)dwhcb);
hdc = GetMemoryDC();
if ( !hdc )
{
  result_1 = 0x80004005;
  goto LABEL_23;
}

这一段,首先是dwTrans让我们很是疑惑,不过看到这个fastfail应该都释然了,if ( &dwTrans != &dwTrans ) __fastfail(4u);,不能利用的。

II.退出前

LABEL_25:
  if ( pMem )
    MemoryProtection::HeapFree(ghalfTone_ape, val256, v22);
LABEL_28:
  TSmartPointer<CImgBits>::~TSmartPointer<CImgBits>(&v28);
  TSmartPointer<CImgCacheEntry>::~TSmartPointer<CImgCacheEntry>(&v27);
  return result_1;
}

很安全的样子。

这样,MSHTML.DLL里面的部分我们就全看完了,确实处理很安全,在我这次逆向中看起来也没发现什么可以用的点。如果还有空的话,再看一看GDI32.DLL里面的实现好了。

03 - 自动化抽取dump信息

又是一周,老实说又忘记了这码事,趁着周末填一下。在了解了如何抽取DUMP文件中关于URI的方法之后,继续自动化操作就好。我们把dump归档到某个目录下(work_path),然后利用批处理,或者随便什么语言什么脚本,只要你开心就好,去调用cdb,并将解析出的结果保存到txt中。

废话不多说,我就是这么喜欢直奔主题,以下是批处理文件。如果你要使用这个批处理,机智的你一定知道怎么修改来匹配你机器的环境。如果不知道的话那还是别找漏洞了:)。

@echo off 
set work_path=E:\wowowowow\dump
E: 
cd %work_path% 
for /R %%s in (*.dmp) do ( 
    echo "*************************"
    echo NOW PROCESSING: %%s
    echo "*************************"    
    echo  .ecxr;k;.foreach^(place ^{s -^[1^]u 0 L^?80000000 ^"http^"^}^)^{du ^/c100 ^$^{place^}^}| "C:\Program Files (x86)\Windows Kits\8.1\Debuggers\x86\cdb.exe" -i srv*E:\symbols\*http://msdl.microsoft.com/download/symbols -z %%s >> %%s.out.txt
) 
echo "DONE."

经过一番等待之后,得到如下的输出:

outp1.png

毕竟不是文物分析,dmp文件老实说可以删掉了。使用可以预览文档内容的工具,例如FileLocator Pro(Linux 下的替代:searchmonkey),对“*.txt”来搜索“://” or "http://"或者其他你感兴趣的东西,分析得到dump文件中保存的那一段历史。

搜索“WAV”,得到这么一串URL:

00234824  "http://DOGE/main.php?DOGEDOGE&A=ListView"
051239e8  "http://DOGE/play.php?file=DOGEDOGEDOGEDOGEDOGEDOGE-015800405922.WAV"

outp1.png

根据URL中残留的信息,可以断定是在网页中预览了某个WAV文件,然后产生了崩溃,崩溃的位置也不错;

0:006> Unable to load image C:\WINDOWS\system32\wmp.dll, Win32 error 0n2
*** WARNING: Unable to verify timestamp for wmp.dll
*** ERROR: Module load completed but symbols could not be loaded for wmp.dll
eax=00000001 ebx=00000000 ecx=00000000 edx=003e953c esi=00000000 edi=00000001
eip=082a7e7f esp=03b9f950 ebp=03b9f960 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
wmp+0x37e7f:
082a7e7f ??              ???
  *** Stack trace for last set context - .thread/.cxr resets it
ChildEBP RetAddr  
WARNING: Stack unwind information not available. Following frames may be wrong.
03b9f94c 06b1ded0 wmp+0x37e7f

接下来只要保存文件,尝试复现即可。

(作者 blast,CH 03 完)

02 - Machine to Machine - 自动化WinDBG分析过程

挖坑,说不定哪天心血来潮不填了呢。

既然我们有了源源不断的DUMP,那么再找个方法自动看一看,这种事肯定不能是人工去做,人工的话,这得搭上我所有的时间,还不如让我老老实实对着IDA一个个函数去逆向呢。

Windbg其实是cdb的GUI版本。使用cdb,应该可以达到同样的效果。cdb位于Windows SDK Debugging Tools目录下。

使用cdb -y SymbolPath -i ImagePath -z DumpFileName可以打开dump文件,然后就和windbg的操作一模一样了。

cdb.png

那这还得手动操作啊,如何自动化?简单,使用管道。linux下大家已经玩的熟练了,类似ps aux|grep xx,在windows也是一样。 echo .ecxr;k;| cdb -z C:\xx.dmp 试一试?你会惊奇的发现,连.detach都不需要。分析完,程序就退了。留下一屏幕的分析结果。

有了这个管道信息,剩余的大家也都明白,要不就读( ReadConsoleOutput or GetStdHandle + GetConsoleScreenBufferInfo whatever)一下这里输出的东西,要不就使用输出重定向>>

echo .ecxr;k;| cdb -z C:\xx.dmp >> c:\a.txt
more c:\a.txt

这样一切都搞定了(上面只是例子,注意权限。c:\根目录不一定写的进去)。

这是要做什么?下一章再说吧。在下一章开始之前,我们再简单修改一下语句

.foreach(place {s -[1]u 0 L?80000000 "http"}){du /c100 ${place}}

这样,我们看起来是不是清爽多了?所有字符都在一行了。

0:012> .foreach(place {s -[1]u 0 L?80000000 "http"}){du /c100 ${place}}
0080dc9c  "https://fonts.googleapis.com/css?family=PT+Sans"
00815c24  "http://***.com/includes/js/virtualpaginate.js"
00815e82  "http://***.com/download.php?id=584CE4A51..."

接下来,将所有内容输出。到文件也好,到你的自动化处理程序也好,这都是我们下一篇文章要说的事情了。

(作者 blast,CH 02 完)

01 - Ghost in dump - 从浏览器崩溃数据中找到问题所在

挖坑,说不定哪天心血来潮不填了呢。

周末看了看书商,现在的新潮书目已经被大数据、机器学习洗了个遍。漏洞挖掘这个东西,在人手不足(满眼辛酸泪)的时候,还是得借助一下大众的力量。但其实我在写的这些并不是大数据,也不是人工智能。纯粹是大量数据,通过分散工作,倒更有点像bittorrent ,或者苏联的马大兄弟最终想达到的境界。

汇聚了大量的崩溃数据之后,怎么解?节点只是无责任地尝试各种可能,将数据汇聚给你这里的时候,数据会有各种杂点。这包括:

  1. 节点机器软件环境的不同
  2. 节点机器的系统位数不一(x86、x64)
  3. 节点机器的补丁化程度不一样、系统不一样(xp、7、8、10?)
  4. 节点机器甚至有可能出现一些随机的问题,例如某个数据就是突然不正确了
  5. 节点机器的硬件配置不同(打印机……)
  6. ……

    软件环境的不同导致的问题:

QQ图片20170326195240.png

为什么不统一节点呢?节点需要多样性,这个更符合马大兄弟的观点不是么。正儿八经说,这样的多样性更符合日常的用户分布。

有一批DUMP之后,首先要做的事就是筛选。丢掉所有的INT3(在Edge中多为fastfail)、丢掉所有空指针(因为我们本来就难以复现,这些空指针的实际情况我们之后再看)、丢掉所有栈中有其他DLL的(如杀毒软件、输入法等),最后将剩余的DUMP统一批量解析。解析后拿出一些可疑的详细分析:

suspecious.gif

  • 流程分析

打出栈后可以粗略分析:

0:010> kvn
  *** Stack trace for last set context - .thread/.cxr resets it
 # ChildEBP RetAddr  Args to Child              
WARNING: Frame IP not in any known module. Following frames may be wrong.
00 06208f48 06fcf46d 0799e3f8 00000000 0620aaf0 0x620aab0
01 0620aab0 06fc8d11 00000000 5f0623b8 0000097f edgehtml!CDoc::ExecHelper+0x6751 (FPO: [6,1749,4])
02 0620aad0 5f146ab5 082c0000 5f0623b8 0000097f edgehtml!CDoc::Exec+0x21 (FPO: [6,0,0])
03 0620add8 5f301a39 01723474 5f0623b8 0000097f ieframe!CDocObjectHost::ExecDown+0x215 (FPO: [6,183,4])
04 0620ae0c 5f301b29 0000097f 01723468 00000000 ieframe!DocObjectHost_ExecDown+0x61 (FPO: [Non-Fpo])
05 0620ae40 5f359947 ffffffff 01723698 0620b27c ieframe!DocObjectHost_UserClickedRecently+0x33 (FPO: [Non-Fpo])
06 0620ae50 5f2abbca 000608a8 00000000 0620ae78 ieframe!CInformationBar::ConfigureUI+0x7b (FPO: [Non-Fpo])
07 0620b27c 5f14db52 000608a8 00000010 00000000 ieframe!`BrowserTelemetry::Instance'::`2'::`dynamic atexit destructor for 'wrapper''+0x6ed8a
08 0620b2d4 5f1487b5 00000010 00000000 06a01a54 ieframe!CDocObjectHost::_HandlePageActionBlocked+0x227 (FPO: [2,13,4])
09 0620b2f8 5f148ca1 00000037 00000010 00000000 ieframe!CDocObjectHost::_HandleDocHostCmds+0x4a (FPO: [Non-Fpo])
0a 0620b324 071f50fd 01723468 06a01a54 00000037 ieframe!CDocObjectHost::Exec+0x4b1 (FPO: [Non-Fpo])
0b 0620b358 07065302 00000037 00000010 00000000 edgehtml!`TextInput::TextInputLogging::Instance'::`2'::`dynamic atexit destructor for 'wrapper''+0x15760d
0c 0620b394 06df8fe0 00000000 00000000 082c8500 edgehtml!CDoc::NotifyPageActionBlockedState+0x69 (FPO: [2,5,4])
0d 0620b3ac 07729dfd 00000010 00000000 00000000 edgehtml!CDoc::OnPageActionBlocked+0x3d (FPO: [Non-Fpo])
0e 0620b3d8 0713644b 00000010 00000000 00000000 edgehtml!NotifyHaveProtectedUserFromUnsafeContent+0xb0 (FPO: [Non-Fpo])
0f 0620d494 06d15c08 00000000 1a69e360 1b0914a0 edgehtml!`TextInput::TextInputLogging::Instance'::`2'::`dynamic atexit destructor for 'wrapper''+0x9895b
10 0620d518 06d13e98 0620d528 06e8a712 00000002 edgehtml!CObjectElement::CreateObject+0x15d (FPO: [0,27,4])
11 0620d520 06e8a712 00000002 8000ffff 1bc0d400 edgehtml!CHtmObject10ParseCtx::Execute+0x18 (FPO: [0,0,4])
12 0620d568 06e9dde2 ffffffff 1bc0d400 00000000 edgehtml!CHtmParseBase::Execute+0x1e2 (FPO: [0,13,4])
13 0620d584 06e9d9ec 00000002 1410bf00 00000000 edgehtml!CHtmPost::Broadcast+0xa2 (FPO: [Non-Fpo])
14 0620d6ac 06e9ac0f ffffffff 00000000 0709baa0 edgehtml!CHtmPost::Exec+0x35c (FPO: [Non-Fpo])
15 0620d6e4 070195f8 0620d7d8 1412f980 1410bf00 edgehtml!CHtmLoad::PerformSyncParse+0x8c (FPO: [0,7,4])
16 0620d710 06e9790b 0620d7d8 1412f980 17c0b9e0 edgehtml!CHtmLoad::Init+0x1a8 (FPO: [Non-Fpo])
17 0620d740 06e977cd 17c0b9e0 00000001 00000000 edgehtml!CDwnInfo::SetLoad+0x11b (FPO: [Non-Fpo])
18 0620d768 06e97728 00000001 0620d7d8 00000000 edgehtml!CDwnCtx::SetLoad+0x3d (FPO: [3,1,4])
19 0620d784 06e8ca12 00000001 0620d7d8 00000000 edgehtml!CHtmCtx::SetLoad+0x18 (FPO: [Non-Fpo])
1a 0620d7b4 06e81492 0620d7d8 0620d944 00000000 edgehtml!CMarkup::Load+0x1c2 (FPO: [1,3,4])
1b 0620d880 06e80f51 09585e10 082c8500 082c8500 edgehtml!CMarkup::Load+0x142 (FPO: [6,45,4])
1c 0620d90c 06e809d8 0620d958 00000001 00000001 edgehtml!CDoc::ParseHtmlStream+0x18f (FPO: [Non-Fpo])
1d 0620da98 06e80793 09585e10 0000026a 00000008 edgehtml!InjectHtmlStream+0x168 (FPO: [8,87,4])
1e 0620dad0 06ea69ed 1afb6f00 00000135 00000008 edgehtml!HandleHTMLInjection+0x6a (FPO: [Non-Fpo])
1f 0620dbbc 07017599 00000000 1afb6f00 00000135 edgehtml!CElement::InjectInternal+0x34d (FPO: [5,49,4])
20 0620dc20 070174c4 081f3ba8 1d01cdb0 0620dc5c edgehtml!CElement::Var_set_innerHTML+0xba (FPO: [2,15,4])
21 0620dc48 07de14d9 0e1e29c0 02000002 0620dcd0 edgehtml!CFastDOM::CHTMLElement::Trampoline_Set_innerHTML+0x34 (FPO: [Non-Fpo])
22 0620dcc0 07d03eb7 0e1e29c0 02000002 1cfd15c0 Chakra!Js::JavascriptExternalFunction::ExternalFunctionThunk+0x189 (FPO: [2,21,0])
23 0620dcec 07d4f4d1 081fe620 0620dd24 0620dd44 Chakra!Js::JavascriptOperators::RootToThisObject+0xc2 (FPO: [Non-Fpo])
24 0620dd00 07dc7569 0e1e29c0 00000000 081fe620 Chakra!ThreadContext::ExecuteImplicitCall<<lambda_ba4868153056010d087fde4938f3dfe7> >+0x41 (FPO: [Non-Fpo])
25 0620dd48 07ceed5b 1d01cdb0 081fe620 00000688 Chakra!Js::JavascriptOperators::CallSetter+0x49 (FPO: [Non-Fpo])
26 0620dd90 07d8a526 00000000 00000688 1d01cdb0 Chakra!Js::JavascriptOperators::SetProperty_Internal<0>+0x7fb (FPO: [Non-Fpo])
27 0620ddbc 07db8729 1d01cdb0 081fe620 0620dde8 Chakra!Js::JavascriptOperators::OP_SetProperty+0x56 (FPO: [Non-Fpo])
28 0620de08 07d8fb69 1d07b950 1cf30878 0000000e Chakra!Js::JavascriptOperators::PatchPutValueNoLocalFastPath<0,Js::InlineCache>+0xa9 (FPO: [7,9,4])
29 0620de48 07d900a7 1acbdf92 1cfd15c0 00000000 Chakra!Js::InterpreterStackFrame::DoSetProperty_NoFastPath<Js::OpLayoutT_ElementCP<Js::LayoutSizePolicy<0> > const >+0x89 (FPO: [Non-Fpo])
2a 0620de70 07d91634 1acbdf92 1cfd15c0 00000000 Chakra!Js::InterpreterStackFrame::DoSetProperty<Js::OpLayoutT_ElementCP<Js::LayoutSizePolicy<0> > const >+0x47 (FPO: [Non-Fpo])
2b 0620de88 07d93ac7 1acbdf92 ffffffff 0620df00 Chakra!Js::InterpreterStackFrame::OP_SetProperty<Js::OpLayoutT_ElementCP<Js::LayoutSizePolicy<0> > const >+0x1a (FPO: [Non-Fpo])
2c 0620debc 07d935aa 25e00e78 081fd090 0620df00 Chakra!Js::InterpreterStackFrame::ProcessUnprofiled+0x447 (FPO: [Non-Fpo])
2d 0620def8 07d970dd 1acbdef0 1acbdf95 00000001 Chakra!Js::InterpreterStackFrame::Process+0x11a (FPO: [0,9,0])
2e 0620e0e0 07d97d78 0620e110 00000000 10000001 Chakra!Js::InterpreterStackFrame::InterpreterHelper+0x2dd (FPO: [Non-Fpo])
2f 0620e118 07de16f1 179bd890 10000001 1aea0740 Chakra!Js::InterpreterStackFrame::InterpreterThunk+0x38 (FPO: [1,1,4])
30 0620e15c 07d90632 10000001 0620e2cc 1ac79b85 Chakra!Js::JavascriptFunction::CallFunction<1>+0x91 (FPO: [Non-Fpo])
31 0620e184 07d93904 1ac79ba7 0620e2cc ffffffff Chakra!Js::InterpreterStackFrame::OP_CallI<Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallI<Js::LayoutSizePolicy<0> > > >+0xa2 (FPO: [Non-Fpo])
32 0620e1bc 07d935aa 25e03178 081fd090 0620e200 Chakra!Js::InterpreterStackFrame::ProcessUnprofiled+0x284 (FPO: [Non-Fpo])
33 0620e1f8 07d970dd 1ac79b60 1ac79bb0 00000002 Chakra!Js::InterpreterStackFrame::Process+0x11a (FPO: [0,9,0])
34 0620e388 07d97d78 0620e3b8 00000000 02000002 Chakra!Js::InterpreterStackFrame::InterpreterHelper+0x2dd (FPO: [Non-Fpo])
35 0620e3c0 07de16f1 1d01ca50 02000002 0e130240 Chakra!Js::InterpreterStackFrame::InterpreterThunk+0x38 (FPO: [1,1,4])
36 0620e40c 07d8fc83 02000002 0620e5b0 179330e6 Chakra!Js::JavascriptFunction::CallFunction<1>+0x91 (FPO: [Non-Fpo])
37 0620e42c 07d91f95 179330e6 1d01ca50 00000000 Chakra!Js::InterpreterStackFrame::OP_CallCommon<Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallI<Js::LayoutSizePolicy<0> > > >+0x43 (FPO: [Non-Fpo])
38 0620e464 07d95557 179330e6 0620e5b0 ffffffff Chakra!Js::InterpreterStackFrame::OP_ProfiledCallI<Js::OpLayoutT_CallI<Js::LayoutSizePolicy<0> > >+0x75 (FPO: [Non-Fpo])
39 0620e49c 07d9353e 25e03418 081fd090 0620e4e0 Chakra!Js::InterpreterStackFrame::ProcessProfiled+0x307 (FPO: [Non-Fpo])
3a 0620e4d8 07d970dd 17933080 179330eb 00000003 Chakra!Js::InterpreterStackFrame::Process+0xae (FPO: [0,9,0])
3b 0620e678 07d97d78 0620e6a8 00000000 10000003 Chakra!Js::InterpreterStackFrame::InterpreterHelper+0x2dd (FPO: [Non-Fpo])
3c 0620e6b0 07de16f1 17e02450 10000003 18180960 Chakra!Js::InterpreterStackFrame::InterpreterThunk+0x38 (FPO: [1,1,4])
3d 0620e6fc 07d8fcc3 10000003 0620e970 0e4d327b Chakra!Js::JavascriptFunction::CallFunction<1>+0x91 (FPO: [Non-Fpo])
3e 0620e71c 07d91d15 0e4d327b 17e02450 1814c510 Chakra!Js::InterpreterStackFrame::OP_CallCommon<Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallI<Js::LayoutSizePolicy<0> > > >+0x83 (FPO: [Non-Fpo])
3f 0620e744 07d95ccc 0e4d327b 0620e970 ffffffff Chakra!Js::InterpreterStackFrame::OP_ProfiledReturnTypeCallI<Js::OpLayoutT_CallI<Js::LayoutSizePolicy<0> > >+0x35 (FPO: [Non-Fpo])
40 0620e77c 07d9353e 25e03738 00000000 0620e870 Chakra!Js::InterpreterStackFrame::ProcessProfiled+0xa7c (FPO: [Non-Fpo])
41 0620e7bc 07d928e6 25e03748 0e4d3205 0e4d320c Chakra!Js::InterpreterStackFrame::Process+0xae (FPO: [0,9,0])
42 0620e7f8 07d963db 0e4d320c ffffffff 0620e870 Chakra!Js::InterpreterStackFrame::OP_TryCatch+0x46 (FPO: [Non-Fpo])
43 0620e82c 07d9353e 25e03888 081fd090 0620e870 Chakra!Js::InterpreterStackFrame::ProcessProfiled+0x118b (FPO: [Non-Fpo])
44 0620e868 07d970dd 0e4d3200 0e4d3280 00000003 Chakra!Js::InterpreterStackFrame::Process+0xae (FPO: [0,9,0])
45 0620ea48 07d97d78 0620ea78 00000000 02000003 Chakra!Js::InterpreterStackFrame::InterpreterHelper+0x2dd (FPO: [Non-Fpo])
46 0620ea80 07de16f1 17e02d80 02000003 18180960 Chakra!Js::InterpreterStackFrame::InterpreterThunk+0x38 (FPO: [1,1,4])
47 0620eacc 07d8fc83 02000003 0620ec68 0620eba0 Chakra!Js::JavascriptFunction::CallFunction<1>+0x91 (FPO: [Non-Fpo])
48 0620eaec 07d91dd7 179461cf 17e02d80 00000000 Chakra!Js::InterpreterStackFrame::OP_CallCommon<Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallI<Js::LayoutSizePolicy<0> > > >+0x43 (FPO: [Non-Fpo])
49 0620eb24 07d953cc 179461cf 0620ec68 ffffffff Chakra!Js::InterpreterStackFrame::OP_ProfiledCallIWithICIndex<Js::OpLayoutT_CallIWithICIndex<Js::LayoutSizePolicy<0> > >+0x77 (FPO: [Non-Fpo])
4a 0620eb5c 07d9353e 25e03bd8 081fd090 0620eba0 Chakra!Js::InterpreterStackFrame::ProcessProfiled+0x17c (FPO: [Non-Fpo])
4b 0620eb98 07d970dd 17946190 179461d8 00000003 Chakra!Js::InterpreterStackFrame::Process+0xae (FPO: [0,9,0])
4c 0620ed38 07d97d78 0620ed68 00000000 10000003 Chakra!Js::InterpreterStackFrame::InterpreterHelper+0x2dd (FPO: [Non-Fpo])
4d 0620ed70 07de16f1 17e18e10 10000003 17e0e9e0 Chakra!Js::InterpreterStackFrame::InterpreterThunk+0x38 (FPO: [1,1,4])
4e 0620edbc 07d90632 10000003 0620ef2c 1bd7a99e Chakra!Js::JavascriptFunction::CallFunction<1>+0x91 (FPO: [Non-Fpo])
4f 0620ede4 07d93904 1bd7a9a8 0620ef2c ffffffff Chakra!Js::InterpreterStackFrame::OP_CallI<Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallI<Js::LayoutSizePolicy<0> > > >+0xa2 (FPO: [Non-Fpo])
50 0620ee1c 07d935aa 25e03e98 081fd090 0620ee60 Chakra!Js::InterpreterStackFrame::ProcessUnprofiled+0x284 (FPO: [Non-Fpo])
51 0620ee58 07d970dd 1bd7a900 1bd7a9b1 00000001 Chakra!Js::InterpreterStackFrame::Process+0x11a (FPO: [0,9,0])
52 0620eff8 07d97d78 0620f028 00000000 02000001 Chakra!Js::InterpreterStackFrame::InterpreterHelper+0x2dd (FPO: [Non-Fpo])
53 0620f030 07de16f1 179bd8f0 02000001 0e130050 Chakra!Js::InterpreterStackFrame::InterpreterThunk+0x38 (FPO: [1,1,4])
54 0620f078 07d8a402 02000001 0620f0a0 179bd8f0 Chakra!Js::JavascriptFunction::CallFunction<1>+0x91 (FPO: [Non-Fpo])
55 0620f0e4 07d8a2a2 1d0104b0 1d0104b0 081fe620 Chakra!Js::JavascriptFunction::CalloutHelper<0>+0x142 (FPO: [Non-Fpo])
56 0620f138 07db86fb 00000eca 011304e0 17e76240 Chakra!Js::JavascriptFunction::EntryApply+0xc2 (FPO: [2,3,4])
57 0620f190 07de16f1 17e7a1e0 10000002 17e75c20 Chakra!Js::JavascriptOperators::PatchPutValueNoLocalFastPath<0,Js::InlineCache>+0x7b (FPO: [7,9,4])
58 0620f1dc 07d90632 10000002 0620f36c 14ae15dd Chakra!Js::JavascriptFunction::CallFunction<1>+0x91 (FPO: [Non-Fpo])
59 0620f204 07d93904 14ae1648 0620f36c ffffffff Chakra!Js::InterpreterStackFrame::OP_CallI<Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallI<Js::LayoutSizePolicy<0> > > >+0xa2 (FPO: [Non-Fpo])
5a 0620f23c 07d935aa 25e022f8 081fd090 0620f280 Chakra!Js::InterpreterStackFrame::ProcessUnprofiled+0x284 (FPO: [Non-Fpo])
5b 0620f278 07d970dd 14ae1550 14ae1651 00000002 Chakra!Js::InterpreterStackFrame::Process+0x11a (FPO: [0,9,0])
5c 0620f430 07d97d78 0620f460 00000000 10000002 Chakra!Js::InterpreterStackFrame::InterpreterHelper+0x2dd (FPO: [Non-Fpo])
5d 0620f498 07de16f1 1d009f80 00000001 0e15c480 Chakra!Js::InterpreterStackFrame::InterpreterThunk+0x38 (FPO: [1,1,4])
5e 0620f4e0 07cee125 00000001 1530b280 25e025ec Chakra!Js::JavascriptFunction::CallFunction<1>+0x91 (FPO: [Non-Fpo])
5f 0620f55c 07ced757 081fe620 00000001 1530b280 Chakra!Js::JavascriptFunction::CallRootFunctionInternal+0xe5 (FPO: [Non-Fpo])
60 0620f570 07c7b162 081fe620 00000001 1530b280 Chakra!Js::JavascriptFunction::CallRootFunction+0x14 (FPO: [3,0,0])
61 0620f5f0 07cf2f25 0620f620 00000001 1530b280 Chakra!ScriptSite::CallRootFunction+0x74 (FPO: [Non-Fpo])
62 0620f634 07cef5f3 1d009f80 0620f660 00000000 Chakra!ScriptSite::Execute+0x105 (FPO: [Non-Fpo])
63 0620f688 0701b8cb 081f3ba8 1d009f80 00000001 Chakra!ScriptEngineBase::Execute+0xa3 (FPO: [6,11,0])
64 0620f6e0 0701a705 000001a7 00000000 1a4ccdc0 edgehtml!CScriptTimers::ExecuteTimer+0x18b (FPO: [Non-Fpo])
65 0620f754 06feb012 000001aa 082ec000 082ea000 edgehtml!CWindow::FireTimeOut+0x1a5 (FPO: [1,21,4])
66 0620f7a8 06fea3a5 00000000 00000000 082ea000 edgehtml!CPaintBeat::ProcessTimers+0x1f2 (FPO: [2,15,4])
67 0620f7d8 07047aec 00000000 07047a00 082ea000 edgehtml!CPaintBeat::OnBeat+0x1d5 (FPO: [1,5,4])
68 0620f7f8 07047a54 00002981 07047a00 0000023e edgehtml!CPaintBeat::OnPaintTimer+0x4c (FPO: [Non-Fpo])
69 0620f814 06ee89df 00002981 0000000f 00000001 edgehtml!CContainedTimerSink<CPaintBeat>::OnTimerMethodCall+0x54 (FPO: [Non-Fpo])
6a 0620f898 06ee7aec 02160785 06ee79c0 00000000 edgehtml!GlobalWndOnPaintPriorityMethodCall+0x2af (FPO: [0,27,4])
6b 0620f8ec 754784f3 00150818 0000000f 00000000 edgehtml!GlobalWndProc+0x12c (FPO: [Non-Fpo])
6c 0620f918 75456c40 06ee79c0 00150818 0000000f user32!_InternalCallWinProc+0x2b
6d 0620f9c0 75456820 06ee79c0 00000000 0000000f user32!UserCallWinProcCheckWow+0x1f0 (FPO: [SEH])
6e 0620fa20 7545d169 01e0fa30 00000000 0000000f user32!DispatchClientMessage+0xf0 (FPO: [Non-Fpo])

问题出在setInnerHTML上,但是这段代码是某个Timer触发的。因此我们可能还需要从其他地方搜索。

首先展开所有线程的栈,找到入口,如果入口的参数也不可读的话,就需要祭出s搜索了。

32:

 s -u 0 L?80000000 "http"

64:

!for_each_module s -[1]a ${@#Base} L?${@#Size}  "http" 

让我们自动化一点:

.foreach(place {s -[1]u 0 L?80000000 "http"}){du ${place}}

可以得到类似:

0:010> .foreach(place {s -[1]u 0 L?80000000 "http"}){du ${place}} 
0169bbd8  "https://qzonestyle.gtimg.cn/aoi/"
0169bc18  "sprite/icenter.32.png?max_age=19"
0169bc58  "830212&d=20170316153125"
016e9858  "http://qlogo3.store.qq.com/qzone"
016e9898  "/997427126/997427126/50?14360624"
016e98d8  "72"
016e9a08  "https://qlogo2.store.qq.com/qzon"
016e9a48  "e/840161873/840161873/30?1374381"
016e9a88  "815"
016e9b42  "http://s2.symcb.com"
016e9b6a  "http://s1.symcb.com/pca3-g5.crl"
016e9bb8  "http://qlogo3.store.qq.com/qzone"
016e9bf8  "/997427126/997427126/50?14360624"
016e9c38  "72"
016ea038  "https://qzonestyle.gtimg.cn/qzon"
016ea078  "e/biz/ac/comm/gdtlib.20160810.js"
016ea0b8  ""

这样的输出,我们可以大致知道是在访问QQ空间的时候出的问题。使用和DUMP产生者同样的环境,发现可以复现,但是,很不幸的是,使用纯净Edge环境并没有出现。因此这个DUMP对我们来说没有用,删掉。

大部分的DUMP其实都是这个情况,因此,还需要慢慢尝试。

(作者: blast,CH 1. 完)

是否可行?Let's "Fuzz"

老实说最近5个月我都在摸鱼,挖漏洞的活也全都扔给Fuzzer去做了,我自己倒是在干一些与安全无关的东西,所以一直没更新博客,也没太关注业内的新东西。

过了北京尚冷的3月,我也要开始回来继续研究研究安全相关的东西了。Fuzzer的收获嘛,除了两个偶现的,看起来有问题的之外,没有什么新发现。这两个偶现的我也只能根据自动录像来判断到底发生了什么,我自己也不是很明白到底怎么个复现法,毕竟一长串操作下来,十次只能复现两次。清明回来之后,如果还是这样估计我就直接录像加谜之PoC扔给微软了。

但是,我倒不止是有我那几台小破机器天天呼呼地跑着。现在手头倒是有大量的,数以万计的来自真实世界的DUMP等着我去分析。但是初步分析了5000个左右的崩溃记录来看,效果并不乐观。DUMP生成的机器上环境各异,崩的东西也是乱七八糟,5000个居然是全部无法复现的。伤脑筋,看来下一步,我要做一个自动处理它们的工具,希望能抓到一些好玩的东西。

hello world, let's fuzz