“写笔记”支持四种格式——Word 文档、Excel 表格、Markdown、纯文本,起稿或二次编辑时都能随时切换,同一篇笔记想用哪种形态来记,都由你说了算。
md、txt、csv、json 这类纯文本则原样载入,不做多余加工。拿一张现成的表倒进来、改几笔、再导出去,等于白用一台免费的格式转换器。
要带走就在右上角点“下载”,可导出 PDF、Word、Markdown、Excel、TXT 等格式;列表卡片“⋯”菜单里,也有同样的下载入口。
在“工具”页点“+ 上传工具”即可发布:填好名称与链接,再用 Markdown 把使用方法写清楚——能解决什么问题、怎么装、怎么用,比堆介绍实在。
要分发安装包就一并上传压缩包(ZIP、RAR、7Z、TAR.GZ,最大 35MB),别人在详情页一键下载;只放链接不带附件也可以。
工具按大家的收藏热度排序,好用的自然会被顶上来。发布后可在详情页或卡片菜单里编辑、下架。
写笔记时勾上“隐藏”,这篇就只存在于你自己的账号里:不进列表、不进搜索、不上首页精选,也不会出现在任何公开的页面,链接发给别人同样打不开。
适合放密码、草稿、日记这类只给自己看的内容;想公开,去“发布”打开它,把“隐藏”的勾去掉再保存,之后编辑会默认保持原状态,不会悄悄变回公开。
你的内容会同时保存在多个副本上,系统定期做备份与完整性校验,再配合异地容灾机制:就算某台机器出问题,数据也不会丢,可以长期放心存放;特别重要的资料,仍建议你另外再留一份备份。
全站跑在容器化、模块化的现代架构上,更新、部署、回滚都很快,扩展性和稳定性都按长期运营的标准来设计(Built for reliability, designed to scale)。
这个网站最早只是一个人的笔记仓库,后来慢慢长成现在的知识中枢。设计上很克制——没有广告、没有追踪、没有推荐算法,只是干干净净地存放一些东西;既然做好了,就公开出来,万一有人用得上呢。
不做大而全,不做平台梦,保持简单、保持克制、保持好奇。所有内容都由用户贡献、由用户维护:不会突然冒出付费墙,不会在角落塞广告位,也不会把你的数据卖给第三方。
产品会持续迭代,站内日志页记录着每一次改动,改了什么都有迹可循;想了解这个站是怎么一步步走到今天的,翻翻日志就能看到来龙去脉。
如果在这里看到涉嫌违规的内容,点对应卡片右侧的“举报”按钮就能提交,我们会尽快核实处理;也谢谢你花一点时间,一起把这里维护干净。
香港AI网络安全CTF挑战赛复盘:Misc、Crypto、Reverse 解题思路
上周末去香港打了场CTF,叫"AI x Cybersecurity Challenge"。说实话,这名字听着挺唬人的,但实际打下来发现,题目设计确实跟传统CTF有点不一样——它更贴近AI时代的安全场景,比如自动化攻防、漏洞发现这些。当然,对咱们选手来说,本质还是那套:给附件、读题面、分析线索、拿Flag。
这篇文章主要是把我做过的Misc、Crypto、Reverse三个方向的11道题做个复盘。不会讲太多赛制规则,重点放在解题思路上,希望能给后面要打类似比赛的朋友一些参考。
先列个总表,方便大家快速了解这三个方向都考了啥:
| 方向 | 题目数量 | 主要考察能力 |
|---|---|---|
| Misc | 4 | 流量分析、数据恢复、文件修复、语义关系 |
| Crypto | 4 | PRNG、RSA、AES、数论近似泄露 |
| Reverse | 3 | Android逆向、协议逆向、驱动交互 |
总体观感:这11道题到底在考什么?
先说个整体感受。Misc、Crypto、Reverse虽然是三个不同的方向,但这组题有个很明显的共同点——它们都不是让你盲目上工具的。
- Misc里,数据可能藏在DNS查询、RAID成员盘、伪装文件和文本关系中。你光靠
strings和binwalk扫一遍是找不到flag的,得先搞清楚数据的结构。 - Crypto里,突破口不在"哦这个是RSA"这种层面,而在于随机数状态、RSA参数关系和生成过程。你得知道随机数被消耗了几次,RSA的辅助值泄露了什么关系。
- Reverse里,flag也不是简单搜字符串就能拿到的。你得理解程序到底信任了什么——是签名?是协议?还是驱动返回的结果?
用一句话概括的话:Misc考结构感,Crypto考边界感,Reverse考程序行为感。
下面按题目类型展开,每道题我都会把解题思路、关键判断点、踩坑的地方都写清楚。
Part 1:Misc
Misc题型阅读指南
Misc方向一共4题,覆盖了:
- 流量分析
- 语义谜题
- RAID数据恢复
- 伪装文件修复
做这类题,最重要的不是背工具列表,而是问自己三个问题:
- 这份数据原本应该是什么结构?
- 哪些字段、偏移、文件头或长度特征是可信的?
- 每解开一层后,新的数据类型是什么?
我个人的做题习惯是:
- 先用
file、binwalk、xxd、strings建立第一印象 - 不要只搜flag,先判断容器格式和编码链
- 遇到磁盘、镜像、压缩包时,优先确认元数据和偏移
- 每一步都保存中间结果,方便回滚和验证
1. Mysterious Code:DNS流量里的多层编码
题型标签:流量分析、编码识别、ZipCrypto已知明文
题目描述给了三个关键词:IDS告警、可疑流量、BASE58。附件是流量相关数据。
第一步不是爆破,也不是直接搜flag,而是先确认可疑内容出现在哪个协议里。分析后发现,真正有用的数据藏在DNS查询中。
具体做法是:把DNS查询内容按顺序提取并拼接。但拼接后的结果并不是明文,而是一条多层处理链:
DNS查询拼接 -> Base32解码 -> 自定义Base58解码 -> 得到ZipCrypto加密ZIP -> 利用PNG文件头做已知明文攻击 -> 还原图片并读取flag
这里有两个关键判断点。
第一个判断:Base32解码后结果仍然不像普通文本。很多人在这一步会卡住——看到"成功解码"就以为已经结束了。但实际上它只是中间层,不是最终答案。你得继续判断这个解码后的数据是什么类型。
第二个判断:ZIP使用的是ZipCrypto加密,而且内部文件是PNG。PNG文件头固定(\x89PNG),非常适合做已知明文攻击。相比盲目爆破密码,利用文件格式特征会更稳定。这里用bkcrack或者自己写脚本都可以。
复盘提醒:
- DNS查询常用于分片外带数据,顺序很重要
- 编码链每解一层都要重新判断数据类型
- 看到加密ZIP不要立刻爆破,先看加密方式和内部文件名
- PNG、PDF、ZIP等常见格式都有可利用的固定头
最终flag:flag{crack_it_is_to_ezzzzz}
2. Riddler:词语关系里的隐藏密码
题型标签:文本Misc、语义关系、词向量类比
Riddler的题面是一份"放错地方的笔记",里面有很多奇怪短语和误导性描述。它不像传统隐写题那样给图片、压缩包、流量字段,也不像古典密码那样直接给一串密文。
这题真正的入口在于**"关系"**。
题面反复强调signal、noise、relationship这些概念。换句话说,不能只看每个词本身,而要看词与词之间的语义方向。解题时,我把每行短语拆成语义类比关系,用类似GloVe Twitter词向量的方式做类比恢复,还原出一组可读的token。
举个例子,如果题面里有"king : man :: queen : ?"这种结构,那"? "就应该是"woman"。当然这题的类比关系没那么直白,需要先理解题面在说什么。
恢复出的token进一步组合成压缩包密码。用该密码解压附件,就得到了最终flag。
这题最有价值的地方在于提醒我们:Misc的"文本题"不一定是藏头、词频、首字母,也可能是语义建模。
复盘提醒:
- 题面中的"噪声""关系""信号"往往不是装饰文字
- 文本题先不要局限在古典密码
- 如果每行结构相似,可以考虑它们是不是在表达同一种关系
- 恢复token后,要继续检查是否能作为密码、文件名、路径或key使用
最终flag:flag{39fc0ee0f9da4fa397631f0dcba31555}
3. database:降级RAID 5里的业务数据库
题型标签:磁盘取证、RAID 5重建、ext4、SQLite
database是一道典型的数据恢复题。题面说某公司的RAID 5业务服务器备份只保留了一部分数据,需要检查残留证据,尽可能还原有用内容并找到隐藏flag。
附件解压后可以确认,两个文件都是Linux Software RAID 1.2成员。由于RAID 5每条stripe中包含parity chunk,即使缺少一个member,只要布局、chunk size、data offset等参数正确,仍然可以在degraded mode下重建逻辑卷。
解题主线如下:
识别RAID member -> 读取RAID5参数 -> 确认缺失成员位置 -> 跳过data offset -> 按left-symmetric布局重建逻辑卷 -> 解析ext4文件系统 -> 恢复SQLite数据库 -> 筛选真正flag
最容易踩坑的地方是data offset。Linux md RAID 1.2 metadata通常不在纯数据区开头,如果重建时不跳过对应偏移,就会把metadata当作数据区,后续ext4识别会错位。
具体来说,RAID 1.2的superblock在设备末尾的8KB处,data offset需要通过mdadm --examine查看。我当时的做法是:
- 先用
mdadm --examine查看每个成员盘的参数 - 确认chunk size、layout、data offset
- 用
dd跳过data offset,提取纯数据区 - 按照left-symmetric布局重组数据
- 挂载重组后的逻辑卷
重建后可以恢复ext4文件系统,并找到多份important.db。SQLite检查通过后,表内有多条形似flag的记录,但前三条记录的分类和说明分别指向decoy、distraction、red herring。真正的记录是category为System、message为Critical system flag的那一条。
复盘提醒:
- 磁盘题先判断是不是完整文件系统,还是RAID/LVM/分区成员
- RAID题不要只看文件头,要读取superblock参数
- RAID5缺一盘可恢复,但前提是布局和偏移正确
- 数据库里出现多个flag-like字符串时,要结合字段语义判断真假
最终flag:flag{raid5_recovery_1s_1mportant}
4. ntfs_dump:伪装成NTFS的ZIP碎片
题型标签:文件修复、magic signature、ZIP结构、步长隐藏
ntfs_dump的题面说数据藏在bin文件中,flag格式为FLAG{xxx}。文件开头有NTFS boot sector特征,很容易让人先按完整NTFS文件系统去恢复。
但继续观察会发现,文件里充满ZIP、PNG、JPEG、PDF等magic signature的重复模式,整体不像正常NTFS。真正的突破点不是"挂载NTFS",而是识别出伪装文件中的ZIP结构。
关键转折点如下:
- ZIP central directory和EOCD能定位到尾部结构
- local file header前两个字节被破坏——原本应该是
PK\x03\x04,实际前缀异常 - 修复local header后,可以重建出可识别的ZIP
这里修复local header的方法是:找到EOCD,然后根据central directory中的offset找到每个local file header的位置,把前两个字节改成PK(即\x50\x4B)。
解出text.txt后,还不能直接搜到flag。文件大小为4240 bytes,刚好能被16整除,这提示数据可能按固定stride交错隐藏。对每个offset尝试data[offset::16],在offset 0处得到flag。
复盘提醒:
- 文件头像NTFS,不代表整个文件就是NTFS
- 大量重复magic signature往往说明存在拼接、嵌套或伪装
- ZIP修复要关注local header、central directory、EOCD三者关系
- 固定长度文件可以检查是否存在stride、列读、交错写入
最终flag:FLAG{DE6DDD7A-5D57-415A-99D7-6C0CE3F688BC}
Part 2:Crypto
Crypto题型阅读指南
Crypto方向一共4题,表面上都是常见算法:Base64、Python random、RSA、AES、MT19937。
但真正的考点不在"背算法名字",而在生成过程。
做Crypto题时,我建议先问自己:
- 随机数是怎么产生的?
- 随机数被消耗了几次?
- RSA的辅助值泄露了什么关系?
- 加密前后的结构是否能提供已知明文?
- 结果是否能被格式校验?
1. CardSeed:可重现shuffle的逆向
题型标签:Python random、seed枚举、置换还原
CardSeed的附件中只有一个较短的Python脚本。它先将flag做Base64,再用random.seed(key_number)初始化随机数,随后对字节数组执行shuffle,最后输出被打乱后的结果。
题目的保护机制并不复杂:
key_number = randrange(999999),搜索空间只有999999- Python的
random.shuffle()是伪随机过程 - seed相同、列表长度相同,shuffle产生的置换就相同
- Base64只是编码,不是加密
解法就是枚举seed,重建同样长度的索引数组,对索引数组执行同样的shuffle,然后把输出字符串按逆置换放回原位置。每个候选结果再做Base64 validate,并检查是否符合flag{...}格式。
具体实现思路:
import random
import base64
# 假设encrypted是打乱后的输出
encrypted = b"..." # 从附件读取
for seed in range(999999):
random.seed(seed)
indices = list(range(len(encrypted)))
random.shuffle(indices)
# 逆置换
result = [0] * len(encrypted)
for i, idx in enumerate(indices):
result[idx] = encrypted[i]
try:
decoded = base64.b64decode(bytes(result))
if decoded.startswith(b'flag{') and decoded.endswith(b'}'):
print(f"Found seed: {seed}, flag: {decoded.decode()}")
break
except:
continue
这题非常适合用来理解"随机"和"不可恢复"之间的区别——只要seed空间足够小,所谓随机打乱就会变成可穷举的置换。
复盘提醒:
- 看到Python random要第一时间检查seed来源
- shuffle可以通过索引数组还原置换
- Base64 validate是很好的候选过滤器
- 不要把编码当加密
最终seed为614033,flag:flag{69d27b78-b762-406e-962d-c21afd6b0ba0}
2. Drift:RSA中"很小的误差"并不小
题型标签:RSA、phi(N)近似泄露、小范围枚举
Drift的题面问得很直接:一个转换后的RSA指数,只和phi(N)的倍数差了一点点,这点误差真的安全吗?
附件逻辑可以抽象为:
a = k * phi(N) + x
其中k很小(范围大约是2到96),x只有150 bits。对于1024-bit RSA来说,这不是"无害噪声",而是对phi(N)的高精度近似泄露。
原因在于:
phi(N) = N - (p + q) + 1
一旦能恢复phi(N)或者p+q,就可以通过二次方程分解N。
实际解法:
- 枚举小范围的k(2到96)
- 对于每个k,计算近似的phi(N):
phi_approx = a // k - 计算近似的p+q:
S_approx = N - phi_approx + 1 - 在小范围内搜索正确的S = p+q
- 正确的S会让
D = S^2 - 4N成为完全平方数 - 找到正确S后,恢复p、q,进一步计算私钥d并解密密文
这里的关键是:能恢复p+q,就基本等价于能分解N。
复盘提醒:
- RSA题里所有"接近phi(N)"的值都要高度警惕
- 小范围参数不要怕枚举,先把数学关系写清楚
- 完全平方数检查是RSA分解中常用的验证方式
最终flag:flag{rsa_exponent_transforms_need_error_bounds}
3. LuckyRand:从AES-CBC密文反推MT19937状态
题型标签:AES-CBC、MT19937、状态恢复、IV重组
LuckyRand把受保护的信息混入大量看似随机的数据中。题目使用AES-CBC,但关键不只是AES,而是PRNG的生成与消耗顺序。
程序中,明文结构大致为:
padded_flag || 160个PRNG生成的128-bit block
密文共163个block,因此flag padding后占3个AES block(48 bytes)。后面的160个block可以结合CBC解密关系还原出PRNG输出,再加上公开key,可以拿到足够多的MT19937输出。
核心步骤如下:
利用CBC关系恢复PRNG block
- 对于CBC模式,
P_i = D(C_i) XOR C_{i-1} - 如果已知部分明文或能推断出关系,可以恢复出PRNG的输出
- 对于CBC模式,
每个128-bit block拆成4个32-bit输出
- MT19937每次输出32位,所以128位就是4个输出
对tempered output做untemper
- MT19937的输出经过了一个"temper"变换
- 需要逆向这个变换才能得到内部状态
恢复MT state word
- MT19937有624个32-bit状态字
- 需要足够多的输出来恢复这些状态
处理缺失的IV对应word
- IV是AES-CBC的第一个block的"前一个block"
- 如果IV是随机生成的,也需要从PRNG输出中恢复
利用CPython twist原地更新细节求缺失状态
- 这里有个大坑:CPython的MT19937 twist并不是"同时更新整个数组",而是原地更新
- 最后一个state word的计算会用到已经改写过的mt[0]
- 如果忽略这个实现细节,方程关系就会错
重组IV,解密前3个block得到flag
复盘提醒:
- AES-CBC题要写出每个block的依赖关系
- MT19937输出足够多时可以恢复状态
- tempered output需要untemper才是内部状态
- 具体语言实现细节可能影响恢复公式
最终flag:flag{75b24dd7-e758-4587-8d34-ccffbe81eb16}
4. RsaComb:把散落的RSA线索串起来
题型标签:RSA组合题、三次方根、Wiener Attack
RsaComb是一道RSA风格的组合题。题面给了几组看起来不太寻常的辅助线索,关键不是单点突破,而是把它们按依赖关系串起来。
攻击链可以概括为:
从Gift^3恢复Gift -> 恢复辅助公钥gift_e_pub -> 恢复真实RSA公钥e -> Wiener Attack恢复私钥d -> 解密
第一步利用的是三次方根关系。如果Gift^3的值没有模约束或者没有溢出,直接开三次方就能得到Gift。
恢复Gift后,可以进一步解出辅助公钥信息。随后再从辅助关系中恢复真实RSA公钥指数e。
最后的关键是Wiener Attack。真实私钥d满足Wiener attack条件(d比较小),因此可以通过连分数展开从(e, N)恢复d。拿到d后,就回到标准RSA解密流程。
Wiener Attack的原理是:如果d < N^(1/4)/3,那么可以通过连分数逼近e/N来找到d。具体实现可以用Python的sympy库或者自己写连分数展开。
复盘提醒:
- RSA题里的辅助值不一定直接泄露p、q或d
- 只要能恢复e,或者约束d的大小,就可能落入经典攻击
- 三次方根要先判断是否没有模约束或没有溢出
- Wiener Attack的入口是"小私钥d"
最终flag:flag{rsa_comb_v2_cube_root}
Part 3:Reverse
Reverse题型阅读指南
Reverse方向一共3题,分别对应:
- Android APK签名校验
- 自定义socket通信流程
- Windows驱动交互与IOCTL校验
这三题的共同点是:flag不在显眼字符串里。真正要还原的是程序的信任逻辑。
做Reverse题,我建议按这个顺序推进:
- 先跑起来,观察输入输出和报错
- 找到关键函数,而不是全量反编译所有代码
- 判断flag是被解密、被验证,还是由通信流程返回
- 把程序依赖的上下文补齐(签名、密钥、证书、驱动连接)
- 最后写脚本复现逻辑
1. CodeSign:APK v2签名参与解密
题型标签:Android逆向、APK v2签名、SHA1、异或解密
CodeSign是一道Android逆向题。题面说"移动保险库显示访问已被允许,但秘密没有出现"。
分析MainActivity后可以发现,程序确实会在按钮点击后尝试把解密结果写入tv_flag,但UI层又提示输出被禁用。
真正的核心在decrypt()所需的key。程序通过SignUtils.getAppSignature()获取当前APK的签名证书,对DER数据做SHA1,然后参与异或解密。
这里有一个关键坑点:该APK使用v2签名,并没有传统META-INF/*.RSA证书文件。不能按老APK的方式去找证书,而要从APK Signing Block中提取证书DER,再计算SHA1。
APK v2签名的证书位置:
- APK文件结构:ZIP Central Directory -> APK Signing Block -> ZIP Entry Contents
- 证书DER在APK Signing Block中,需要用专门的工具或脚本提取
拿到签名SHA1后,将其作为key与SECRET_DATA做异或即可恢复flag。
还有一个小细节:反汇编fill-array-data时,末尾附近可能出现对齐补位00,但它不属于密文,不能参与异或。
复盘提醒:
- Android签名题要先判断APK签名方案版本
- v2签名证书在APK Signing Block,不在传统META-INF路径
- 反汇编数组要注意对齐补位
- UI提示不一定等于真实失败,关键看解密函数依赖
最终flag:flag{ICQ_Dyn4m1c_Byp4ss_K1ng}
2. SimpleSocket:RSA保护AES key,AES解flag
题型标签:协议逆向、RSA-OAEP、AES-ECB、PEM修复
SimpleSocket附件中有Python socket通信逻辑,以及三个数据包文件。题目要求逆向自定义通信流程,还原最终内容。
分析后可以看出通信结构:
packet2保存RSA私钥packet3是RSA-OAEP加密后的AES key,长度128 bytespacket1是AES-ECB加密后的flag,长度48 bytes
这里不能只根据变量名猜数据含义,而要结合数据长度和加密算法特征判断:
- RSA 1024-bit密文长度正好是128 bytes
- AES密文长度是16 bytes的倍数(48 = 16 * 3)
另一个细节是,packet2中保存的是字面量\n(两个字符:反斜杠和n),不是真实换行。导入PEM私钥前,需要先把字面量\\n替换成真实换行。
完整解题流程:
- 修复PEM私钥:将
\\n替换为\n - 用RSA私钥以OAEP方式解密
packet3,得到AES key - 用AES key以ECB模式解密
packet1,得到flag
复盘提醒:
- 协议逆向题要结合数据长度判断加密算法
- PEM格式的私钥要注意换行符的处理
- RSA-OAEP和AES-ECB的参数要确认(OAEP的hash算法、ECB的padding等)
最终flag:flag{simplesocket_reverse_engineering}
3. DriverCheck:Windows驱动交互与IOCTL校验
题型标签:Windows驱动、IOCTL、逆向分析
DriverCheck是一道Windows驱动逆向题。题面说需要与一个驱动程序交互,驱动会校验输入并返回结果。
分析驱动文件(.sys)后,可以发现:
- 驱动注册了若干IOCTL(I/O Control Code)
- 每个IOCTL对应不同的操作
- flag的获取需要按特定顺序发送IOCTL请求
具体来说,驱动的处理逻辑是:
- 接收用户态程序发送的IOCTL
- 根据IOCTL码执行不同的校验逻辑
- 校验通过后,返回部分flag数据
- 需要组合多次IOCTL请求才能得到完整flag
解题步骤:
- 用IDA或Ghidra反编译驱动文件
- 找到
DriverEntry和IRP_MJ_DEVICE_CONTROL处理函数 - 分析IOCTL码的分发逻辑
- 找出每个IOCTL对应的校验条件
- 编写用户态程序,按顺序发送IOCTL请求
- 收集并组合返回的数据,得到flag
复盘提醒:
- Windows驱动逆向需要理解WDM驱动框架
- IOCTL码通常通过
CTL_CODE宏定义 - 驱动的校验逻辑可能涉及设备对象、同步机制等
- 用户态程序需要通过
CreateFile打开设备,用DeviceIoControl发送请求
最终flag:flag{driver_ioctl_reversing}
总结
打完这11道题,最大的感受是:CTF不再是"背工具"或"背算法"的游戏了。
Misc考的是你能不能看懂数据的结构,Crypto考的是你能不能抓住参数的边界,Reverse考的是你能不能理解程序的信任逻辑。
如果你正在准备类似的比赛,我建议多花时间在"理解原理"上,而不是"记住命令"上。比如:
- 遇到随机数,先想seed从哪里来
- 遇到RSA,先想哪些参数泄露了信息
- 遇到APK,先想签名方案版本
- 遇到驱动,先想IOCTL的分发逻辑
希望这篇复盘对你有帮助。如果你有更好的解法或者发现了我的错误,欢迎在评论区指正。咱们下次CTF见!