文章总结: 本文是香港人工智能网络安全挑战赛Misc类题目的解题报告。核心涵盖RAID5阵列降级恢复与数据库提取、NTFS镜像中ZIP文件头的手动搜索与碎片重组,以及基于步长隐写和GloVe词向量模型的类比解密技术。为参赛者提供了磁盘恢复与AI安全交叉领域的实用操作指南。 综合评分: 88 文章分类: CTF,AI安全,数据安全,实战经验
Al%20x%20Cybersecurity%20Challenge香港人工智能网络安全挑战赛wp(Miscellaneous全)
赛查查
2026年7月15日%2012:45 北京
在小说阅读器读本章
去阅读
以下文章来源于玫家大院 ,作者玫幽倩
玫家大院 .
希望我们所有人都越来越好,博客更新更快https://mei-you-qian.github.io/,合作私聊s2764174229
快一个月没更新了,小发一篇嘿嘿
这套题的misc还是蛮有意思的,感觉蛮好玩的,像RAID%205、词向量模型之类的,稍微写了一下,misc的wp已经手搓复盘过了,别的部分ai做了还没手动复盘,就先不发了
Miscellaneous
database
题目描述:
某公司的RAID%205业务服务器备份只保留下部分数据。请检查残留证据,尽可能还原有用内容,并找出隐藏的%20flag。
题目说的很明确了这个服务器采用的是RAID%205,而且是只保留下部分数据,看起来是一道RAID%205恢复的题目
应该是先恢复RAID%205阵列,再进入文件系统找flag
那在找之前我们自然是先要搞明白什么是RAID%205阵列
RAID,Redundant%20Array%20of%20Independent%20Disks,即独立磁盘冗余阵列,我们一般称之为磁盘阵列
其实本质上就是用多个独立的磁盘组成在一起形成一个大的磁盘系统,从而实现比单块磁盘更好的存储性能和更高的可靠性
而RAID5正是其中一种排列方式,实际上,这是目前用的最多的一种磁盘阵列排列方式,因为其兼顾了存储性能、数据安全和存储成本
其排布大致如下
RAID%205至少需要3块硬盘
数据会被分成若干数据块,分别写入不同硬盘;同时,系统会计算一份用于恢复数据的校验信息%20Parity,并将校验信息分散存储在各块硬盘中。
例如有%203%20块硬盘:
校验块不会固定放在某一块硬盘上,而是轮流分布,这样可以避免某一块专门负责校验的硬盘成为性能瓶颈
那如何恢复数据呢,RAID%205一般采用的是XOR技术
例如%20A%20XOR%20B%20=%20P%20存进去的
那么如果A的数据坏了,只要%20P%20XOR%20B%20=%20A%20就能把A恢复出来
所以只要同时坏的不超过1块,RAID%205通常仍然可以继续读取数据
好来看题来
可以看到题目给了两块镜像
file%20disk1.img
file%20disk2.img
检查文件类型可以直接发现是三块盘搭的RAID%205,但是只给了两块,尝试降级恢复
看看具体的RAID元数据,我们一般在Linux直接用mdadm来处理RAID
先把这两个镜像变成loop设备,因为RAID的成员盘一般不能直接挂载,我们先建立loop设备,其实就是把.img文件给模拟成一块磁盘,%20给它分配一个/dev/loopX设备
sudo%20losetup%20-fP%20disk1.img
sudo%20losetup%20-fP%20disk2.img
查看%20loop%20设备对应关系:
losetup%20-a
建立好loop设备后就可以用mdadm来看元数据了
sudo%20mdadm%20--examine%20/dev/loop0
sudo%20mdadm%20--examine%20/dev/loop1
可以看到关键信息,这两块分别是role%200和role%201,即第一块成员盘和第二块成员盘
我们缺失了role%202,第三块成员盘
注意这一步是必须的,因为我们得知道这俩分别是哪一块成员盘,才能按条带规则来重组RAID
由于缺失了一块盘,所以我们需要降级模式来组装RAID
sudo%20mdadm%20--assemble%20--readonly%20--run%20/dev/md0%20/dev/loop0%20/dev/loop1
各参数大概是下边这些意思
--assemble%20组装已有%20RAID%20阵列
--readonly%20只读方式组装,避免搞坏了
--run%20即使阵列不完整,也尝试启动
/dev/md0%20生成的%20RAID%20设备名
/dev/loop0%20第一个%20RAID%20成员
/dev/loop1%20第二个%20RAID%20成员
cat%20/proc/mdstat
即可看见RAID的状态,这边的[UU_]中U表示正常存在的成员盘,_表示缺失的成员盘
通过%20RAID%20规则把成员盘重新组合起来后,我们终于得到了完整的%20ext4%20文件系统
开始挂载就好了
sudo%20mkdir%20-p%20/mnt/raid_recover
sudo%20mount%20-o%20ro%20/dev/md0%20/mnt/raid_recover
创建一下挂载目录然后直接只读挂载
cd过去,发现成功恢复,我们已经进入了原文件系统
之后就是找东西环节了,题目说是数据,我们可以搜一下
sudo%20find%20/mnt/raid_recover%20-type%20f%20|%20grep%20-iE%20'db|sqlite|database'
这个%20/var/lib/companydb/important.db的数据库明显很可疑
打开看看
sqlite3%20/mnt/raid_recover/var/lib/companydb/important.db
.tables看看表名,就发现个此地无银三百两的表
我们直接全部拉出来看看
SELECT%20*%20FROM%20flag_not_here;
给了四个flag,不过仔细看看就能发现只有最后一个是对的
所以本题答案为flag{raid5_recovery_1s_1mportant}
ntfs_dump
题目描述:
某员工将数据隐藏到bin文件中,请尽可能还原数据,并找出隐藏的flag,flag格式为FLAG{xxx}。
和题目描述一模一样啊,只给了一个bin文件,说是把数据隐藏进去了,让我们还原
先file看看文件格式,发现被识别为了NTFS的启动扇区,看起来像是个NTFS文件系统镜像
但是扔到Xways报错了,似乎不是一个正常的NTFS镜像
不过BPB似乎没有损坏,我们还是能看到一些基础参数
可以看到这边每扇区字节数是00%2002,小端序存储,所以应该是0x0200,即512字节
每簇是8个扇区,所以是每簇8*512=4096字节
扇区总数是0x4000,即16384(小端序
$MFT起始簇号为4(小端序
不过其实Xways的技术报告可以直接分析出就是了
偏移MFT%20起始簇号%20×%20簇大小
所以$MFT%20偏移应该是16384,即0x4000
正常情况下准备按应该能看到FILE,但是现在这里塞了一大堆的伪造文件头,这会导致binwalk一类的工具无法检测
显然,我们必须手动搜索些有用的才行,那一般这种数据恢复的题目都会放到压缩包里作为载体,所以我们可以优先对于ZIP的几个主要结构十六进制进行搜索,主要是下边这三个
PK\x03\x04%20Local%20File%20Header,本地文件头
PK\x01\x02%20Central%20Directory,中央目录
PK\x05\x06%20End%20of%20Central%20Directory,中央目录结束记录
当然,其中中央目录当然是最主要的,里边会记录很多东西
from%20pathlib%20import%20Path
data%20=%20Path("ntfs_dump.bin").read_bytes()
for%20sig%20in%20[
%20 %20b"PK\x03\x04",
%20 %20b"PK\x01\x02",
%20 %20b"PK\x05\x06",
%20 %20b"text.txt",
]:
%20 %20pos%20=%20[]
%20 %20start%20=%200
%20 %20while%20True:
%20 %20 %20 %20i%20=%20data.find(sig,%20start)
%20 %20 %20 %20if%20i%20==%20-1:
%20 %20 %20 %20 %20 %20break
%20 %20 %20 %20pos.append(i)
%20 %20 %20 %20start%20=%20i%20+%201
%20 %20print(sig,%20len(pos),%20[hex(x)%20for%20x%20in%20pos[:10]])
发现了虽然 PK\x03\x04 有很多个,但是 PK\x01\x02 和 PK\x05\x06 都只出现了一次
这说明有一个0x5b735附近的zip很有可能是真的
探查一下基本信息
from%20pathlib%20import%20Path
import%20struct
data%20=%20Path("ntfs_dump.bin").read_bytes()
cd_off%20=%200x5b735
fields%20=%20struct.unpack_from("<4s6H3I5H2I",%20data,%20cd_off)
(
%20 %20sig,
%20 %20ver_made,
%20 %20ver_need,
%20 %20flag,
%20 %20method,
%20 %20mtime,
%20 %20mdate,
%20 %20crc,
%20 %20csize,
%20 %20usize,
%20 %20name_len,
%20 %20extra_len,
%20 %20comment_len,
%20 %20disk,
%20 %20int_attr,
%20 %20ext_attr,
%20 %20local_header_off,
)%20=%20fields
name%20=%20data[cd_off%20+%2046%20:%20cd_off%20+%2046%20+%20name_len]
print("filename:",%20name)
print("crc:",%20hex(crc))
print("compressed%20size:",%20csize)
print("uncompressed%20size:",%20usize)
print("method:",%20method)
print("local%20header%20offset:",%20hex(local_header_off))
根据各种偏移解析一下中央目录,得到了一些信息
重点可以看到压缩方法是0,即stored,不压缩,直接存的
所以压缩数据长度和原始数据长度相同
搜索一下text.txt
发现有两个地方都有
我们知道text.txt是8字节,所以它后面的数据起点应该是0x5910ae%20+%208%20=%200x5910b6
也就是说,第一段疑似文件内容从这里开始:
0x5910b6
继续往后看,会发现数据到某个位置突然变成大量假文件头模式
假文件头开始位置是0x591911
所以第一段真实数据范围是0x5910b6%20~%200x591911
计算长度0x591911%20–%200x5910b6%20=%200x85b%20=%202139
但是中央目录告诉我们,text.txt 应该有%204240%20字节。
还差2101字节,即0x835字节呢
不在这边,会不会是和中央目录连在一起呢?
我们回去继续观察%20ZIP%20中央目录
中央目录从0x5b735开始,往前取 2101 字节看看,就是0x5af00%20~%200x5b735这个范围
我们只要验证这两段拼起来的%20CRC32%20是否等于中央目录里的0x2bcce130即可确认正确与否其实
from%20pathlib%20import%20Path
import%20zlib
data%20=%20Path("ntfs_dump.bin").read_bytes()
part1%20=%20data[0x5910b6:0x591911]
part2%20=%20data[0x5af00:0x5b735]
payload%20=%20part1%20+%20part2
print("part1%20length:%20 %20 %20",%20len(part1))
print("part2%20length:%20 %20 %20",%20len(part2))
print("total%20length:%20 %20 %20",%20len(payload))
print("crc32:%20 %20 %20 %20 %20 %20 ",%20hex(zlib.crc32(payload)%20&%200xffffffff))
发现合起来还真的crc32一样,确认了就是这个
我们开始尝试自己创建一个zip,缺一个文件头,我们尝试按中央目录重建本地文件头
from%20pathlib%20import%20Path
import%20struct
import%20zlib
import%20zipfile
dump%20=%20Path("ntfs_dump.bin").read_bytes()
#%20------------------------------------------------------------
#%201.%20ZIP%20中央目录和%20EOCD%20的位置
#%20------------------------------------------------------------
cd_off%20=%200x5b735
eocd_off%20=%200x5b76b
#%20------------------------------------------------------------
#%202.%20解析中央目录
#%20------------------------------------------------------------
fields%20=%20struct.unpack_from("<4s6H3I5H2I",%20dump,%20cd_off)
(
%20 %20sig,
%20 %20ver_made,
%20 %20ver_need,
%20 %20flag,
%20 %20method,
%20 %20mtime,
%20 %20mdate,
%20 %20crc,
%20 %20csize,
%20 %20usize,
%20 %20name_len,
%20 %20extra_len,
%20 %20comment_len,
%20 %20disk,
%20 %20int_attr,
%20 %20ext_attr,
%20 %20local_header_off,
)%20=%20fields
filename%20=%20dump[cd_off%20+%2046%20:%20cd_off%20+%2046%20+%20name_len]
print("[+]%20filename:%20 %20 %20 %20 %20 %20",%20filename.decode())
print("[+]%20crc:%20 %20 %20 %20 %20 %20 %20 %20 ",%20hex(crc))
print("[+]%20compressed%20size:%20 %20 ",%20csize)
print("[+]%20uncompressed%20size:%20 ",%20usize)
print("[+]%20method:%20 %20 %20 %20 %20 %20 %20",%20method)
#%20------------------------------------------------------------
#%203.%20恢复%20text.txt%20的两段真实数据
#%20------------------------------------------------------------
part1%20=%20dump[0x5910b6:0x591911]
part2%20=%20dump[0x5af00:0x5b735]
payload%20=%20part1%20+%20part2
assert%20len(payload)%20==%20csize
assert%20(zlib.crc32(payload)%20&%200xFFFFFFFF)%20==%20crc
print("[+]%20payload%20recovered")
print("[+]%20payload%20length:%20 %20 %20",%20len(payload))
print("[+]%20payload%20crc32:%20 %20 %20 ",%20hex(zlib.crc32(payload)%20&%200xFFFFFFFF))
#%20------------------------------------------------------------
#%204.%20构造%20Local%20File%20Header
#%20------------------------------------------------------------
local_header%20=%20(
%20 %20struct.pack(
%20 %20 %20 %20"<4s5H3I2H",
%20 %20 %20 %20b"PK\x03\x04",
%20 %20 %20 %20ver_need,
%20 %20 %20 %20flag,
%20 %20 %20 %20method,
%20 %20 %20 %20mtime,
%20 %20 %20 %20mdate,
%20 %20 %20 %20crc,
%20 %20 %20 %20csize,
%20 %20 %20 %20usize,
%20 %20 %20 %20name_len,
%20 %20 %20 %200,
%20 %20)
%20 %20+%20filename
)
#%20------------------------------------------------------------
#%205.%20拷贝原始中央目录和%20EOCD
#%20------------------------------------------------------------
central_directory%20=%20dump[cd_off%20:%20cd_off%20+%200x36]
eocd%20=%20dump[eocd_off%20:%20eocd_off%20+%2022]
recovered_zip%20=%20local_header%20+%20payload%20+%20central_directory%20+%20eocd
Path("recovered.zip").write_bytes(recovered_zip)
print("[+]%20recovered.zip%20written")
#%20------------------------------------------------------------
#%206.%20测试%20ZIP%20是否可以正常读取
#%20------------------------------------------------------------
with%20zipfile.ZipFile("recovered.zip",%20"r")%20as%20z:
%20 %20print("[+]%20zip%20files:",%20z.namelist())
%20 %20text%20=%20z.read("text.txt")
%20 %20Path("text.txt").write_bytes(text)
%20 %20print("[+]%20text.txt%20extracted:",%20len(text),%20"bytes")
得到了恢复的压缩包和text.txt,但是发现不是明文,还需要提取才行
注意到这个数据的大小是4240字节,4240%20=%2016%20*%20265
猜测可能有固定步长隐写,可能是每%2016%20字节藏%201%20字节真实数据,但是步长和偏移不知道,我们可以爆破看看
from%20pathlib%20import%20Path
import%20re
data%20=%20Path("text.txt").read_bytes()
for%20step%20in%20range(2,%2065):
%20 %20for%20offset%20in%20range(step):
%20 %20 %20 %20stream%20=%20data[offset::step]
%20 %20 %20 %20m%20=%20re.search(rb"FLAG\{[^}]+\}",%20stream)
%20 %20 %20 %20if%20m:
%20 %20 %20 %20 %20 %20print("[+]%20found")
%20 %20 %20 %20 %20 %20print("step%20 %20=",%20step)
%20 %20 %20 %20 %20 %20print("offset%20=",%20offset)
%20 %20 %20 %20 %20 %20print("flag%20 %20=",%20m.group().decode())
发现确实是每%2016%20字节藏%201%20字节真实数据
得到了本题的flag
FLAG{DE6DDD7A-5D57-415A-99D7-6C0CE3F688BC}
Riddler
题目描述:
A%20misplaced%20note%20is%20full%20of%20strange%20phrases%20and%20misleading%20remarks.%20The%20real%20message%20is%20hidden%20among%20relationships%20that%20look%20accidental%20at%20first%20glance.%20Can%20you%20separate%20the%20signal%20from%20the%20noise?
一份放错地方的笔记中充满了奇怪的短语和误导性的描述。真正的信息藏在乍看偶然的关系之中,你能从噪声里找出线索吗?
给的附件很直接啊,一个有flag的压缩包和一个秘密的txt,这txt应该就是题目里那个充满了奇怪的短语和误导性描述的笔记了
打开可以看到,的确和题目说的一样,充满了噪声,而线索正藏在这里边
不难定位到关键是中间那部分
بعدكـ%20is%20to%20رباعيه,%20?%20is%20to%20love-buzz
الْحي%20is%20to%20toyota,%20?%20is%20to%20الغيـرة
feriass%20is%20to%20dham,%20?%20is%20to%20much
ميسيnn%20is%20to%20دعواتكمn,%20?%20is%20to%20were
يصيير%20is%20to%20newt,%20?%20is%20to%20ta
لأمانة%20is%20to%20roan,%20?%20is%20to%20viral
elain%20is%20to%20omán,%20?%20is%20to%20dy
一眼看过去的话像是什么类比题A%20is%20to%20B,%20?%20is%20to%20C,当然我们也可以写作
A%20:%20B%20=%20?%20:%20C
笔记里已经帮我们排除了很多可能了,不是频率分析,不是进制转化,更不是翻译隐藏的,是需要我们找到其中的关系,忽略那些重复的意思,找到这些词的关系
这题需要注意题目和文件里给的那些关键词
unfinished%20language%20experiment
strange%20scripts
not%20direct%20translation
file%20appears%20too%20short
这些词指向了词向量,尤其是专门强调了文件太短,too%20short,专门说了这也是解密的一部分,其实也是一种提示,提示我们是短维度的模型
所以这题的考点其实是词向量类比,%20word%20embedding%20analogy
词向量是什么呢,可以理解为是某些语义关系可以通过向量加减体现出来
有点难以理解吧,我们可以举个例子先,比如king%20–%20man%20+%20woman%20≈%20queen
为什么词可以变成向量?
因为一个词的含义可以由其上下文决定,比如king和queen经常在一个上下文,computer就离这俩比较远
这就必须引入GloVe这个词向量模型的概念了
它的作用就是把一个词变成一串数字,也就是一个向量,注意这些数字不是乱来的,而是这个模型通过大量文本训练出来的
那本题中的格式是A%20:%20B%20=%20?%20:%20C
我们可以理解为,如果关系是从 A 到 B,那么同样的关系也应该从 ? 到 C
也就是说?%20=%20B%20–%20A%20+%20C
所以每一行都要计算这个式子才行
vector(?)%20=%20vector(B)%20-%20vector(A)%20+%20vector(C)
最后我们还要在词向量模型中找最接近这个向量的词
那题目已经提示了,这一个词向量模型应该是一个短维度的模型
这个可能比较难想吧,或许也需要尝试,多亏了我们队伍的Jerry刚刚做过类似的题目,一试就出来了是%20glove-twitter-25%20模型,即Twitter语料上的%2025%20维%20GloVe%20词向量模型
最后直接写代码就好了
import%20gensim.downloader%20as%20api
model%20=%20api.load("glove-twitter-25")
rows%20=%20[
%20 %20("بعدكـ",%20"رباعيه",%20"love-buzz"),
%20 %20("الْحي",%20"toyota",%20"الغيـرة"),
%20 %20("feriass",%20"dham",%20"much"),
%20 %20("ميسيnn",%20"دعواتكمn",%20"were"),
%20 %20("يصيير",%20"newt",%20"ta"),
%20 %20("لأمانة",%20"roan",%20"viral"),
%20 %20("elain",%20"omán",%20"dy"),
]
result%20=%20[]
for%20a,%20b,%20c%20in%20rows:
%20 %20word,%20score%20=%20model.most_similar(
%20 %20 %20 %20positive=[b,%20c],
%20 %20 %20 %20negative=[a],
%20 %20 %20 %20topn=1
%20 %20)[0]
%20 %20print(f"{b}%20-%20{a}%20+%20{c}%20=%20{word}%20 score={score}")
%20 %20result.append(word)
password%20=%20"".join(result)
print("password%20=",%20password)
这边要进行词向量的运算自然少不了Gensim库,这主要是拿来处理词向量、加载词向量模型的,在这边我们就可以让其自动下载并加载我们要用的这个短维的词向量模型
得到结果:
|%20行%20|%20运算%20|%20最近词%20| |%20—%20|%20—%20|%20—%20| |%201%20|%20رباعيه%20–%20بعدكـ%20+%20love-buzz%20|%20ctf%20| |%202%20|%20toyota%20–%20الْحي%20+%20الغيـرة%20|%20maybe%20| |%203%20|%20dham%20–%20feriass%20+%20much%20|%20you%20| |%204%20|%20دعواتكمn%20–%20ميسيnn%20+%20were%20|%20are%20| |%205%20|%20newt%20–%20يصيير%20+%20ta%20|%20ai%20| |%206%20|%20roan%20–%20لأمانة%20+%20viral%20|%20master%20| |%207%20|%20omán%20–%20elain%20+%20dy%20|%20ado%20|
拼接就能得到密码了
ctfmaybeyouareaimasterado
最后直接拿密码打开压缩包即可
得到flag
flag{39fc0ee0f9da4fa397631f0dcba31555}
Mysterious%20Code
题目描述:
一次%20IDS%20告警留下了BASE58的可疑流量。请分析这些痕迹,还原隐藏内容并找出其中的秘密。
只给了一份流量包,题目给的描述明显是先分析流量,然后可能涉及BASE58编码,最后要我们还原隐藏内容才行
wireshark打开一看发现是大量的DNS流量
而且DNS查询的域名格式非常类似
0-GRSXGZ2UK5YGQSSSORMTCVDHLJWXKNCQ.hackg.fun
1-NBTHIQTHKZTXKRL2IZRUE4DVHFLDSRDT.hackg.fun
2-OVATQZDRPB2VKNDEKM3E6TSEIZSWUWKX.hackg.fun
...
EOF-f1b662512c1fcbfc.hackg.fun
通过观察不难发现这些域名的格式都是统一的”序号-数据.hackg.fun”的格式
可以看到最后还有EOF
这说明DNS查询很可能被用来传输隐藏数据,攻击者利用域名字段携带文本来传输
由于单个域名长度有限所以拆成了很多片,前边的序号就是排序的
所以我们要按序号提取这些分片的内容才行
from%20scapy.all%20import%20rdpcap,%20DNSQR
from%20collections%20import%20defaultdict,%20Counter
from%20pathlib%20import%20Path
import%20re
pcap%20=%20Path("Mysterious_Code.pcapng")
pkts%20=%20rdpcap(str(pcap))
pat%20=%20re.compile(rb"^(\d+)-([A-Za-z0-9]+)\.hackg\.fun\.?$",%20re.I)
chunks%20=%20defaultdict(list)
for%20pkt%20in%20pkts:
%20 %20if%20pkt.haslayer(DNSQR):
%20 %20 %20 %20q%20=%20pkt[DNSQR].qname
%20 %20 %20 %20m%20=%20pat.match(q)
%20 %20 %20 %20if%20m:
%20 %20 %20 %20 %20 %20idx%20=%20int(m.group(1))
%20 %20 %20 %20 %20 %20data%20=%20m.group(2).decode()
%20 %20 %20 %20 %20 %20chunks[idx].append(data)
print("chunk%20count:",%20len(chunks))
print("index%20range:",%20min(chunks),%20max(chunks))
s%20=%20""
for%20i%20in%20sorted(chunks):
%20 %20s%20+=%20Counter(chunks[i]).most_common(1)[0][0]
print("concat%20length:",%20len(s))
print("alphabet:",%20"".join(sorted(set(s))))
print("result:",%20s)
输出了很多内容,我们在输出内容的同时也整理了alphabet
得到字符表为
23456ABCDEFGHIJKLMNOPQRSTUVWXYZ
这很容易让人联想到Base32编码,其字符表如下
ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
虽然没有出现7,但仍然符合Base32的字符范围,所以我们尝试解码
可以用cyberchef进行解码,成功解码出可读内容
继续对解码后的这串内容进行研究,提取字符表
其实也可以用Frequency%20Distribution进行研究
得到新的字符表为
123456789ABCDEFGHIJKMNOPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz
正好58个,不免让人想到题目提示的Base58编码
但是这和默认的Base58字符表不同
123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz
实际上,我们默认的Base58编码一般指的都是Bitcoin%20Base58字母表,因为这个编码本身就是比特币的创造者中本聪设计的,专门用于生成人类友好的标识符和地址的
这Base58和Base64最大的区别就是刻意排除了容易引起混淆的字符
如数字0和大写O,还有大写的I和小写的l,还有+和/这六个
但是这个其实是没有唯一标准的,本题就是利用了这个设计初衷,修改了Base58的字母表,从我们统计的内容即可发现少了%200%20L%20i%20o
在再这些字符表的基础上尝试一下大小写和数字的排布,不难发现本题的字母表应该是
123456789abcdefghjklmnpqrstuvwxyzABCDEFGHIJKMNOPQRSTUVWXYZ
即“数字%20+%20小写字母%20+%20大写字母”
把这个字符表放到cyberchef即可
看文件头就可以确认正确了,我们解出了隐藏内容,一个压缩包
里边是一个Store存储、ZipCrypto加密方法的png图片,明显是明文爆破的题目
由于PNG文件有用固定的文件头,开头一般固定为文件头89504e470d0a1a0a,之后大概率紧跟IHDR块,也就是可以确定前16字节89504e470d0a1a0a0000000d49484452
作为明文爆破的明文绰绰有余了
创一个1.bin的文件,之后直接bkcrack明文爆破即可
bkcrack%20-C%20download.zip%20-c%20secret.png -p 1.bin -o 0 -j 8
成功得到keys,直接生成无密码的zip即可
bkcrack -C download.zip -k d1c9002e fddfca1e 86c3fee5 -D decrypted.zip
成功得到解密后的图片
打开来就是本题的flag了
flag{Crack_is_to_ezzzzzzzz}
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:赛查查 《Al x Cybersecurity Challenge香港人工智能网络安全挑战赛wp(Miscellaneous全)》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。








评论