IE安全系列:指令碼先鋒(II)

wyzsk發表於2020-08-19
作者: blast · 2015/05/04 9:59

接著上一篇的來,本文多為Javascript的指令碼閱讀和解釋,閱讀都是自行完成,所以不免可能會出現一些錯誤或者聯想過度的情況,如果你發現了哪兒有問題請重重地拍出來。

IV.1 HTML與網馬攻擊4 — Virus In the Wild


本篇中我們將從真實的Exploit Kit的利用程式碼入手介紹分析方式,文中例子採用Angler Exploit Kit 和 Magnitude Exploit Kit(下簡稱為AEK和MEK)在2015年4月初最新的程式碼。

AEK和MEK是網際網路上最著名的漏洞利用工具包,在Phoenix Exploit Kit作者鋃鐺入獄之後,這兩個Exploit Kit的“市場份額”一下子竄到了前面,因為作者更新快,採用加密方式繁多,導致防毒軟體更新特徵也較為困難,這讓它們有充足的機會去攻擊有漏洞的電腦。本文將介紹這兩個Exploit Kit的加密方式和解密方法。

首先,讓我們從AEK開始,AEK和MEK都需要有一個Landing Page,用於將使用者定向到惡意頁面上,開啟Landing Page,我們會發現AEK為了做混淆,給Landing Page加了很多無用的垃圾資料,並將加密後的資料混淆插入在這些程式碼中:

enter image description here

圖:code部分是加密後的程式碼

將頁面向下拖動,快到最後的地方就是它的解密指令碼了:

enter image description here

圖:AEK的指令碼

是的,這就是一個高度混淆後的指令碼,因為JavaScript程式碼(或者說類C語言語法)的寬鬆性,作者在這裡面使用了大量的空白、回車、縮排符,同時還替換了變數名,使用大量的數學函式來做混淆。

對於人來說,要閱讀這樣的程式碼簡直是一個非常噁心的工作,所以在此推薦使用一些程式碼規範化工具,例如Malzilla提供的js標準化,或者這裡還有一個小技巧:使用Notepad++或者類似工具的括號匹配功能:

enter image description here

將游標移動到function的大括號處,此類軟體會自動標示出函式範圍,可見上圖中該函式範圍是LN268-LN385。這樣,我們就能清晰的知道這個程式碼的結構了:

enter image description here

而之前我們也說過,Function除外的Global程式碼,會從上往下執行,因此,攻擊者如果想要實現讀取-解密-執行的步驟,執行必然是最後一步,因此我們只需要在它執行之前將要執行的資料找到即可。

enter image description here

翻到最後,可以發現LN568-574的Script段其實和上面LN558-565幾乎一樣。這意味著這段程式碼很可能包含著解密和執行兩步的內容。

從最後一句入手,

LN564:

rqNNhndhLxLVVb[nmfAbWwoA]('TQgaaGLDVYlaQT',QXuSacI)

逐個檢視變數的作用,

LN267表明:

rqNNhndhLxLVVb = window;

這是因為Javascript中允許將任何物件賦值給某個變數,因此rqNNhndhLxLVVb此時實際上可以看作是window的“別名”或者“同義詞”。

看看第二個變數nmfAbWwoA的來源:

LN561可以發現是該變數的第一次使用的地方:

nmfAbWwoA = "ezVI"+"Migbc".substr(6,8)  //ezVI

nmfAbWwoA = nmfAbWwoA+[].join(dLpy) + "xlyG"

變數dLpy經過閱讀可知在:

LN437處賦值:

dLpy=  ('EoVzQHTfRyGU').substr(12,12)  //””

因此nmfAbWwoA的值實際上只是“ezVIxlyG”。

第三個變數: LN558可以發現是該變數第一次賦值的地方:

#!javascript
var QXuSacI; 
QXuSacI= ['Y', 'r', 'a', 'd', 'P'].join(dLpy)

由於dLpy我們已經知道是空字元了,所以實際上QXuSacI的值就是”Yradp”。

這樣,將三處組合,LN564的原始語句實際上是:

window[”ezVIxlyG”]('TQgaaGLDVYlaQT', ‘Yradp’);

這個ezVIxlyG的原型是什麼呢?搜尋程式碼,找到它的賦值點:

LN438:

#!javascript
var D1Kx; 
ezVIxlyG= !!D1Kx?true:( function (){ ……} );

這裡有一個約定俗成的內容,可以看到D1Kx是剛剛定義而且未賦值的,將其作為布林型來處理時,其預設值是false,因此,!!D1Kx的值實際也是false。

這樣該三目表示式實際上只是相當於一個普通的賦值:

#!javascript
ezVIxlyG = function(){……}

由於此時我們還沒處理該函式,因此這個混淆後的程式碼應該是十分難讀的,所以,我們對其進行一個簡單的處理: 複製LN439-545

  • 替換所有雙空格、三空格->單空格,直到沒有2個以上連續空格為止。

  • \n、\r全部刪除

  • 使用工具將程式碼重新格式化。

完成後,程式碼如下所示:

enter image description here

可見程式碼還是難以理解,這是因為其中包含了大量的變數:

enter image description here

你可以看到這個地方定義的變數大部分都會分散地被之後的程式碼使用到。

所以我們要關注的還是函式的最後:

#!javascript
if(flag == 1)
{
    rqNNhndhLxLVVb [YPub] ( UjcS )
}
else 
{
    rqNNhndhLxLVVb [YPub] ( UjcS )
}

這裡又是一個無用分支,rqNNhndhLxLVVb我們已經知道是window物件了,YPub是什麼呢,可以看看上面的程式碼,最好倒著看,我的注也是從5開始倒著寫到1處的,請注意:

#!javascript
YPub=tP+yMwnso  (注:eval)
tP= 'e' 、 yMwnso= ('Rv'+('uapt') ['re'+'place'] ( 'u', dLpy)) [iCQl0] ( K1DMU, 2 ) +'l'   (注:val)
iCQl0= (wRxKW+'snubnstrn') [F9k2c] ( /n/g , qm3sXy)     (注:substr)
F9k2c=qm3sXy+'r'+ yMwnso + 'pl' + qm3sXy    + 'ac' +yMwnso   (注:replace)
K1DMU=3-2

因此我們看到了這句實際上是:

window[”eval”](UjcS);

現在知道做什麼了嗎?對,先把eval換成alert!

enter image description here

然後,直接執行該HTML,得到解密後資料:

enter image description here

enter image description here

等等,共4次,將這些內容合起來就是解密後的程式碼了。可以看到這個程式碼利用了多個新漏洞,甚至包括卡巴斯基控制元件的安全漏洞。

IV.2 HTML與網馬攻擊5 - Virus In The Wild


讓我們再看看Magnitude Exploit Kit這款EK的程式碼,相比AEK而言,難度是高還是低

enter image description here

圖:MEK的Landing Page

可以看出來,相比而言它的程式碼貌似要簡單得多,可以清晰的看到document和eval被分別賦予了兩個不同的變數。

透過將eval修改為alert,執行後得到:

enter image description here

完了?顯然沒有,將eval換成alert之後得到的資料是一個function,而點選確定之後,得到了一個指令碼錯誤:

enter image description here

圖:指令碼錯誤

仔細閱讀一下,首先,這個eval的範圍是:

enter image description here

在它之後出現了一個從未見到過的函式:

enter image description here

而對比我們之前彈出的alert可以發現,這個函式就是eval解出來的結果,因此,我們應該做的是把eval部分換成解密後的內容:

#!javascript
function t1g6(a,b){var inn = document.getElementById('avp6').innerText;var out='';var c=inn.split('*');for(var k=a;k<b;k++) out += String.fromCharCode(c[k]-21);return out;}

用上述內容替換掉eval塊,得到:

enter image description here

但是之後顯然沒有eval了,這時,其實我們只需要瞭解document[”XXX”]將返回document下的XXX物件,這個物件是可以作為函式來呼叫的(或者不如說函式就是一個物件:) )就可以了:

enter image description here

因此後面的c1h82by0(document)就顯得很是危險,所以讓我們看看s4tb[0]的內容並註釋掉後面的內容,記得之前說的嘛?一個script塊中的程式碼一直到出錯為止都是可以正常執行的,所以不用管之後的程式碼會不會出錯了,主要是後面的程式碼很可能是惡意程式碼,不能讓惡意程式碼在我們自己的電腦上跑起來。

enter image description here

圖:執行結果

因此,可以知道這裡是在做document[”createElement”]這個操作,司馬昭之心,路人皆知,再將其內容改為alert(s4tb1),執行可得:

enter image description here

圖:執行結果

串上後面的內容可以知道,這段程式碼事實上正在建立一個iframe,其src執行漏洞內碼表面: hXXp://bf29df.e66.83.1c.3d8a.54.1393d.bc7dc6b.6.scg512374t1.changesmoves.in/47b1d0b4466375c9306821f48abcd6b5(放心,此時這個網站已經無法訪問了。)

至此,這個頁面的核心內容我們已經全部瞭解了,至於後面的幾個變數,解法也是一樣的,如果想要練手的話,可以試著將頁面內容全部還原成無混淆狀態試試看。頁面內容見附件。

IV.3 HTML與網馬攻擊6-利用arguments.callee實現“遞迴解密”的網馬以及解密


希臘神話中有一條名為Ouroboros的蛇,它咬著自己的尾巴,它的姿態象徵著“不死”、“完全、“無限”、“世界”、“睿智”等種種意味。

enter image description here

圖:烏洛波洛斯,網路圖

在程式設計中,稱作遞迴,遞迴在Javascript中可以像C的程式碼一樣:

function a(){ a();}

來呼叫,不僅如此,javascript還支援一種arguments.callee的方式來呼叫。callee為對當前函式的引用,故可以作為類似遞迴的方式來呼叫自身。

不過,遞迴還是需要謹慎的,稍有不慎,一個bug即可導致整個程式出現不可知的情況。

enter image description here

圖:IE10遞迴導致死迴圈,棧空間全部用完導致崩潰

言歸正傳,先讓我們看兩個例子大致理解一下普通遞迴和arguments.callee:

以下兩個例子輸出均為:1 2 3。

普通遞迴,

#!javascript
function mylog(current, max)
{
if(current <= max) 
{
console.log(current); 
add(current+1, max);
}
} 

mylog(1,3); 

以及arguments.callee:

#!javascript
function f(x)
{
console.log(x);
return arguments.callee;    
}
    
f(1)(2)(3);

從實際入手吧,請參考如下網馬的例子:

enter image description here

是否第一眼就看到了倒數第二行出現了

eval(I3qVh4gPT);

如果你試圖將它改為alert(I3qVh4gPT);,那麼這個函式的解密結果必然會失敗:

enter image description here

原因是什麼呢?請看第一行出現了

#!javascript
v ar eJmF3VT3H=arguments.callee.toString().replace(/\W/g,'').toUpperCase();

我們知道arguments.callee是對當前函式的引用,那麼這個引用轉為字串是什麼呢?讓我們測試一下:

enter image description here

原來就是返回了當前函式。

enter image description here

仔細一看,這裡會把函式自己當成引數來解密。

所以,如果我們想要解開這個指令碼的加密應該怎麼弄呢?

1. 簡單閱讀程式碼


從最後來,①eval(I3qVh4gPT);引用了變數I3qVh4gPT。

②I3qVh4gPT+=String.fromCharCode(EHxDfdAM5);引用了變數EHxDfdAM5。

③EHxDfdAM5=e3FP5e1M6-IA17ef3d3[bqjtxUvBR];if(EHxDfdAM5<0) {EHxDfdAM5=EHxDfdAM5+256;} 引用了變數e3FP5e1M6、IA17ef3d3[0]。

④e3FP5e1M6=parseInt(EWX1TnOBq,16); 引用了變數 EWX1TnOBq,將其作為十六進位制解析。

⑤var EWX1TnOBq=mXSYkqH0X.substr(PwgNCEKQL,2); 中,mXSYkqH0X是引數,PwgNCEKQL是計數器。

⑥for(PwgNCEKQL=0;PwgNCEKQL<Oq32NWn5D;PwgNCEKQL+=2) 這段程式碼在這個迴圈內,迴圈上限出現在:

⑦Oq32NWn5D=mXSYkqH0X.length; ,也即引數的長度,因此,這段程式碼在解密傳入的引數。

還有,③中出現了另一個變數IA17ef3d3,這個變數出現在⑧IA17ef3d3[PwgNCEKQL]=fgMN0vK2r.charCodeAt(va31p5um0);,這之中還引用了fgMN0vK2r、va31p5um0兩個變數

⑨fgMN0vK2r=RsIkkqdYi[(fgMN0vK2r^eJmF3VT3H.charCodeAt(gMKy026SO))&255]^((fgMN0vK2r>>8)&16777215);中出現了fgMN0vK2r。RsIkkqdYi是一個預設金鑰組,eJmF3VT3H是當前函式(arguments.callee.toString()等處理後的結果),gMKy026SO是計數器。因此這句是在基於一個金鑰組產生一個金鑰組;

⑩for(PwgNCEKQL=0;PwgNCEKQL<8;PwgNCEKQL++) {var va31p5um0=Oq32NWn5D+PwgNCEKQL;xy3D07u0l[PwgNCEKQL]=1;xy3D07u0l[PwgNCEKQL]=FSB4JaYie;if (va31p5um0>=8) {va31p5um0=va31p5um0-8;IA17ef3d3[PwgNCEKQL]=fgMN0vK2r.charCodeAt(va31p5um0);} 同樣,va31p5um0也在參與解密。

也即,將傳入引數每隔2個字元作為一個HEX,然後解出來,與將函式自身的字串透過解密演算法解出來的資料相減,兩者結果小於0的話,加上256,最終對所有字元都如此操作,將結果連線起來得到解密資料。

既然函式本身不能輕易修改,那麼只好從最終的eval做突破了,javascript中允許“劫持”一個物件。即和操作普通變數的賦值一樣,函式也是可以透過賦值來覆蓋的,請看第二部分。

2. 函式劫持


enter image description here

針對這個程式碼,因為最終它會透過eval來執行惡意程式碼,所以新增eval=alert,在執行到eval之前將其劫持即可。

執行程式碼可以得到:

enter image description here

最後,總結一下,在Jscript9.dll中,指令碼的函式中呼叫arguments.callee.toString()時,大致經歷瞭如下幾個步驟: 解析指令碼文字(ScriptSite::ParseScriptText); 送與解析核心,生成位元組碼,透過位元組碼直譯器(即Intepreter)來執行; 在處理到arguments.callee.toString()時,jscript會將函式自身marshal成BSTR,然後轉換成JsVar,傳遞給後續要使用它的程式碼。

題外話,這個程式碼其實是2010年發現的一個廣告軟體(是當時流行的Rogue antivirus,也就是偽裝成防毒軟體的廣告程式)安裝頁的Landing Page,當時我還特地上論壇和大家討論了怎麼解決,大家給出的思路也相當多,除了上述我說的方法之外,一些自動化解密工具也可以處理此類網馬,例如Malzilla。不過建議大家不要過於依賴工具,而是把工具當作可以簡化重複勞動的工具是最好。

到此為止,指令碼先鋒系列的解密部分就告一段落了,下一篇開始,將簡單的介紹偵錯程式的用法以及如何對網馬中使用的Shellcode進行除錯,其中也包括簡單的對惡意SWF、PDF的分析的內容。

參考資料


(1] 文中惡意指令碼打包,請在虛擬環境下測試與除錯(密碼drops.wooyun.org ):Download

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章