CTF 中的VM題目入門(二)
承接上一篇CTF VM入門,主要講TWCTF的一道題 EscapeMe。題目的利用中涉及到了MMU,比較複雜。
總體框架分析
先逆向host kvm.elf
首先是open /dev/kvm『等一系列初始化操作,之後會將運行時傳入的文件分別打開,將文件描述符存入全局變數 ptr。
之後會調用
sub_2B8E(a1, 0, 0LL, 0LL, 0LL);
其中關鍵的是
fd = *((_DWORD *)ptr + a2 + 1);
......
v9 = palloc(v8, (nbytes + 4095) & 0xFFFFFFFFFFFFF000LL);
......
read(fd, (void *)(*(_QWORD *)(a1 + 16) + v9), nbytes) == -1
......
result = v9;
其中a2是ptr的索引,a1是之前創建的vm struct,0x10偏移位置是作為guest物理地址的虛擬地址。palloc是題目自己實現的類似ptmalloc的分配器。並沒有仔細去逆向這套機制。palloc返回地址加上mmap的地址之後,直接就能在host中讀寫guest地址空間。不用做其他的轉換。
隨後v9被直接設置為guest的rip
v20 = v5;
v21 = 2LL;
if ( ioctl(fd, 0x4090AE82uLL, &v19) >= 0 )
其中v5是剛才的v9, v21是一個其它的寄存器。
0x4090AE82uLL的定義為是KVM_SET_REGS。
這樣kvm_run之後,進入kernel執行
kernel.bin並沒有符號表。我直接先從vmmcall入手分析。在kvm.elf的函數sub_1D61中,可以看到對應的IO操作。包括read,write,palloc,pfree等。其中當rax為0x30時調用的函數sub_2B8E,就是之前說過的載入bin文件的函數。返回的地址,將會被放在rax中,返回guest。
v21 = v8;
v26 += 3LL;
if ( ioctl(fd, 0x4090AE82uLL, &v21) >= 0 )
guest在得到入口地址之後,會做一系列檢查操作,最後進入177f這個函數。
seg000:0000000000001787 mov ax, 2Bh ; +
seg000:000000000000178B mov ds, eax
seg000:000000000000178D assume ds:nothing
seg000:000000000000178D push 2Bh
seg000:000000000000178F push rsi
seg000:0000000000001790 pushfq
seg000:0000000000001791 or [rsp+18h+var_18], 200h
seg000:0000000000001799 push 23h
seg000:000000000000179B push rdi
.......
iretq
這樣的話,iretq之後,rip的值即為rdi的值,rdi為上一個函數返回的elf文件的入口地址。
在理解了這套kvm系統如何運行的,我們再來看這個剛載入的elf文件。
一個菜單程序,在edit函數中:
v4 = strlen(*(_QWORD *)(16LL * v5 + memo));
read(0LL, *(_QWORD *)(16LL * v5 + memo), v4);
可以造成off by one。
off by one就不講了,直接貼上出題人的exp。
exploit.py說一下調試的一些事情。我們用gdb直接調試的是host,如何看guest的堆棧呢?
首先,讀出host為guest分配的物理地址。通過在相應的mmap位置下端點即可讀出。在這個題目中mmap的返回值是:0x7ffff75e4000。
其後,只要將guest中的虛擬地址轉換成物理地址,再加上這塊mmap的地址,就能得到相應的host的虛擬地址,即可通過gdb讀出數值。
這個地方比較繞,舉個例子,在guest的菜單程序中,有一個全局變數memo。要讀取memo的值,首先找到它在guest中的虛擬地址:0x604028。第二步,將它轉換成guest的物理地址(guest的MMU機制實現在下一部分)。可以自己讀出cr3的值,一步一步計算,但我採取的方法是:在host中的translate函數下端點,修改第三個參數 laddr為0x604028,再讀取返回值,這樣比較省事。
uint64_t __cdecl translate(vm *vm, uint64_t pml4_addr, uint64_t laddr, int write, int user)
第三步,將得到的數值,加上0x7ffff75e4000就得到了memo在host中的虛擬地址。就可以通過gdb讀出來了。