您好,登錄后才能下訂單哦!
本篇文章為大家展示了如何進(jìn)行CVE-2018-4990 Adobe Reader代碼執(zhí)行漏洞利用分析,內(nèi)容簡(jiǎn)明扼要并且容易理解,絕對(duì)能使你眼前一亮,通過這篇文章的詳細(xì)介紹希望你能有所收獲。
2018年5月15日,ESET發(fā)布文章“A tale of two zero-days”,該文章披露了今年3月ESET在惡意軟件掃描引擎(VirusTotal)上捕獲了一個(gè)用于攻擊測(cè)試的 PDF文檔。該P(yáng)DF文檔樣本包含兩枚0-day漏洞(CVE-2018-4990,CVE-2018-8120)以實(shí)現(xiàn)針對(duì)Adobe Acrobat/Reader PDF閱讀器的任意代碼執(zhí)行。其中CVE-2018-4990為Adobe PDF閱讀器的代碼執(zhí)行漏洞,而CVE-2018-8120則是Windows操作系統(tǒng)Win32k的內(nèi)核提權(quán)漏洞,在獲取代碼執(zhí)行權(quán)限后通過內(nèi)核提權(quán)漏洞繞過Adobe PDF閱讀器的沙盒保護(hù),實(shí)現(xiàn)任意代碼執(zhí)行。
360威脅情報(bào)中心分析確認(rèn)披露的漏洞可被利用,我們?cè)噲D通過公開的POC樣本中針對(duì)Adobe Acrobat/Reader代碼執(zhí)行的漏洞(CVE-2018-4990)利用過程進(jìn)行詳細(xì)分析,并記錄整個(gè)分析過程。如有分析不當(dāng)之處敬請(qǐng)諒解。
操作系統(tǒng):Windows 7 SP1
AdobeReader DC:1700920044
樣本MD5:bd23ad33accef14684d42c32769092a0
使用PDFStream打開漏洞樣本,在尾部可以發(fā)現(xiàn)使用了JavaScript來觸發(fā)利用漏洞:
通過分析可知,JavaScript中的前部分為PDF閱讀器漏洞觸發(fā)后加載運(yùn)行的載荷,主要用于提權(quán)并執(zhí)行惡意代碼。而之后的JavaScript代碼中則通過兩個(gè)Array實(shí)例sprayarr及a1來實(shí)現(xiàn)內(nèi)存Spray布局,這里需要注意的是a1對(duì)Array中奇數(shù)下標(biāo)的element進(jìn)行了釋放,這是UAF類漏洞利用中常見的一種內(nèi)存布局手法:
內(nèi)存部署成功之后,接著在myfun1,myfun2中調(diào)用了兩次觸發(fā)double free的腳本,該腳本代碼觸發(fā)了double free,從而導(dǎo)致后來的代碼執(zhí)行,觸發(fā)double free的腳本:
varf1 = this.getField("Button1");
最后對(duì)array實(shí)例sprayarr2進(jìn)行賦值,每個(gè)element為一個(gè)長度為0x20000-0x24的ArrayBuffer,接著遍歷sprayarr可以發(fā)現(xiàn)其對(duì)應(yīng)的某一個(gè)sprayarr的element長度被修改為了0x20000-0x24(默認(rèn)的長度為0x10000-0x24),此時(shí)通過超長的sprayarr[i1]即可修改相鄰的sprayarr[i1+1]對(duì)象的len長度屬性,從腳本代碼中可以看到長度被修改為了0x66666666,最終通過該超長的sprayarr[i1+1]即可實(shí)現(xiàn)全內(nèi)存的讀寫:
為此攻擊者編寫了專門的利用超長sprayarr對(duì)象實(shí)現(xiàn)全內(nèi)存讀寫的函數(shù):
獲取全內(nèi)存讀寫能力后,POC中通過偽造bookmarkRoot的對(duì)象實(shí)現(xiàn)代碼執(zhí)行:
POC運(yùn)行之后會(huì)導(dǎo)致崩潰:
崩潰的原因?yàn)閛bjecscript地址為硬編碼,其中0x23A59BA4-0x23800000的地址并不適配測(cè)試的Adobe Reader版本,從而導(dǎo)致崩潰:
通過對(duì)POC中的Payload功能解析,我們確定了POC中的幾個(gè)需要分析的要點(diǎn),這也是搞清楚整個(gè)漏洞利用的關(guān)鍵:
l sprayarr,a1在進(jìn)行內(nèi)存spray時(shí)的內(nèi)存結(jié)構(gòu)
l 觸發(fā)double free的代碼具體分析(var f1 = this.getField("Button1");)
l sprayarr2初始化時(shí)的內(nèi)存狀態(tài),其初始每個(gè)element長度正好是sprayarr中超長element的長度,這不禁讓我們懷疑sprayarr2和某個(gè)sprayarr重合了(或許是第二點(diǎn)中的代碼將sprayarr中的某個(gè)element釋放了?然后被sprayarr2重用?)
帶著Payload功能解析中得出了漏洞利用關(guān)鍵點(diǎn)我們開始逐一進(jìn)行調(diào)試分析。
樣本中具體的漏洞觸發(fā)/利用部分都是JavaScript腳本,因此調(diào)試的時(shí)候我們可以依賴對(duì)應(yīng)的三角函數(shù)實(shí)現(xiàn)具體的中斷。為了獲取對(duì)應(yīng)的內(nèi)存結(jié)構(gòu),我們可以直接修改對(duì)應(yīng)POC,比如在POC中創(chuàng)建一個(gè)Array的實(shí)例myContent,將該Array中第0個(gè)element賦值為0x1a2c3d4f,以便于內(nèi)存搜索,之后分別將我們感興趣的變量賦值到該Array中即可很方便的定位內(nèi)存 進(jìn)行分析:
通過上述的三角函數(shù)斷下后,此時(shí)通過搜索0x1a2c3d4f即可找到對(duì)應(yīng)的myContent結(jié)構(gòu),如下所示地址0x062035f8開始的數(shù)據(jù)則為對(duì)應(yīng)的tag(標(biāo)記為0x1a2c3d4f),之后的四字節(jié)值0xffffff81標(biāo)記該element的type類型,再往后依次為我們賦值的element,由于都是Array,所以type均為0xffffff87:
而為了獲取sprayarr和a1的內(nèi)存狀態(tài),我們可以在下圖位置下三角函數(shù)斷點(diǎn)來方便調(diào)試:
有了分析腳本中內(nèi)存結(jié)構(gòu)的方法,我們通過上述的辦法定位到sprayarr的內(nèi)存結(jié)構(gòu),可以看到element個(gè)數(shù)為0x1000,偏移c的位置包含指向具體內(nèi)存element的指針0x07e94718:
進(jìn)一步可以看到對(duì)應(yīng)的每一個(gè)element對(duì)象的地址,由于sprayarr的element為ArrayBuffer,所以其對(duì)應(yīng)的type為0xffffff87,從下標(biāo)1開始全部都是type為0xffffff87的ArrayBuffer:
查看下標(biāo)為1的element對(duì)象的內(nèi)存結(jié)構(gòu)如下所示,大的紅框分別對(duì)應(yīng)的sprayarr[1]和sprayarr[2]中的Arraybuffer對(duì)象,其內(nèi)存為連續(xù)布局。每個(gè)Arraybuffer對(duì)象偏移+c的位置為具體的內(nèi)存空間,下圖中每個(gè)Arraybuffer對(duì)象的實(shí)際內(nèi)存空間也是連續(xù)分布,而內(nèi)存中的兩個(gè)值相減則正好為Arraybuffer初始化時(shí)的長度(0x88dc760 - 0x88cc760=10000):
進(jìn)入到具體的Arraybuffer內(nèi)存空間查看(0x88cc760),實(shí)際內(nèi)存長度為ffe8(和分配的0x10000-0x24一致):
而sprayarr連續(xù)布局的內(nèi)存空間一直到延伸到地址0x18ce0038:
al
繼續(xù)使用分析相關(guān)內(nèi)存結(jié)構(gòu)的方法查看此時(shí)的al array,同理偏移+c的地方指向了具體的element:
由于a1中的奇數(shù)下標(biāo)的element在此時(shí)已經(jīng)被釋放,因此右側(cè)element的內(nèi)存都為0,此處選擇al[2]進(jìn)行查看,可以看到0x074926d8處保存了對(duì)應(yīng)的長度,之后0x74926E0處指向腳本代碼中對(duì)應(yīng)的Uint32Array,起偏移+0xC的位置包含了指向具體內(nèi)存的指針,而0x063008a0處即為對(duì)應(yīng)的Uint32Array(252),其長度為252*4=1008=0x3f0,而a1中有所Uint32Array最后都指向連續(xù)布局的內(nèi)存空間:
對(duì)應(yīng)的JavaScript腳本,其中a1i1,a1i1的值在此時(shí)分別為0x0d0e0048和0x0d0f0048:
Uint32Array(252)對(duì)應(yīng)的內(nèi)存布局(0x063008a0):
釋放的al[3]對(duì)象內(nèi)存布局:
Uint32Array最終的內(nèi)存布局(從0x06300890開始到0x88be180):
sprayarr2
對(duì)sprayarr2的內(nèi)存布局分析同樣通過三角函數(shù)在sprayarr長度修改后斷下:
sprayarr2的內(nèi)存結(jié)構(gòu)不過多展示,和sprayarr大致相同,此時(shí)sprayarr[i1]的長度已經(jīng)被修改為0x1ffe8:
其后相鄰element的len也被修改為0x66666666:
整個(gè)sprayarr的起始地址對(duì)應(yīng)的內(nèi)存數(shù)據(jù):
檢查sprayarr2內(nèi)存,發(fā)現(xiàn)sprayarr中被修改的element和sprayarr2中的某個(gè)element指向同一片內(nèi)存:
而sprayarr2中除了和sprayarr中一致的0x0d0e0058外可以發(fā)現(xiàn),正常情況下,sprayarr2的element內(nèi)存地址是從0x18d4a0d8開始的一片連續(xù)內(nèi)存,而此處0x0d0e0058更像是漏洞利用中占坑的結(jié)果,這也肯定了我們之前的猜測(cè),sprayarr中的內(nèi)存被釋放然后被sprayarr2占據(jù),由于正好占據(jù)的長度為spayarr element大小的兩倍,因此可以猜測(cè)釋放了兩次,每次釋放一個(gè)sprayarr element:
這里值得注意的是重用的內(nèi)存地址0x0d0e0058似乎和POC中a1i1和a1i1的值非常接近,難道是通過這個(gè)地方控制的釋放?
釋放函數(shù)定位
為了驗(yàn)證到底是否是var f1 = this.getField("Button1");相關(guān)代碼導(dǎo)致了對(duì)應(yīng)的a1i1和a1i1中的精確地址釋放,通過以下windbg斷點(diǎn)進(jìn)行驗(yàn)證:
buMSVCR120!free ".if poi(esp+4)=0x0d0e0048{}.else{gc}"
由于該函數(shù)在程序運(yùn)行中會(huì)被大量調(diào)用,直接下斷點(diǎn)會(huì)導(dǎo)致程序卡死,而且此處我們是為了驗(yàn)證var f1 = this.getField("Button1");相關(guān)代碼是否導(dǎo)致了sprayarr中的element被釋放,因此可以在該代碼前后加log函數(shù),log被斷下后再下對(duì)應(yīng)的free斷點(diǎn)并運(yùn)行:
再次運(yùn)行,調(diào)試器斷下,可以看到此時(shí)正在釋放地址0x0d0e0048中的內(nèi)存,可以看到是JP2KLib!JP2kCopyRect+0xbae6調(diào)用了free釋放函數(shù):
查看JP2KLib!JP2kCopyRect+0xbae6即可定位到漏洞觸發(fā)點(diǎn)(釋放函數(shù)):
動(dòng)態(tài)調(diào)試分析可以發(fā)現(xiàn)漏洞函數(shù)運(yùn)行到該loop前,其循環(huán)遍歷的地址為eax,而eax的值為0x08AA2820(通過poi(poi(esi+0x48)+0x0c)獲?。?/p>
而0x08aa2820正好為腳本中的a1中某個(gè)釋放的hole,即其大小為0x3f0:
而循環(huán)的校驗(yàn)值為0xff,0xfe*4 = 0x3f8,即可以訪問到之后的a1i1和a1i1(0x0d0e0048,0x0d0f0048):
之后繼續(xù)訪問并讀取地址0x0d0e0048中的數(shù)據(jù),并通過eax傳入函數(shù)sub_10066FEA。
sub_10066FEA最終會(huì)調(diào)用MSVCR120!free釋放該內(nèi)存:
精準(zhǔn)的內(nèi)存釋放過程
動(dòng)態(tài)調(diào)試得知第一次調(diào)用var f1 = this.getField("Button1");相關(guān)函數(shù)將導(dǎo)致0x0d0e0048對(duì)應(yīng)的0x10000長度的內(nèi)存被釋放:
繼續(xù)循環(huán)執(zhí)行后,漏洞函數(shù)讀取之后的地址0x0d0f0048中的數(shù)據(jù),并釋放對(duì)應(yīng)大小為0x10000的內(nèi)存,此時(shí)兩次釋放了總共0x20000長度的內(nèi)存(兩次釋放由myfun1函數(shù)中的getField相關(guān)代碼完成,myfun2中的getField刪除不影響利用):
通過對(duì)比hole被占據(jù)成buffer后的內(nèi)存結(jié)構(gòu)和正常a1的element就可以發(fā)現(xiàn),該結(jié)構(gòu)中尋址的起始地址要比之前的element低16個(gè)字節(jié),因此獲取a1i1和a1i1 element時(shí)的尋址編號(hào)分別是0xfd和0xfe:
最后通過sprayarra2賦值搶占該0x20000的內(nèi)存,一旦分配成功,即變相的將sprayarr中0x0d0f0048位置的element的長度從0xffe8修改為0x11fe8:
漏洞觸發(fā)原理分析
分析到這還剩最后兩個(gè)疑問。
1. poi(poi(esi+0x48)+0x0c)這個(gè)被遍歷的地址是如何被分配的?
2. poi(poi(esi+0x48)+0x04)處的0xff來自何處?
第一個(gè)問題
通過調(diào)試回溯,我們找到了具體的jp2h解析函數(shù),如下所示:
其對(duì)應(yīng)的參數(shù)如下:
參數(shù)1:getField("Button1")獲取的圖片的實(shí)際大小
參數(shù)2:poi(poi(esi+0x48)+0x0c)
參數(shù)3:圖片的對(duì)象,可以看到包含的具體圖片內(nèi)容
函數(shù)調(diào)用前,poi(poi(esi+0x48)+0x0c)這個(gè)地址初始化為零:
繼續(xù)跟蹤調(diào)試可以看到,給poi(poi(esi+0x48)+0x0c)分配的內(nèi)存地址大小為0x3f4,即一個(gè)a1中的hole:
而圖片的實(shí)際大小則是0x3f4,正好用于填補(bǔ)a1的hole:
poi(poi(esi+0x48)+0x0c)為pg2h解析時(shí)分配,其實(shí)際大小和圖片大小一致(0x3f4),正好填補(bǔ)a1中的hole,而在loop邏輯處理poi(poi(esi+0x48)+0x0c)時(shí),由于長度為0xfe(0xfe*4 = 0x3f8),所以越界8字節(jié)剛好讀到hole中殘留的攻擊者部署地址!
至此,可以清楚的知道漏洞觸發(fā)的原因:越界讀取,而我們也可以將漏洞觸發(fā)的代碼注釋修改成越界讀了:
0xff控制字
其中控制loop循環(huán)次數(shù)的0xff,實(shí)際在poi(poi(esi+0x48)+0x4)的位置。
通過回溯分析,發(fā)現(xiàn)該值同樣來自pg2h函數(shù)中的解析。
調(diào)試知道,其賦值來自于pg2h第三個(gè)參數(shù),圖片對(duì)象+10的位置。
067f4700這個(gè)變量主要用于標(biāo)記解析圖片時(shí)的指針,當(dāng)解析圖片時(shí),該指針從圖片的開始一直遞加,直到圖片尾部,如下圖所示為掃描到圖片中間時(shí)的值。
掃描完之后發(fā)現(xiàn)該指針指向后面的一片fffffff的內(nèi)容,如下所示:
此時(shí)通過該指針給poi(poi(esi+0x48)+0x4)賦值時(shí)即為對(duì)應(yīng)的0xff,從而導(dǎo)致之后的越界讀(這個(gè)地方感覺應(yīng)該是pclr后面應(yīng)該還有字段,突然截?cái)嗔藢?dǎo)致了一個(gè)錯(cuò)誤的殘留指針)。
而打過補(bǔ)丁之后,poi(poi(esi+0x48)+0x0c)處的buffer被設(shè)置為零,不再指向之前a1中的某一個(gè)hole地址,從而無法通過釋放函數(shù)前的校驗(yàn):
腳本函數(shù)myfun1中 array2的賦值會(huì)導(dǎo)致部分a1里hole偏移249處,地址為0x0d0e0048的4字節(jié)數(shù)據(jù)被置空(因?yàn)槠銾int32Array的大小為250,所以正好可以將偏移249的地址為0x0d0e0048處的4字節(jié)數(shù)據(jù)置空)。
如下所示,部分被填掉的a1 hole,可以看到對(duì)應(yīng)的長度由0x3f0變成了0x3e8:
而直接刪除后會(huì)影響漏洞觸發(fā),如下所示可以看到崩潰的原因在于漏洞函數(shù)釋放的地址不合法導(dǎo)致,其遍歷的buffer并不是我們分配的任何一個(gè)a1的element對(duì)象,因此猜測(cè)針對(duì)array2的循環(huán)遍歷主要是用于將部分符合大小的臟 hole給填補(bǔ)上,這樣var f1 = this.getField("Button1");相關(guān)代碼調(diào)用的時(shí)候就能確保該buffer分配到我們指定的a1 hole中。通過調(diào)整array2的大小后發(fā)現(xiàn),將大小值從0x200一直減少到0x70都能保證漏洞的穩(wěn)定觸發(fā),但是低于0x70則會(huì)降低觸發(fā)運(yùn)行漏洞函數(shù)的幾率,此處可以進(jìn)一步研究:
通過以上的分析過程可知漏洞函數(shù)JP2KLib!JP2kCopyRect+0xbae6中對(duì)訪問的buffer檢驗(yàn)有誤,導(dǎo)致可以越界讀取之后8字節(jié)的內(nèi)容(0xff的獲取有可能是pclr突然中斷導(dǎo)致),所以該8字節(jié)的內(nèi)容為攻擊者可控,并在之后用于精確釋放內(nèi)存,最終通過精確釋放,并重用該釋放的地址,獲取一個(gè)超長element,以實(shí)現(xiàn)全局內(nèi)存讀寫,最終導(dǎo)致任意代碼執(zhí)行。
整個(gè)漏洞利用過程總結(jié)如下(編號(hào)和內(nèi)存布局圖中的編號(hào)對(duì)應(yīng)):
1、 通過heap spray a1布置大量比buffer稍大的Uint32Array,將Uint32Array中249,250位置的element設(shè)置為需要釋放的地址,之后將a1中奇數(shù)elemnt釋放(以便于之后JP2KLib中解析圖片時(shí)被分配到)。
2、 heap spray sprayarr布置之后需要釋放的內(nèi)存對(duì)象(即sprayarr)
3、 通過加載指定大小的jp2k圖片,導(dǎo)致解析jp2k圖片時(shí)分配的內(nèi)存為之前某一個(gè)a1中的hole,之后運(yùn)行到JP2KLib!JP2kCopyRect+0xbae6,漏洞觸發(fā)越界讀取249,250偏移處(即sprayarr中兩個(gè)相連的elment)的內(nèi)存并釋放(合計(jì)0x20000),轉(zhuǎn)化為類UAF的利用
4、 同上
5、 通過sprayarr2的賦值搶占釋放的0x20000內(nèi)存,一旦搶占成功,sprayarr中之前被釋放的elment的長度就會(huì)被修改為0x20000
6、 最終通過精確釋放,并重用該釋放地址,獲取一個(gè)超長element,以實(shí)現(xiàn)全局內(nèi)存讀寫,再通過全局內(nèi)存讀寫,偽造bookmarkRoot的對(duì)象實(shí)現(xiàn)任意代碼執(zhí)行!
上述內(nèi)容就是如何進(jìn)行CVE-2018-4990 Adobe Reader代碼執(zhí)行漏洞利用分析,你們學(xué)到知識(shí)或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識(shí)儲(chǔ)備,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。