- 概述
- A tale of two mallocs
- CVE-2015-3864
- CVE-2017-0781
- Take Down MacOS Bluetooth with Zero-click RCE
- BleedingTooth: Linux Bluetooth Zero-Click Remote Code Execution
- Over The Air: Exploiting Broadcom’s Wi-Fi Stack (Part 1)
- CVE-2021-3156
- Zoom RCE from Pwn2Own 2021
- Instagram_RCE: Code Execution Vulnerability in Instagram App for Android and iOS
- 3D-Red-Pill-A-Guest-To-Host-Escape-On-QEMUKVM-Virtio-Device
- GROOMING THE IOS KERNEL HEAP
GitHub Link
https://github.com/hac425xxx/heap-exploitation-in-real-world
概述
本文將以多個真實的堆相關的漏洞以及一些高質量的研究文章為例,介紹在真實漏洞場景下的堆利用技巧,主要是介紹各種現實漏洞場景的堆布局技巧。
vuln obj : 存在漏洞的對象,比如存在堆溢出的對象。
target obj: 漏洞利用時需要篡改的對象,比如利用堆溢出時,我們想覆蓋的對象。
A tale of two mallocs
當來利用堆溢出的時候,我們需要找一個 gadget object 來進行利用
常用的 gadget object 類型:
- 有函數指針,虛表指針,可用於劫持控制流。
- 對象中有 size/length 字段,修改這個字段可以導致后續使用對象時發送越界。
- 對象中的指針在后面的邏輯中會被用於讀、寫內存等操作,利用程序的邏輯可以完成一些漏洞利用的原語。
此外利用堆漏洞非常重要的是進行堆布局,為了進行堆布局我們需要找到外部輸入可控的內存操作原語,搜索內存操作原語時需要關注三個要素:
- 內存分配的大小是否可控
- 內存中的內容是否可控
- 分配到的內存的生命周期是否可控,即我們能否控制其申請和釋放的時機
然后根據不同的內存控制原語采用合適方式進行堆布局,常見搜索內存操作原語的思路是在代碼中找含有malloc,new,realloc,std::vector,std::string的調用點,然后判斷是否可以由外部輸入觸發。
作者提到進行堆布局的時候,可以選用占位式堆噴,如圖所示:
此外為了提升溢出到 gadget 的概率我們可以先把內存碎片清理了,然后再進行占位式堆噴,這樣 vuln obj 和 gadget obj 大概率會相鄰分配。
如果分配 gadget object 的時候會有一些小對象的分配從而導致影響內存布局,我們可以先在堆上占位一些小的內存塊,然后在分配 gadget 前釋放一些小的內存塊,這樣小對象就會落在之前占位的小內存塊中,避免影響 vuln obj 和 gadget object 之間的布局。
Assuming the gadget object is the one which allocates the unwanted allocations, one way to deal with this issue is to do the following:
- First, at the beginning of the exploit, we allocate a bunch of smaller blocks, let’s say 100 blocks of 0x100 bytes each
- We then allocate all the placeholder sets (表示一組相鄰的 overflowable 和 gadget 占位對象)
- Then, for each placeholder set, we first free the gadget placeholder.
- Then we free a few of the small filter allocations. These will be added to the free bins
- Then we allocate the gadget itself, the smaller unwanted allocations will use the filter allocations we just freed, and the gadget itself will fall on the placeholder.
CVE-2015-3864
漏洞是一個整數溢出導致的堆溢出
case FOURCC('t', 'x', '3', 'g'):
{
uint32_t type;
const void *data;
size_t size = 0;
if (!mLastTrack->meta->findData(
kKeyTextFormatData, &type, &data, &size)) {
size = 0;
}
if (SIZE_MAX - chunk_size <= size) { // <---- attempt to prevent overflow
return ERROR_MALFORMED;
}
uint8_t *buffer = new uint8_t[size + chunk_size];
if (size > 0) {
memcpy(buffer, data, size);
}
if ((size_t)(mDataSource->readAt(*offset, buffer + size, chunk_size))
< chunk_size) {
delete[] buffer;
buffer = NULL;
return ERROR_IO;
}
在分配內存前有一個檢查,但由於 chunk_size
在 32 位系統下下也是 uint64_t
的,所以當 chunk_size > SIZE_MAX
時(比如 0xFFFFFFFFFFFFFFFF
),會通過檢查,然后下面分配的時候就會整數溢出。
從文件中解析 chunk_size
的代碼如下
status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
uint32_t hdr[2];
if (mDataSource->readAt(*offset, hdr, 8) < 8) {
return ERROR_IO;
}
uint64_t chunk_size = ntohl(hdr[0]);
uint32_t chunk_type = ntohl(hdr[1]);
off64_t data_offset = *offset + 8;
if (chunk_size == 1) {
if (mDataSource->readAt(*offset + 8, &chunk_size, 8) < 8) {
return ERROR_IO;
}
chunk_size = ntoh64(chunk_size); // 從文件中獲取 8 字節的 chunk_size
因此通過這個漏洞我們可以實現一個堆溢出,vuln obj 的大小可控,溢出的內容可控,由於是整數溢出后面可能會拷貝大量的數據(比如 0xFFFFFFFFFFFFFFFF
),最終可能會由於訪問到沒有映射的內存導致進程崩潰。
因此,利用這種類型的整數溢出漏洞,我們需要找到一些路徑,讓溢出發生后,不會拷貝過量的數據,對於這個漏洞來說我們可能有以下兩種方式:
- 內存分配后,程序會調用 readAt 從文件中讀取 chunk_size 的數據,如果能讓 readAt 返回值小於 chunk_size (比如控制文件大小),就能避免溢出過量的數據。
- 通過覆蓋在具體數據拷貝之前調用的函數指針完成利用,從而避免了溢出過量數據。
該漏洞的利用采取的是第二種方式,即溢出到 mDataSource
對象 (其類型 MPEG4DataSource
),然后把該對象的虛表劫持,從而劫持 readAt
這個虛函數調用。
那么利用該漏洞的 target obj
就是 mDataSource
對象,如果要利用該漏洞,buffer 和 mDataSource 的內存布局如下:
下面就介紹如何利用程序中的邏輯來整理堆的布局,實現上述的布局,首先需要分析源碼定位 vuln obj 和 target obj 的分配方式,當解析到 stbl
標簽時,會分配 mDataSource
對象(target obj),當解析 tx3g
標簽時就會分配 vuln obj 並觸發溢出。
然后再看看程序用的內存分配器的一些特點,其使用的是 jemalloc ,jemalloc的大概思路是把內存塊分成大小相同的 object,然后返回給申請者,類似於內核的 slub 分配器。
由於堆分配器的實現,堆在初始情況下先分配的對象的地址會比后分配的對象的地址小,這時如果直接先分配 target obj 的話,vuln obj 就會在 target obj 的后面,因此無法溢出到 target obj.
假如先分配 vuln obj 的話,由於漏洞觸發點的內存分配和數據溢出是一起發生的,也是無法覆蓋到 mDataSource
。
或者我們可以利用jemalloc的LIFO特性,先把后面的塊釋放了,然后把前面的塊釋放了,然后再先分配 target obj 后分配 vuln obj,也可以實現上述的布局。
exploit
里面的思路是采用占位對象,然后通過控制占位對象的分配與釋放,來完成布局,示意圖如下:
- 首先分配兩個占位對象 A 和 B,且 A 和 B 的前后都是被分配出去的內存,然后釋放B,再利用
stbl
標簽分配 target 對象,這樣mDataSource
就會落在 B 的位置。 - 然后釋放 A ,並利用
tx3g
標簽觸發漏洞,分配buffer到 A 的位置,就能完成我們需要的布局,這樣一溢出就可以覆蓋到mDataSource
對象。
要完成上述布局,關鍵點在於需要找到可以通過用戶輸入(mp4文件)控制 分配/釋放 的代碼邏輯,在 libstagefright
中我們可以利用 avcC
和 hvcC
來進行占位,通過這兩個標簽的特點為:
- 分配任意大小的內存,內存中的內容可控(從文件讀取)
- 內存釋放的時機可以通過輸入文件中的特定標簽控制
利用 avcC
和 hvcC
來占位的示例圖如下:
為了穩定的完成堆布局,還需要解決內存碎片的問題,上述布局成立的前提是 avcC 和 hvcC 對象是相鄰的,如果內存中存在內存碎片的話,這兩個對象是有可能不相鄰的。
為了解決這個問題常用的方式是首先把之前的內存碎片清掉(即大量分配內存,把之前堆中碎片的內存塊申請完),之后再申請內存的時候,我們分配到的內存塊的順序就會符合我們的預期了。
exploit
中使用的是 pssh
標簽來清理內存碎片,原因是程序在解析 pssh
標簽時會根據標簽中的數據分配內存,內存的大小和內容可控,且分配出來的內存在整個文件解析完成后才會被釋放,因此直接在輸入文件中放置多個 pssh
標簽就可以完成內存碎片的清理工作。
完成堆布局后現在就可以溢出到 mDataSource
對象 (其類型 MPEG4DataSource
),接下來的利用思路就是覆蓋對象的虛表,把虛表劫持到我們偽造的需要,然后對象進行虛函數調用的時候就可以劫持pc。
目前的問題是如何知道 偽造的虛表 的地址,獲取地址一般有兩種方式:
- 找一個信息泄漏漏洞,或者利用漏洞構造信息泄漏,從而可以拿到堆的地址,然后把虛表指針指向堆上的可控數據位置即可。
- 利用堆噴射技術,在某個固定的地址布置偽造的虛表數據,然后把虛表指針指向該位置即可。
這里使用的是堆噴射技術,以32位系統為例,堆噴射的思路大概是由於進程虛擬地址空間的大小限制,當程序分配大量內存時(比如 0xff000 字節時), 盡管存在隨機化,我們依然可以以極大的概率在某個具體的地址(0xf7500000)上布置我們的數據。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#define ALLOC_SIZE 0xff000
#define ALLOC_COUNT 0x1
int main(int argc, char** argv) {
int i = 0;
char* min_ptr = (char*)0xffffffff;
char* max_ptr = (char*)0;
for (i = 0; i < ALLOC_COUNT; ++i) {
char* ptr = mmap(NULL, ALLOC_SIZE,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS,
-1, 0);
if (ptr < min_ptr) {
fprintf(stderr, "new min: %p\n", ptr);
min_ptr = ptr;
}
if (ptr + ALLOC_SIZE > max_ptr) {
fprintf(stderr, "new max: %p\n", ptr + ALLOC_SIZE);
max_ptr = ptr + ALLOC_SIZE;
}
memset(ptr, '\xcc', ALLOC_SIZE);
}
fprintf(stderr, "finished min: %p max %p\n", min_ptr, max_ptr);
((void(*)())0xf7500000)();
}
利用大量的內存分配可以讓進程的某個地址存放我們的數據,數據的內容如何控制呢,按照我的理解,我們在進行堆噴的時候盡量按頁對齊去分配,然后以頁為單位進行數據布局,就可以實現在可預測的地址,布置可控的數據,具體的偏移和布局還需要調試確認。
exploit 中的堆噴射思路如下
小結
該漏洞的利用非常經典,主要的特點如下:
- 觸發整數溢出后,通過劫持后續可能會使用的虛函數調用來避免整數溢出后的內存拷貝訪問到不可訪問的內存.
- 通過分析程序對文件的解析流程,找到了內存申請和釋放的原語(avcC 和 hvcC),隨后使用這兩個標簽進行占位並完成了 vuln obj 和 target obj 的布局。
- 為了增大占位成功的概率,使用了 pssh 標簽分配大量對象,清理內存碎片。
- 為了在沒有信息泄露的情況下劫持虛表,利用
pssh
進行堆噴射,在可預測的地址處放置 rop gadget.
主要可以學習的思路有:
- 觸發漏洞后,要思考后續的步驟,比如后續要覆蓋什么,為了完成覆蓋,需要什么樣的內存布局。
- 然后去程序中查找是否存在內存控制的原語,比如內存申請/釋放原語,申請的大小、內存的內容、釋放的時機是否可控等。
- 然后就根據堆分配器的特性來嘗試堆布局。
- 堆噴射在32位下還是很有用的,64位下在一些情況下面也是可以用的。
- 占位型堆布局和內存碎片的清理也非常的經典。
CVE-2017-0781
漏洞是8字節的溢出, vuln obj 的大小可控。
作者通過不斷調整 vuln obj 的大小來觸發漏洞,然后分析 crash 的上下文,最后在32字節的run中找到了合適的 target obj (fixed_queue_t),該對象中有個函數指針,可以用於劫持控制流。
為了實現控制 pc,還利用堆噴在可預測的地址上布置了數據,布置的過程主要靠嘗試和調試。
小結
該漏洞利用最直接借鑒的思想是通過不斷嘗試 vuln obj 的大小,來找到潛在的 target obj,這種思路比較有通用性,也可以減少人工搜索 target obj 的工作量,甚至會發現一些比較神奇的對象。
還有就是 jemalloc 的堆噴可以通過觀察 regions 的內存布局來提升成功率。
Take Down MacOS Bluetooth with Zero-click RCE
這組漏洞利用包含兩個漏洞,一個信息泄露漏洞(CVE-2020-3847)和一個堆溢出漏洞(CVE-2020-3848)。
CVE-2020-3847 的成因是內存申請的大小和訪問內存時的偏移檢查有問題,漏洞觸發流程:
- 首先發一個請求,讓 ServiceAttributeResults 分配 16 字節的內存
- 然后再發一個請求,設置一個比較大的偏移,就可以 泄露出 ServiceAttributeResults 后面的內存數據
CVE-2020-3848 的漏洞成因是分配內存時分配的是 0x20 字節,但是拷貝數據時最多可以拷貝 0xff 字節。
為了實現 Zero Click 的漏洞利用,作者經過分析發現只能通過創建 SDP 連接來讓目標分配內存,且一個設備最多只能創建 30 個 SDP 連接,創建 30 個 SDP 連接后實際會分配的對象和關系如下圖
隨后作者通過釋放其中的幾個 SDP 連接對象,並結合信息泄露漏洞來檢查當前堆布局是否可以用於利用,即vuln obj 后面是否存在可以用於利用的對象。
大概思路應該是通過一些內存的申請釋放看能否讓 vuln obj 后面放置可利用的對象。
小結
主要啟示在於:
- 在審計代碼的時候,要注意審計內存申請和使用不是在同一次請求、解析過程中處理的情況,兩者的不一致就有可能導致漏洞。
- 該漏洞利用的堆布局有點撞運氣的感覺,不過由於有信息泄露,我們可以多次嘗試,直到符合預期的堆布局出現再觸發堆溢出漏洞。
- 有限的堆操作原語(只能控制某些特定對象的申請於釋放)也是非常有用的。
BleedingTooth: Linux Bluetooth Zero-Click Remote Code Execution
這篇文章涉及3個漏洞,本節主要涉及其中2個被利用的漏洞,即 CVE-2020-12352 和 CVE-2020-12351,比較有意思的是作者在編寫 CVE-2020-12352 的 POC 時,觸發了 CVE-2020-12351.
CVE-2020-12351 是一個棧變量未初始化漏洞導致的信息泄露,作者通過隨機的發送一些報文進行嘗試,最終可以通過該漏洞泄露出 內核和堆的地址。
CVE-2020-12352 是一個類型混淆漏洞,漏洞的成因是把 struct amp_mgr
對象當作了 struct sock
對象傳入了 sk_filter
函數進行處理。
經過分析,通過控制 sock
結構體 (即被類型混淆的 amp_mgr
對象)的 sk_filter
指針可以完成漏洞利用, struct sock
的結構體布局如下:
// pahole -E -C sock --hex bluetooth.ko
struct sock {
struct sock_common {
...
short unsigned int skc_family; /* 0x10 0x2 */
...
} __sk_common; /* 0 0x88 */
...
struct sk_filter * sk_filter; /* 0x110 0x8 */
可以看到 sk_filter
字段位於 sock
結構體偏移 0x110
處,但是 struct amp_mgr
的大小為 0x70
,由於 slub 分配器的特性, amp_mgr
結構體實際會在 kmalloc-128
處分配,所以 sk_filter
字段實際位於 amp_mgr
結構體后面第二個內存塊中,如下圖所示:
amp_mgr
分配在 kmalloc-128
中,當 amp_mgr
被當作 sock
結構傳入 sk_filter
函數處理時,其訪問 sk->sk_filter
時,實際訪問的是圖中標紅區域。
因此目前的問題是如何控制 fake sk_filter
字段,作者的思路是首先分配 amp_mgr
,然后再 kmalloc-128
中分配兩塊內存,從而控制 fake sk_filter
字段.
上述思路的關鍵是需要找到可以遠程從 kmalloc-128 中分配內存的原語,常見思路是在協議棧代碼中搜索內存申請函數(比如 kmalloc, kzalloc等),不過作者沒有找到上述的原語,最終是利用 kmemdup
操作實現的內存申請,代碼如下
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/bluetooth/a2mp.c
static int a2mp_getampassoc_rsp(struct amp_mgr *mgr, struct sk_buff *skb,
struct a2mp_cmd *hdr)
{
...
u16 len = le16_to_cpu(hdr->len);
...
assoc_len = len - sizeof(*rsp);
...
ctrl = amp_ctrl_lookup(mgr, rsp->id);
if (ctrl) {
u8 *assoc;
assoc = kmemdup(rsp->amp_assoc, assoc_len, GFP_KERNEL);
if (!assoc) {
amp_ctrl_put(ctrl);
return -ENOMEM;
}
ctrl->assoc = assoc;
ctrl->assoc_len = assoc_len;
ctrl->assoc_rem_len = assoc_len;
ctrl->assoc_len_so_far = 0;
amp_ctrl_put(ctrl);
}
...
}
大概堆布局思路如下:
- 首先利用
kmemdup
的原語大量分配內存,清理之前slab
中的碎片 - 然后分配
amp_mgr
,之后再分配一些ctrl->assoc
,最后觸發漏洞就可以控制fake sk_filter
的值。
不過看 exploit
里面的堆布局思路,貌似是直接先分配多個 ctrl->assoc
,然后釋放掉最后一個 assoc
和 amp_mgr
,最后重連,就有一定概率會達到上面的布局,從而完成利用。
堆布局相關代碼片段:
小結
- 搜索內存操作原語時,間接調用內存申請的函數也是需要關注的。
- 做漏洞利用時有一定概率也是可以接受的,比如堆布局,即使無法達到100%,能提升一點概率是一點,或者如果概率可以接受的話也可以直接隨機着來。
Over The Air: Exploiting Broadcom’s Wi-Fi Stack (Part 1)
文章中利用的漏洞是固件在解除 TDLS 連接時 由於解析 FT IE 時沒有檢查長度導致的堆溢出漏洞
因此我們就有了一個堆溢出漏洞,vuln obj 的大小是 256 字節,下面就需要找被溢出的對象,以及尋找堆布局的方式。
作者首先逆向分析了固件中的堆分配器,然后通過 固件代碼 patch、內存dump等手段實現了一個堆的可視化工具,可以跟蹤固件的內存申請和釋放、可視化某個時刻的堆內存布局:
圖中紅色表示正在使用的內存塊,灰色表示處於 free 狀態的內存塊。
- 圖中第一行表示 初始的堆狀態,可以看到這里有一塊很大的空閑內存.
- 第二行表示 創建 TDLS 連接后的堆狀態,有很多內存被使用了,值得注意的是此時堆中還有兩塊特別標注的空閑內存塊,即
0x1f03fc
(大小為 0x11c )和0x1f2108
(大小為 0x124).
然后我們如果嘗試解除 TDLS 連接並發送惡意的FT IE觸發漏洞,由於堆分配器的實現,此時 malloc(256)
會分配到 0x1f03fc
這塊內存,我們也就可以溢出到 0x1f0520
后面的塊。
此外經過作者的不斷嘗試發現,堆狀態非常穩定,每次 創建/解除 TDLS 連接 時堆的狀態基本是一樣的,這樣如果能在這個堆狀態下完成利用,exploit的穩定性應該也是比較高的,不過如果程序中有一些可用於堆布局的原語(內存申請、釋放),可能可以改變這個內存布局,不過作者沒有嘗試,而是在當前內存狀態下完成了利用。
因此 vuln obj
是 0x1f03fc
這塊內存,target obj
是 0x1f0520
后面的內存塊。
對於堆溢出的利用常見的思路有兩種:
- 修改堆塊頭的元數據,比如 size,或者處於釋放狀態的塊中的 next 指針
- 修改堆中對象的數據,比如對象的長度字段,堆中的指針等,然后利用程序對對象的操作來進行后續的利用
vuln obj
和 target obj
的內存 dump 如下:
可以看到 target obj
的頭不由兩個看着像指針的值,不過經過修改嘗試發現這兩個指針好像沒有人用,因此目前只能嘗試修改內存塊的 頭部字段,由於 target obj 所在內存塊是 inuse 狀態,所以頭部字段中只有 size 字段是有效的。
在 glibc 的堆溢出 中常用的方式是修改 size 字段來構造重疊的堆塊,這里也是這樣的思路,由於目標系統中堆的使用情況比較復雜, 作者采取的方式是,寫腳本自動化地測試,即嘗試把 target obj 的 size 字段修改成不同的值,然后檢查操作完成后的堆狀態。
經過測試,作者發現把 target obj
的 size
字段覆蓋為 72
並斷開 TDLS
連接,堆中出現了重疊的堆塊:
可以看到在斷開 TDLS
連接后,0x1f072c
這個 0
字節的 chunk
位於一個大的空閑塊中間。
創建重疊堆塊后,還需要一個控制內存分配的原語,用於分配到重疊的塊,然后修改它的 size 和 next 指針,從而實現任意地址寫,固件處理 action 幀的代碼中就存在這樣的邏輯:
A 是一個全局變量,利用 A 的分配和申請邏輯,可以獲取一個生命周期、大小、內容可控的內存控制原語。
最后利用堆分配的 best-fit
特性,修改 chunk
的 next
指針,實現把一個已分配的塊鏈接到空閑塊的鏈表里面,然后實現任意地址寫,最后的利用手法如下:
首先利用任意地址寫,往一個初始化階段分配的堆塊中布置 shellcode(由於沒有地址隨機化,系統啟動過程中分配的一些堆內存的位置會固定),然后修改 堆中定時器的函數指針,最后等待定時器到期,執行 shellcode.
選擇把處於使用中的堆塊鏈入空閑塊鏈表的原因是:處於使用的塊的 next 指針為0,這樣就不會導致內存分配器在分配內存時由於遍歷鏈表導致崩潰。
小結
- 堆利用的時候,通過調試、打印、dump等手段查看 vuln obj 附件的內存狀態,以及觸發漏洞前后的內存使用情況,可以很好輔助漏洞利用、定位漏洞利用的方案。
- 對於比較復雜、不熟悉的場景可以嘗試暴力枚舉,來探測可利用的方案。
CVE-2021-3156
漏洞成因是 sudo 在解析命令行參數時存在堆溢出,vuln obj 的大小和內容可控。
目前問題就是尋找 target obj,作者的思路是fuzz,通過隨機選擇 vuln obj 的大小、溢出的大小、setlocale 涉及的相關環境變量(用於控制溢出前的內存塊布局),最后通過對 crash 的上下文去重,發現了三種可用於利用的場景,本節將介紹通過覆蓋 service_user
完成利用的過程。
首先通過調整 poc 和調試,能夠發現在堆上確實是存在 service_user
結構,不過調試發現其和 vuln obj 的偏移過大,直接去嘗試覆蓋 service_user
程序會由於中間的一些數據被覆蓋而崩潰,且兩者的偏移也不固定。
為了解決這個問題,可以利用 setlocale
函數中的 內存分配/釋放 操作進行堆布局,讓 user_args
落在 service_user
的前面。
setlocale
函數在解析環境變量(比如LC_CTYPE
, LC_MESSAGES
, LC_TIME
等)時,會申請內存並把環境變量的值拷貝到申請的內存中,申請的內存在函數退出前會被釋放。
通過利用 setlocale
函數的邏輯和堆分配器的機制,我們提前在堆中創建一些 free 的堆塊,讓 service_user 分配到其中的一個空閑塊,這樣在漏洞觸發時就能在 service_user
前面留一個 free
堆塊,這時我們將 user_args
分配到 service_user
前面就可以穩定溢出service_user
了,大概流程圖如下所示:
小結
- 通過 fuzz 來探測可能用於利用的內存狀態值得學習。
- 通過提前在堆里面鑿一些洞(空閑的堆塊)進行堆布局的思路非常新奇。
Zoom RCE from Pwn2Own 2021
漏洞是 Zoom
在進行密鑰協商時存在一個堆溢出,堆溢出的情況如下:
- vuln obj 的大小固定 1040 字節
- 溢出的大小和內容可控
漏洞利用的環境是 win10 + 32位的 Zoom 軟件。
首先利用堆溢出實現信息泄露,常見的思路是分配一些帶 length 字段的對象,然后覆蓋length字段從而 leak 出一些數據,經過一番分析沒有發現能夠回顯的這種對象。
最后發現,可以給目標 Zoom
客戶端發送特定的請求,讓對端 Zoom
向我們的服務器發起 https
請求,請求的 url 部分可控,有種 SSRF 的感覺。
接下來就是利用這個 bug 進行信息泄露,具體來說就是利用溢出把 URL
末尾的 \x00
覆蓋掉,然后讓目標客戶端向我們的服務的發起請求,就可以泄露 URL 后面的內存數據了。
這里選擇泄露的對象是 TLS1_PRF_PKEY_CTX
,這個對象是 openssl
進行 TLS 協商時創建的,對象中存在指向 DLL
的指針,通過泄露這個指針可以可以拿到 DLL
的基地址。
如果堆布局按照預期就能在我們的服務器收到泄露的數據
不過由於 LFH 分配器的隨機化和內存狀態的不確定性,成功率還是比較低,作者采取了一些措施來提升成功率:
- 創建多個服務器的連接,用於不斷的進行TLS協商,目的是在 LFH 的桶中分配大量
TLS1_PRF_PKEY_CTX
,這個過程中即使對象被釋放也沒關系,因為釋放之后內存中的指針不會被清理,也能進行leak,這樣就可以增大布局的概率。 - 在覆蓋的過程中,可能由於內存布局的不一致導致 vuln obj 和 url 之間存在一些帶指針的對象(比如某些類的實例),作者的處理方式是首先通過堆噴在某個地址(比如 0x0d0d0d0d)放置數據,然后用 0x0d0d0d0d 覆蓋對象,這樣即使覆蓋到了不正確的對象也不會導致進程崩潰,從而可以多次嘗試。
完成信息泄露后,下一步就是找一個對象控制 PC, 這一步使用的是 FileWrapperImpl 該對象會在客戶端被呼叫響鈴的時候被不斷的創建和刪除,因此使用堆溢出覆蓋其虛表即可劫持控制流。
為了劫持虛表還需要進行堆噴,攻擊者可以發起請求讓目標客戶端加載 gif
圖片,而且沒有大小限制,利用這個機制就可以進行堆噴。
第二部的過程中也會存在很多不確定性,比如溢出 vuln obj 的過程中可能會覆蓋到堆中的其他類的虛表,作者的解決方案如下:
- 不斷嘗試溢出、調試搜集在利用過程中可能會觸發的場景,在虛表、rop 中進行適配,比如 A 類調用的時候可能用的是虛表偏移 0x20 的函數指針,我們就在 0x20 的函數指針布置適合 A 的利用 rop.
- 不斷溢出,每次溢出的大小遞增,避免出現一次溢出太多導致不穩定。
小結
- 利用客戶端的 url 請求機制進行 leak 也是一個思路。
- 處理堆布局隨機的情況可以多次嘗試,為了能夠多次嘗試還需要合理控制溢出數據,避免溢出失敗導致進程 Crash.
- 針對不同的溢出場景,在虛表中進行適配思路非常不錯!
Instagram_RCE: Code Execution Vulnerability in Instagram App for Android and iOS
漏洞是在解析圖片時存在整數溢出導致的堆溢出
首先根據 width
, height
, output_components
計算分配內存的大小,然后從文件中一段一段讀入內存,如果 width * height * output_components
發生整數溢出,后面從文件中讀取內容時就會溢出。
之前提到過利用整數溢出漏洞需要找到終止拷貝的條件,或者在訪問到非法地址前劫持控制流,這里采用的是覆蓋拷貝過程中會用到的函數指針,在循環訪問到非法地址前劫持控制流。
作者通過分析循環中的一些函數調用,發現在 jpeg_read_scanlines
函數和子函數里面會使用 cinfo
結構體中的一些的函數指針
struct jpeg_decompress_struct {
......
struct jpeg_d_main_controller *main; <<-- there’s a function pointer here
struct jpeg_d_coef_controller *coef; <<-- there’s a function pointer here
struct jpeg_d_post_controller *post; <<-- there’s a function pointer here
......
};
target obj 找到了不過由於沒有找到合適的內存控制原語,沒法進行完美的堆布局,作者最終采取的策略是通過調試和觀察漏洞觸發前后的堆狀態,來搜索可能用於利用的 vuln obj 大小。
首先通過分析知道 cinfo
分配在 0x5000
的 run
中,然后查看 cinfo 前面的一些run中的使用情況,發現可以通過分配 0xe0
的內存,然后往后溢出即可覆蓋 cinfo
.
原文作者到這里就沒繼續了,這種方式有點撞運氣的成分,如果進程在之前有些其他分配就可能會導致偏移不一致,不過原文中也提到了,我們可以溢出多一點總是能夠溢出到函數指針的。
小結
- 通過修改循環中的函數指針來防止整數溢出后的Crash也是比較經典。
- 有時候實在沒有堆控制原語時,也需要多調試、分析,查看 target obj 和 vuln obj 的附件是否有可以用於利用的內存塊。
3D-Red-Pill-A-Guest-To-Host-Escape-On-QEMUKVM-Virtio-Device
漏洞利用鏈包含兩個漏洞:
- malloc 未初始化導致的信息泄露。
- vrend_resource 堆溢出,vuln obj 的大小可控,溢出的大小內容可控。
利用未初始化漏洞的思路是堆噴帶有函數指針的結構體,然后釋放這些結構體,最后觸發未初始化漏洞並獲取未初始化的內存內容,從而獲取 virglrenderer 庫的地址。
獲取 libc 的地址的思路是申請大塊內存,內存中就會包含libc 的地址
堆溢出的利用思路是堆噴多個 vrend_resource 對象,然后利用溢出修改下一個 vrend_resource 對象的指針,從而實現任意地址寫
溢出前,兩個 vrend_resource 對象的布局
溢出后,第二個 vrend_resource 對象的指針修改為任意地址,然后利用程序的邏輯就可以實現任意地址寫
小結
- 利用未初始化漏洞進行內存泄露的思路值得學習,比如堆噴帶指針的結構,然后利用未初始化漏洞獲取地址。
- 通過堆噴 vuln obj ,然后溢出下一個 vuln obj 的指針來進行任意地址寫,得到啟示是做利用的時候可以先看 vuln obj 是否就可以作為 target obj.
GROOMING THE IOS KERNEL HEAP
主要就是介紹如何在 ios 的內核里面進行占位式堆布局。
具體思路是先清理堆中的內存碎片,然后用多個 victim object 包着一個 vuln object ,這樣就能提升溢出到 victim object 的成功率,如下圖所示
其中: V 表示 victim object, P 表示用於占位的對象,后面溢出的時候就會把 P 釋放,然后分配 vuln object.
為了進一步增加漏洞利用的成功率,可以多創建幾個用於漏洞利用的區域,多次觸發漏洞來提升成功率,如下圖所示:
小結
通過在 vuln obj 前后多包裹幾個 victim object 和 創建多個漏洞利用區域來提升成功率的思路值得借鑒。