第十六届蓝桥杯大赛全国总决赛网络安全赛道 2025

数据分析

server_logs

Challenge

某服务器在 2023-06-15 02:00-04:00 期间被入侵,请分析日志并回答:

  1. 攻击者使用的 SSH 用户名和 IP
  2. 植入的恶意服务名称
  3. 泄露机密文件时使用的 DNS 域名

提交形式:flag

Solution

/var/log/auth.log 的第 201 行找到攻击者使用的 SSH 用户名和 IP 分别是 attacker192.168.42.77

1
Jun 15 02:30:15 server sshd[5678]: Accepted password for attacker from 192.168.42.77 port 1337

/var/log/syslog 的第 501-502 行找到植入的恶意服务名称是 hidden_backdoor

1
2
Jun 15 02:35:15 server systemd[1]: Started hidden_backdoor.service
Jun 15 02:35:15 server hidden_backdoor: listening on [any] 31337 ...

/var/log/dnsmasq.log 的第 601-604 行找到泄露机密文件时使用的 DNS 域名为 data.leak.ev

1
2
3
4
Jun 15 02:40:15 dnsmasq[123]: query[A] CiAgICByb290Oio6MTk0Nzk6MDo5OTk5OTo3Ojo6.data.leak.ev from 192.168.42.77
Jun 15 02:40:17 dnsmasq[123]: query[A] CmRhZW1vbjoqOjE5NDc5OjA6OTk5OTk6Nzo6Ogph.data.leak.ev from 192.168.42.77
Jun 15 02:40:19 dnsmasq[123]: query[A] dHRhY2tlcjokNiRzZWNyZXQkZW5jcnlwdGVkcGFz.data.leak.ev from 192.168.42.77
Jun 15 02:40:21 dnsmasq[123]: query[A] c3dvcmQ6MTk0Nzk6MDo5OTk5OTo3Ojo6CiAgICA.data.leak.ev from 192.168.42.77

拼起来得到 flag

1
flag{attacker_192.168.42.77_hidden_backdoor_data.leak.ev}

flowzip2 [复现]

Challenge

There are many encrypted zip files.

Solution

从流量包导出 200 个加密方式为 AES 的.zip 压缩包

在压缩包的备注发现提示 \d{3},推测密码是三位数的数字,用 ARCHPR 爆一下发现还真是

写脚本尝试从 000 到 999 的三位数字密码来暴力破解并解压指定目录下名为 000.zip 至 199.zip 的加密压缩包

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
# -*- coding: utf-8 -*-

import os
import pyzipper
from tqdm import tqdm
import time

# --- 配置区 ---
# 使用原始字符串 (r"...") 来避免路径中的反斜杠问题
ZIP_DIRECTORY = r"E:\Desktop\flowzip2_fe9d6d2cf5a6fe83f1d7f0fe3c60fa25"
# --- 配置区结束 ---

def generate_passwords():
"""
生成所有可能的密码 (000, 001, ..., 999)
"""
# 使用 f-string 和 {:03d} 格式化来生成三位数的字符串,不足的前面补0
return [f"{i:03d}" for i in range(1000)]

def crack_zips():
"""
爆破和解压ZIP文件
"""
if not os.path.isdir(ZIP_DIRECTORY):
print(f"错误:目录'{ZIP_DIRECTORY}'不存在。请检查路径是否正确。")
return

# 在主函数开始时生成密码列表并打印提示
print("正在生成密码列表 (000-999)...")
passwords = generate_passwords()
print(f"密码列表生成完毕,共 {len(passwords)} 个。")
print(f"开始处理目录'{ZIP_DIRECTORY}'中的ZIP文件...\n")

# 使用 with 语句管理 tqdm 实例,确保进度条在结束或异常时能被正确关闭
# bar_format 自定义了进度条的显示格式,{l_bar} 是左边的描述文本
with tqdm(range(200),
desc="[+] 准备开始...",
bar_format="{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}]") as pbar:
for i in pbar:
file_number_str = f"{i:03d}"
zip_filename = f"{file_number_str}.zip"
zip_filepath = os.path.join(ZIP_DIRECTORY, zip_filename)

if not os.path.exists(zip_filepath):
# 更新描述信息,提示文件不存在
pbar.set_description(f"[-] {zip_filename}不存在,已跳过")
continue

found_password = False
# 遍历所有可能的密码
for password in passwords:
try:
# 尝试用当前密码打开和解压ZIP文件
# 密码需要被编码成bytes
with pyzipper.AESZipFile(zip_filepath, 'r') as zf:
zf.extractall(path=ZIP_DIRECTORY, pwd=password.encode('utf-8'))

# 密码正确,更新tqdm的描述信息
# 这会覆盖上一条信息,实现动态更新的效果
pbar.set_description(f"[+] {zip_filename}已解压,密码是'{password}'")
found_password = True
break # 密码已找到,跳出内部的密码循环

except RuntimeError as e:
# pyzipper在密码错误时会抛出RuntimeError
if 'Bad password' in str(e):
continue # 密码不对,继续尝试下一个密码
else:
# 其他运行时错误,更新描述并跳出密码循环
pbar.set_description(f"[!] 处理{zip_filename}时发生运行时错误")
break

except Exception:
# 捕获其他可能的异常
pbar.set_description(f"[!] 处理{zip_filename}时发生意外错误")
break

if not found_password:
# 如果遍历完所有密码都未成功,更新描述信息
pbar.set_description(f"[-] 未找到{zip_filename}的密码")

# 在所有文件处理完毕后,光标移到新的一行,并打印最终的完成信息
print("\n--- 所有文件处理完毕 ---")

if __name__ == "__main__":
crack_zips()

最后在 138.zip(解压密码是 853)中的 dcsxr.txt 找到 flag

1
flag{5f5491b6-fddf-4be8-ab44-5a18831cc45b}

密码破解

xxtea

Challenge

It’s getting hard to even copy the data this time.

Solution

没啥好说的,照猫画虎就是了 From Hexdump, XXTEA Decrypt - CyberChef

1
2
3
4
5
6
7
8
00000000 9e 45 02 82 ea 25 d2 62 0d 06 e7 b4 5f dc 62 bd |.E...%.b...._.b.|
00000010 39 68 47 26 54 50 9e 26 4c 86 06 2e 2b af 3b 8b |9hG&TP.&L...+.;.|
00000020 7c e4 99 0d ad 12 e2 7e 46 c9 00 29 a6 ea 1f b0 ||......~F..)....|
00000030 ae c1 da 30 f3 98 94 82 33 fd 99 d3 d3 b9 a0 12 |...0....3.......|
00000040 d9 de a5 d2 0e 95 f3 a5 bb f2 f9 91 b2 a3 c2 94 |................|
00000050 b1 1c b7 eb 87 65 d7 0e 0c 6b d5 65 4d e1 43 1e |.....e...k.eM.C.|
00000060 be b4 34 71 b5 53 d4 ea 62 4e b9 80 f3 6f f0 5c |..4q.S..bN...o.\|
00000070 59 75 58 52 9e a4 ec e2 35 b7 d8 68 27 ee c0 94 |YuXR....5..h'...|
1
flag{4eb88a16-be48-4de2-ab2a-ed09a09ed386}

fastcoll

Challenge

这是一个 MD5 碰撞挑战:你能找到两个不同文件,但却拥有相同的 MD5 值吗?

Solution

题目要求有相同前缀 gamelab,因此先在当前目录下新建内容为 gamelab 的文件 prefix.txt

然后运行得到两个文件

1
fastcoll_v1.0.0.5.exe -p prefix -o msg1.bin msg2.bin

用厨子分别编码一下然后交上去就行

1
Z2FtZWxhYgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgJW38EhH3hRbFIaBQ1pJrGjBHm7YrS5TlmOI0uqIkmIeLjwF5kFHpLMMBS9qHZu8CskXE6WmVGDv5KV/BJywYUt9xfoI2x/8k8ldPje4Yyx6N3WzwZgr8tsyDXKmCoxtcr9AOvCI4e+v6BkkqwGwu2OQlPh8TpgdH0Mq0Cic8m
1
Z2FtZWxhYgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgJW38EhH3hRbFIaBQ1pJrGjBFm7YrS5TlmOI0uqIkmIeLjwF5kFHpLMMBS9iHau8CskXE6WmVGDv5K1/BJywYUt9xfoI2x/8k8ldPje4Yyx6N32zwZgr8tsyDXKmCoxtcr9AOvCI4e+v6BkkowGwu2OQlPh8TpgdH0Mi0Cic8m

逆向分析

encodefile [复现]

Challenge

分析一个用于加密明文的可执行程序,通过识别其加密逻辑成功解密生成的密文文件,恢复出原始数据内容。

Solution

这个程序的核心功能是读取 flag.txt,使用 RC4 加密算法对其内容进行加密,然后将加密后的结果写入一个名为 enc.dat 的文件

加密所使用的密钥是硬编码在程序中的字符串 key2025lqb

找到主函数 sub_40487D 并分析

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
__int64 sub_40487D()
{
_QWORD *v1; // rax
unsigned int v2; // ebx
__int64 v3; // rbx
__int64 v4; // rax
_QWORD *v5; // rax
__int64 result; // rax
char v7; // [rsp+Fh] [rbp-481h] BYREF
__int64 v8[2]; // [rsp+10h] [rbp-480h] BYREF
__int64 v9[2]; // [rsp+20h] [rbp-470h] BYREF
_QWORD v10[4]; // [rsp+30h] [rbp-460h] BYREF
_QWORD v11[4]; // [rsp+50h] [rbp-440h] BYREF
_QWORD v12[31]; // [rsp+70h] [rbp-420h] BYREF
__int64 v13; // [rsp+168h] [rbp-328h] BYREF
_QWORD v14[32]; // [rsp+270h] [rbp-220h] BYREF
__int64 v15; // [rsp+370h] [rbp-120h] BYREF
unsigned __int64 ______Stack_Canary_; // [rsp+478h] [rbp-18h]

______Stack_Canary_ = __readfsqword(0x28u);
ifstream((__int64)v14, (__int64)"flag.txt", 4);// 读取 "flag.txt"
ofstream((__int64)v12, (__int64)"enc.dat", 4);// 写入 "enc.dat"
if ( sub_44FB80((__int64)&v15) || sub_44FB80((__int64)&v13) )// 检查文件是否成功打开
{
v1 = sub_46F140(qword_5DD320, (__int64)&unk_578019);
sub_46DC30((__int64)v1, (__int64 (*)(void))sub_46EA20);// 如果文件打开失败,打印错误信息并退出
v2 = 1;
}
else // 如果文件打开成功,执行核心加密逻辑
{
sub_407080();
sub_404E42((__int64)v9);
sub_404DF8((__int64)v8, v14); // 从文件流 v14 ("flag.txt")读取数据到 v8
sub_404E6C((__int64)v10, v8[0], v8[1], v9[0], v9[1], (__int64)&v7);// 再将数据从 v8 转移到 v10
sub_4070A0();
sub_407080();
sub_404F50((__int64)v11, (__int64)"key2025lqb", (__int64)v9);// v11 是密钥结构,使用 "key2025lqb" 进行初始化
sub_404605((__int64)v10, (__int64)v11); // 使用密钥 v11 对数据 v10 进行 RC4 加密
sub_475510(v11);
sub_4070A0();
v3 = sub_404DB4(v10);
v4 = sub_405014(v10);
sub_46E890(v12, v4, v3); // 将数据 (v4, v3) 写入文件流 v12 ("enc.dat")
v5 = sub_46F140(qword_5DD440, (__int64)&unk_578040);
sub_46DC30((__int64)v5, (__int64 (*)(void))sub_46EA20);
v2 = 0;
sub_404F08(v10);
}
sub_44C300(v12);
sub_44C440(v14);
result = v2;
if ( ______Stack_Canary_ != __readfsqword(0x28u) )
sub_52FBA0();
return result;
}

分析加密函数 sub_404605

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
unsigned __int64 __fastcall sub_404605(_QWORD *a1, __int64 a2)
{
int v2; // r12d
unsigned __int64 v3; // rax
__int64 v4; // kr00_8
_BYTE *v5; // rax
unsigned __int64 result; // rax
char v7; // [rsp+17h] [rbp-139h]
int i; // [rsp+18h] [rbp-138h]
int v9; // [rsp+1Ch] [rbp-134h]
int v10; // [rsp+1Ch] [rbp-134h]
int j; // [rsp+20h] [rbp-130h]
int v12; // [rsp+24h] [rbp-12Ch]
unsigned __int64 k; // [rsp+28h] [rbp-128h]
_BYTE v14[264]; // [rsp+30h] [rbp-120h] BYREF
unsigned __int64 v15; // [rsp+138h] [rbp-18h]

v15 = __readfsqword(0x28u);
for ( i = 0; i <= 255; ++i ) // 初始化S盒
v14[i] = i;
v9 = 0;
for ( j = 0; j <= 255; ++j ) // 打乱S盒
{
v2 = (unsigned __int8)v14[j] + v9;
v3 = sub_475730(a2);
v9 = (v2 + *(char *)sub_475A40(a2, j % v3)) % 256;
sub_404D3D(&v14[j], &v14[v9]); // swap(S[i], S[j])
}
v12 = 0; // i
v10 = 0; // j
for ( k = 0LL; k < sub_404DB4(a1); ++k )
{
v12 = (v12 + 1) % 256; // i = (i + 1) % 256
v4 = (unsigned __int8)v14[v12] + v10;
v10 = (unsigned __int8)(HIBYTE(v4) + v14[v12] + v10) - HIBYTE(HIDWORD(v4));// j = (j + S[i]) % 256
sub_404D3D(&v14[v12], &v14[v10]); // swap(S[i], S[j])
v7 = v14[(unsigned __int8)(v14[v12] + v14[v10])];// 生成密钥流中的一个字节
v5 = (_BYTE *)sub_404DD8(a1, k); // 获取第 k 个明文字节的地址
*v5 ^= v7; // XOR
}
result = v15 - __readfsqword(0x28u);
if ( result )
sub_52FBA0();
return result;
}

发现这是标准的 RC4,密钥是硬编码在主函数中的 key2025lqb,用厨子解就行 RC4 - CyberChef

1
flag{db6007d2-9b1e-2f98-cef3-6595b63763dd}

rand_pyc

Challenge

对由 Python 打包生成的 exe 文件进行逆向处理,提取并还原出其核心源码,以便进一步分析程序逻辑并获得正确的输入。

Solution

解包反编译拿到代码

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
# Source Generated with Decompyle++
# File: rand_pyc_obf.pyc (Python 3.8)

import sys
import random
import base64
Ii = input('Please input the flag: ').strip()
if not Ii.startswith('flag{') and Ii.endswith('}') or len(Ii) == 42:
print('Length incorrect')
sys.exit(-999)
oo0O000ooO = base64.b64encode(Ii.encode()).decode() + '_easyctf'
ii = []
for iiI in oo0O000ooO:
random.seed(ord(iiI))
ii.append(random.randint(1000000, 9999999))
iii111 = [
4417023,
5690625,
9639225,
1327718,
4417023,
5085550,
5752075,
9556690,
5240080,
6431679,
3428007,
3189766,
3438336,
5757818,
3189766,
5690625,
4148389,
2254831,
6292433,
2122126,
5240080,
6431679,
9488271,
2464675,
7216908,
5757818,
3189766,
5690625,
3438336,
6431679,
2360475,
6002055,
5240080,
9040261,
8655414,
9347278,
3438336,
2254831,
2122126,
5135281,
2360475,
9347278,
4417023,
1327718,
3438336,
3448715,
9488271,
5501611,
5240080,
5757818,
9488271,
5501611,
5240080,
9347278,
4148389,
1714134,
9923116,
4267438,
4263793,
5752075,
2464675,
7777627,
6002055,
3485900]
Iio0 = []
for iiI in oo0O000ooO:
random.seed(ord(iiI))
Iio0.append(random.randint(1000000, 9999999))
if Iio0 != iii111:
print('Wrong flag')
sys.exit(-1)
print('Correct!')

程序将输入的内容 base64 编码在尾部添加字符串_easyctf,然后分别将这些字符作为伪随机数的种子后生成一个数

由于种子是知道的因此这些随机数也是知道的,用 base64 的表制作一个对应的伪随机数的表,然后查表还原出密文,最后解码 base64 即可

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
import random
import base64

iii111 = [
4417023,
5690625,
9639225,
1327718,
4417023,
5085550,
5752075,
9556690,
5240080,
6431679,
3428007,
3189766,
3438336,
5757818,
3189766,
5690625,
4148389,
2254831,
6292433,
2122126,
5240080,
6431679,
9488271,
2464675,
7216908,
5757818,
3189766,
5690625,
3438336,
6431679,
2360475,
6002055,
5240080,
9040261,
8655414,
9347278,
3438336,
2254831,
2122126,
5135281,
2360475,
9347278,
4417023,
1327718,
3438336,
3448715,
9488271,
5501611,
5240080,
5757818,
9488271,
5501611,
5240080,
9347278,
4148389,
1714134,
9923116,
4267438,
4263793,
5752075,
2464675,
7777627,
6002055,
3485900]

list1 = list("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890+/=_")

list2 = []
for guess_flag in list1:
random.seed(ord(guess_flag))
list2.append(random.randint(1000000, 9999999))
print(list2)

flag_list = []
for i in iii111:
if i in list2:
flag_list.append(list1[list2.index(i)])
else:
print(i)
flag = "".join(flag_list[:-8])
print(base64.b64decode(flag))
1
flag{30de99f4-50d2-9f8f-2868-dcfa9d81483c}