“网谷杯”安全极客大赛 Writeup

碎念

这次比赛是在看见群里有人说起这个比赛,刚好闲着没事顺手打了一下,主要写了 Crypto 和 MISC (流量分析也算 MISC 吧)方向的题目,整体来看难度不高,但有些时候需要一些想象力,因为对其他的方向并不是很了解,因此一个人想打入决赛还是有点难度的=͟͟͞͞( •̀д•́)
不过想要吐槽的一点就是这个比赛的赛制,没想到它的动态积分竟然是越早提交分越高,那这样的话拼的不就是简单题的手速和网速了吗,简单题和难题都没有什么差别了,感觉是有点离谱的。。

Crypto

证书修复-wgb

本题考查 PEM,首先我们要了解私钥文件的格式

1
2
3
4
5
6
7
8
9
10
11
RSAPrivateKey ::= SEQUENCE {
version Version, -- INTEGER(一般是 0)
modulus INTEGER, -- n
publicExponent INTEGER, -- e
privateExponent INTEGER, -- d
prime1 INTEGER, -- p
prime2 INTEGER, -- q
exponent1 INTEGER, -- d mod (p-1)
exponent2 INTEGER, -- d mod (q-1)
coefficient INTEGER, -- q^(-1) mod p
}

flag1

直接给出了私钥文件,将 base64 解码后能够得到 n、d,我们便可以直接解密

1
2
3
4
5
6
7
8
9
10
11
12
13
MIICXAIBAAKBgQCsnlJbVdPOwSCVdI6tiL7J88QfPFomm4mySzKu6AHCVS8gMJc+
OTV+k14Q3M9PjqEDQfpt/oP2nNq0Z8sDYErT323Q75gsJM8YNe+sK/W2qzsP/XP2
y+Z6qLZXYXgE9g1ofqeZYgcwN819Xiw7ELHkHP53w+KChUDp7fJH/s0+wwIDAQAB
AoGAIwia5Gid7UdrCoo2bufi+O/dbQ19qnpEzwfOCIuT19IcF2dlOr63qbHVFI0M
EjR7X0LjxbEzLTkWdijenhfL96+IwzPig5PFL1dzxwp7bFct/9sQNuf/FZD1eC56
QjzXxOD0v9itzVMvbIR80MclgEK1zUeg7tlIRHCofoLoALECQQD8sNNSgmMKdXBt
dimY6ziK/xXtQ2TW2ekQ8l2VT5f/HD2M6n7gwadSPM2P2/UVC2ekK9SMGwe5giGR
bTerjjNZAkEAruELdXFBj/iheihbSXO3uDf9+BdvQFyMDIjPDTjx3GaY2Rwvl1Da
Cnwx2326mW3fuRYT/r9KZ9ViM5VX23vLewJAF7VrjnFHY6LTMqIEkyF2w6vdXc6V
9oaplHp5B1pZc4ktyTvzPZlN3qxvyRClNR1F0yR9o8uhdOldxuxNbd5AuQJAAJAi
7E7gCimfw3fry1G/Dq3HeIwCxK9HmCa8m+tKBA6kgVZw5MjS2uPJeU5vUl0jMkJa
8CAHikwKk+XwMXm1jwJBAK8rEl7TVWO5vmP2Xt/JITHUu5qSgYB9p9YwnfhQa4b/
rOIIFyuuq0tg3xASP+50O+fqvSOK5gZIx+KbNtWugcI=

转换成十六进制后,可以按照上边格式拆分,最终得到:
1
2
n = 121216793759965406495195349784688471172903898464863919579528667261419201175006158506254851920269184174476441342645096664857617956346276850231302641370595849992364168925183323567715165305825214477546120105756089950903529561478112787635175307546002017263761103481685637785188859785795387227441771118141108993731
d = 24601440007954283409258790415889365626604738597756305511807235656867674669709582591386480696270815244895420087866738342299329789849425948470734950224610450629270896689735724164750653504067075012851069860052288413393492140397638874689747304249627737638521072321625292497180208183282090634794285367566044496049

直接给出私钥 d,直接解密

flag2

与 flag1 相比,多了一个公钥文件,里边只有 n、e,
而私钥文件的前半部分不显示,但是我们可以得到 p、q,并且 n、e 和公钥文件中相同,因此也可以直接解 rsa 了。

1
2
3
4
n = 88024091485722968131203129772830524881654529625144134588814340590011554124604316403258794718595058760192581465181932954405288276198255951037890441057834815115351239151027730304080110594116192807779817527469691548261644538550529011882499685981945213841298369305720161373509056672240371216670722254664850792423
e = 65537
p = 9582848127563543144923745964600370727631625906884933335707836734329263380236258927038536326818153720067174259502906373148203118612164064303294028202007639
q = 9185587657654265271925862790545538824407691820908394988893401857915219018271791875599781315096940084389655919586711021311553394520708985687587340820963057

flag3

与第二问不同的是,该私钥文件前边缺失的地方更多,但观察后发现仍然可以得到 d mod p-1,d mod q-1,即 dp,然后便可以使用最常规的 dp 泄露的思路来做

1
2
3
4
n = 161097000312435047639025857083798608426453770394810689419872061300529854423940452844900091705515387130272189159935215821125906831946941166430461267964034092194438035823677086083564924149614045494613496178826123777949284989729443312266399276493423985154752448654420778684877265358368899693320567387749119123981
e = 65537
dp = 8466156897235505876858915949875848826731809609609498324537270201005685410904242916171703973272057335477219350238441206686126349368220272071006389601023573

脚本

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

c1 = bytes_to_long(open("flag1/flag1.txt", "rb").read())
c2 = bytes_to_long(open("flag2/flag2.txt", "rb").read())
c3 = bytes_to_long(open("flag3/flag3.txt", "rb").read())

n1 = 121216793759965406495195349784688471172903898464863919579528667261419201175006158506254851920269184174476441342645096664857617956346276850231302641370595849992364168925183323567715165305825214477546120105756089950903529561478112787635175307546002017263761103481685637785188859785795387227441771118141108993731
n2 = 88024091485722968131203129772830524881654529625144134588814340590011554124604316403258794718595058760192581465181932954405288276198255951037890441057834815115351239151027730304080110594116192807779817527469691548261644538550529011882499685981945213841298369305720161373509056672240371216670722254664850792423
n3 = 161097000312435047639025857083798608426453770394810689419872061300529854423940452844900091705515387130272189159935215821125906831946941166430461267964034092194438035823677086083564924149614045494613496178826123777949284989729443312266399276493423985154752448654420778684877265358368899693320567387749119123981
e = 65537

# flag1 中泄露 d
d1 = 24601440007954283409258790415889365626604738597756305511807235656867674669709582591386480696270815244895420087866738342299329789849425948470734950224610450629270896689735724164750653504067075012851069860052288413393492140397638874689747304249627737638521072321625292497180208183282090634794285367566044496049
m1 = pow(c1, d1, n1)
flag1 = long_to_bytes(m1)

# flag2 中泄露 p,q

p2 = 9582848127563543144923745964600370727631625906884933335707836734329263380236258927038536326818153720067174259502906373148203118612164064303294028202007639
q2 = 9185587657654265271925862790545538824407691820908394988893401857915219018271791875599781315096940084389655919586711021311553394520708985687587340820963057

phi2 = (p2-1)*(q2-1)
d2 = inverse(e, phi2)
m2 = pow(c2, d2, n2)
flag2 = long_to_bytes(m2)

# flag3 中泄露 dp,dq
dp = 8466156897235505876858915949875848826731809609609498324537270201005685410904242916171703973272057335477219350238441206686126349368220272071006389601023573

b = e*dp - 1
for i in range(1,e):
if b % i == 0:
pp = b // i + 1
if n3 % pp == 0:
p3 = pp
q3 = n3 // p3
break

phi3 = (p3 - 1) * (q3 - 1)
d3 = inverse(e, phi3)
m3 = pow(c3, d3, n3)
flag3 = long_to_bytes(m3)
print(flag1 + flag2 + flag3)

三重秘影-wgb

Hint:一个神秘的间谍组织使用了三重加密来隐藏他们的通信内容。你拦截到了一串奇怪密码线索,请注意他们的神秘标记!
还有一段密文:依稀只剩下:“Stone Memory For…?”,请解开此段密文,获取flag

分析

题目首先给出了一个 text.txt 文件,查看过后发现是 BASE64 编码后的结果,先进行 BASE64 的解码,发现开头字符为 PNG 图片的数据开头格式。

直接保存为 PNG 图片,打开后发现是一个二维码,扫码能得到一个字符串...--cfadeb-----,明显前后两个是摩斯密码,得到3cfadeb0

接着使用 010 Editor 打开该图片,往下翻找发现 JPG 的文件格式,并且在文件末尾找到一个字符串d96247f2167ba262a5d8325019623b035436a8be27f807516a76c231307994b0

再将 JPG 文件保存之后,得到一串摩斯密码

解密后得到9273016854,结合前边的两端也是摩斯密码,且刚好在下边这个图片中相邻,因此猜测第一个字符串应该插在第二个字符串中间,得到9273cfadeb016854
这两个字符串一长一短,很容易把它们当作一个密文一个密钥,但是不知道是什么加密。最后结合题目所说Stone Memory For...?,猜测加密算法为 SM4,9273cfadeb016854也刚好 16 个字节,直接 ECB 模式解密便得到 flag

脚本

1
2
3
4
5
6
7
8
9
10
11
from gmssl.sm4 import CryptSM4, SM4_DECRYPT

c = "d96247f2167ba262a5d8325019623b035436a8be27f807516a76c231307994b0"
key = b"9273cfadeb016854"
c_bytes = bytes.fromhex(c)

sm4 = CryptSM4()
sm4.set_key(key, SM4_DECRYPT)

flag = sm4.crypt_ecb(c_bytes)
print(flag)

MISC

Format-8bit-wgb

Hint:看不懂的汉字隐藏了什么秘密呢?

分析

题目给了一个 JPG 文件,010 Editor 打开查看十六进制数据,发现文件结尾存在着一个 Zip 压缩包,可以使用 Binwalk 直接分离

1
2
3
DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
351661 0x55DAD Zip archive data, at least v2.0 to extract, compressed size: 154, uncompressed size: 165, name: 422.txt

得到一个 422.txt 文件,查看发现文件内好像是非常复杂的汉字还有一些乱码,猜测是utf-16编码的字符,尝试过后发现是大端序 utf-16be,将其转换为字节流,然后再使用gbk解码便能得到flag

脚本

1
2
3
4
5
txt = open('422.txt', 'r', encoding='utf-8').read()

utf16be_bytes = b''.join(chr(ord(ch)).encode('utf-16be') for ch in txt)
flag = utf16be_bytes.decode('gbk', errors='ignore')
print(flag)

non-interlaced-wgb

Hint:神奇的图片有什么意义呢?

分析

题目给出了 9 张不同颜色的像素图片,分别为 3 张红色,3 张绿色,3 张蓝色,因此猜测和像素的 RGB 值有关,将图片放入 Stegsolve 进行分析。
图片 1 为例,发现在 Red 的每个通道上都存在很多数据

然后使用 Data Extract 对通道上的数据进行分析,勾选 Red 的所有通道,查看十六进制数据,发现是 Zip 的数据,但是并不完整,因此猜测后边的每个图片是否都带有一部分数据

为了验证猜测,直接查看 图片9 是否含有 Zip 的文件结尾

查看之后,说明确实如我们所假设的那样,因此我们便可以将每个图片中隐写的数据都提取出来,拼接在一起便可以得到一个 Zip 压缩包,解压之后得到 flag.jpg

流量分析

Spyware-wgb

Hint:情况紧急,小A使用了绝妙的隐藏方法传输了密码信息。

分析

附件中给出了一个 1.pcapng 的流量数据包,使用 Wireshark 打开查看发现是 USB 流量,猜测是否会是鼠标流量或者键盘流量。

但是我们并没有发现 usbhid,只发现了许多 Leftover Capture Data ,搜索之后发现是没有被认出的报文数据,因此它们本质上是一样的,只不过需要使用capdata将数据过滤出,然后使用 tshark 提取即可。

1
tshark -r 1.pcapng -T fields -e usb.capdata > 1.txt

观察提取出的数据,发现其数据的变换并不连续,因此猜测不是鼠标流量,而是键盘流量,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from Crypto.Util.number import long_to_bytes
hid = {
0x04: 'a', 0x05: 'b', 0x06: 'c', 0x07: 'd', 0x08: 'e', 0x09: 'f', 0x0A: 'g', 0x0B: 'h', 0x0C: 'i', 0x0D: 'j', 0x0E: 'k', 0x0F: 'l',
0x10: 'm', 0x11: 'n', 0x12: 'o', 0x13: 'p', 0x14: 'q', 0x15: 'r', 0x16: 's', 0x17: 't', 0x18: 'u', 0x19: 'v', 0x1A: 'w', 0x1B: 'x',
0x1C: 'y', 0x1D: 'z', 0x1E: '1', 0x1F: '2', 0x20: '3', 0x21: '4', 0x22: '5', 0x23: '6', 0x24: '7', 0x25: '8', 0x26: '9', 0x27: '0',
0x28: '\n',0x2C: ' '
}

flag = ""
with open("1.txt", "r", encoding="utf-8") as f:
for line in f:
if line.startswith("2000"):
key_code = line[8:10]
if key_code:
key = int(key_code, 16)
if key in hid:
flag += hid[key]

print(flag)
flag =flag.replace("m","0").replace("3","1")
print(long_to_bytes(int(flag,2)))


“网谷杯”安全极客大赛 Writeup
http://ramoor.github.io/2025/09/13/“网谷杯”安全极客大赛 Writeup/
作者
Ramoor
发布于
2025年9月13日
许可协议