一个退役了的老登为小登留下的一些没啥价值的遗产(确信)
老样子,这里还是只会放我本人出的题目,不过由于我退役了,所以我已经不负责Misc的出题和审题了,出题量相比23和24年自然也会少很多 tmd越出越起劲结果最后出的比其他所有人都多了wc(
考虑到战队内部没有其它人会Blockchain了,所以决定留下一套教程题目,不知道后面的小登会不会去做,但那也是他们的事了,我滴任务,完成啦!(
笑死了,7.4那时说的教程题目指的是千链万花来的,结果慢慢的就出多了
水稻 LI 4
什么傻逼才会用这个题目名去对小米SU7啊(恼)
预期难度:3.4⭐
Docker镜像:cauliweak9/mqtt2:v3
考点
- 命令注入
- MQTT交互
前言
本题改编自CISCN2025总决赛的赛题mqtt,使用Python重构且删除了条件竞争部分以降低题目难度
其实本来是想出Modbus交互题的,但是懒得去设计PLC逻辑搞梯形图了,遂作罢,刚好国赛又有这么方便的题目思路,就顺手借鉴一下了(笑)
Writeup正文
核心漏洞代码如下:
1 | def modify_VIN(self,new_vin:str): |
我们可以看到传入的new_vin 是直接拼接在命令中的,这很明显是我们的命令注入漏洞,比如我们的flag在/app/flag,那么我们可以传入 ; cat /app/flag 这样的参数,从而达到将flag写入/mnt/vin中并返回的作用
最后只需要等定时的get_info 触发即可得到flag
相信auth也很简单了:直接复制粘贴就行了,说句实话不如搞个C逆向…
最后附上临时赶工写的脚本:
1 | import paho.mqtt.client as paho |
下面的内容为Project: Syndallion系列题目的Writeup,该系列题目80%以上的考点为区块链相关的知识点
You have been warned…
千链万花
我靠柚子厨蒸鹅心
预期难度:1.0⭐~5.7⭐
Docker镜像:cauliweak9/kaleidoscope:v2
Walkthrough视频(很长,2h,一堆废话):(新生赛还没开赛,暂未发布视频)
考点
- RPC链接的导入+简单的合约编写&部署
- 算数漏洞(整型溢出+精度损失)
- DoS攻击
- 重入攻击
- delegatecall的风险
- 签名漏洞(签名复用+签名延展)
- 基础DeFi标准
- 综合套套娃(使用治理合约管理的简易可升级代理合约)
前言
本题预期是作为一个区块链的入门系列题,希望新同学在完成整个系列的题目之后就已经可以正式接触国外的Blockchain题目了,但是最后的结果嘛…好像合约还是稍微长了那么亿点点,每题前面的引导质量差了那么亿点点…总之希望各位喜欢,有意见欢迎拷打
Writeup正文
本系列题目共有8关,其中最后一关要求在同一个靶机内完成所有7关才会解禁,接下来会按照顺序依次进行题目解析:
Stage 1:浅梦轻眠
预期难度:1.0⭐
本题考查的是合约的基础编写与部署操作,同时附加一些RPC的使用之类的,总的来说是一些必要的基础知识,需要编写的合约很简单,满足条件即可,比如:
1 | // SPDX-License-Identifier: MIT |
Stage 2:溢梦虚空
预期难度:2.4⭐
本题考查的实际是关卡教程的第2点:除法精度损失,其中问题发生在题目的get_swap_price函数中,它的算式思路似乎没有问题,但是将amount放在最前面和from进行相乘的时候放大了除法精度的误差,因此多次swap后会使精度误差以指数级放大,最终导致攻击者卷走几乎所有的两种币
攻击过程中的代币变化如下,其中C1/D1表示用户持有的Cat Coin/Dog Coin,而C2/D2表示合约持有的Cat Coin/Dog Coin,TA为转账数额:
1 | C1 D1 C2 D2 TA |
当然你也可以写一个自动化的合约:
1 | // SPDX-License-Identifier: MIT |
Stage 3:梦隙拒流
预期难度:2.4⭐
本题考查的是一个最基础的DoS攻击,跟着教程走就行:只需要让合约的fallback中直接revert,然后直接让合约转账即可
当然,对于本题我们的攻击合约可以完全不设置fallback和receive,这样攻击合约无法接受转账,因此转账时同样会导致revert,比如如下的代码:(需要注意创建合约的时候要同时设置msg.value)
1 | // SPDX-License-Identifier: MIT |
Stage 4:盗梦空间
预期难度:3.8⭐
本题考点为重入攻击,重入点为airdrop函数,因为在获取空投的时候会调用Token的safeTransfer函数,而该函数会尝试调用代币接收者的onTokenReceived函数(如果接收者是合约的话),而此时的airdrop_received仍旧为false(因为safeTransfer此时并未执行完),因此我们可以在自己的onTokenReceived函数中再一次获取空投,直到获取所有的钱(不收手会导致整型溢出,最后revert)
1 | // SPDX-License-Identifier: MIT |
Stage 5:庄周梦蝶
预期难度:3.5⭐
根据教程,我们发现在我们执行Car合约的order_beers函数时,修改的不是Gas_Station合约的beers,而是Car合约的current_gas_station变量,也就是说我们第一步是可以任意控制Car合约进行delegatecall的对象的,只需要让传入的bottles在数值上等于我们自己的某个合约的地址的值即可,即bottles=uint(uint160(address(Some_Contract))),然后我们只需要预先在这个合约中设计好变量的排布(最简单的方法就是复制粘贴)然后设计一个让fuel=10000的order_beer或者refuel函数即可
1 | // SPDX-License-Identifier: MIT |
Stage 6:千面一人
预期难度:2.8⭐
相信教程中的web3py脚本已经足够各位进行签名伪造的操作了,现在的问题就在于如何只使用2个签名进行刷票操作
其实漏洞很简单,在检查签名反复利用的时候,检查方法是直接比对上一个签名和当前签名是否相同,因此我们只需要交替传入两个签名即可通过检测,至于如何在调用函数时传入数组变量此处就不进行展开了,请各位自行搜集资料
Stage 7:商海如幻
预期难度:3.7⭐
这个ERC20代币的实现有一个非常大的漏洞:在使用transferFrom的时候,不会同步减少allowance[owner][spender]的值,这就导致我们可以不断进行transferFrom操作,最后将所有的代币占为己有
1 | // SPDX-License-Identifier: MIT |
Stage 8:镜花水月
预期难度:5.7⭐
作为本系列最难的题目,整个题目由3个合约构成,且参杂了可升级合约、代理合约、治理合约等多种合约类型(虽说有点小套娃),总之让我们来看看本题该如何解决吧
题目要求很简单:提走Proxy合约中所有的钱,由题目我们可以知道Proxy是一个可升级代理合约,通过修改implementaion的值来修改该代理合约的实现,而其中的_delegate函数则是将调用转发至implementation进行合约的逻辑实现,说了这么多,实际上就是一句话:我们需要将implementation升级为一个包含payable(address).transfer(address(this).balance)的函数的合约并执行这个函数,比如下面的合约:
1 | // SPDX-License-Identifier: MIT |
因此我们需要首先部署这个新的Vault2,然后想方法升级implementation为这个Vault2的地址,然后对Proxy调用withdraw函数,但是想要升级我们就只能通过governanceCall进行调用,不然没有其他方法可以调用upgradeTo
governanceCall要求我们当前所持票数超过阈值300,而我们只有10票,那应该如何刷票呢?检查发现delegateVotes函数在将自己的票权代理给另一人的时候,本人持有的票权并不会消失,因此只需要随便找一个地址然后不断向这个地址delegateVotes就能完成刷票操作
那么现在我们已经有一个刷好票的地址了,是时候执行governanceCall来调用upgradeTo升级合约了,但是此时我们需要构造calldata,这个就需要各位了解calldata了,这里就简单过一下:我们需要的calldata是一个selector+params的组合,其中函数选择器selector在前面重入的时候就已经见过了,类推至本题应该是bytes4(keccak256("upgradeTo(address)")),即3659cfe6,而后面的params是这个函数的参数(需要填充到32字节的hex),这里的话就只有一个地址,这个地址应该是我们的Vault2的地址,比如地址为0xd14d19356CED5C533aDaf59FA9086C87Cb33086e,那么我们的params应该是000000000000000000000000d14d19356ced5c533adaf59fa9086c87cb33086e,因此最后的calldata是3659cfe6000000000000000000000000d14d19356ced5c533adaf59fa9086c87cb33086e
这里的calldata实际上就是
abi.encodeWithSignature("upgradeTo(address)",address(Vault2))
在升级完合约之后,我们对Proxy的地址调用Vault2的withdraw函数即可提走所有款项完成本题
Æmeistralitheum
本来以为没那么难的,测题测着测着就变难了,压缩题目遇测变难说是(
预期难度:7.5⭐(Part I),4.3⭐(Part II)
Docker镜像:cauliweak9/aemeistralitheum:part1,cauliweak9/aemeistralitheum:part2
考点
Part I:信息搜集,Python恶意样本分析(远控木马类),Python字节码逆向,加密流量分析,Metamask钱包助记词爆破
Part II:公链溯源,合约字节码反编译与简单逆向,EIP-7702基础
前言
去年放了个勒索病毒,今年放个远控木马,不过没加多少免杀,反正我电脑上的杀软一下子给我报毒了(
本意就还是出一个相对简单的恶意样本分析题目的,然后想着Winrar的CVE报告和复现满天飞,就想着给小登来点最新最热,结果没想到题目给我埋雷了,在此感谢同校的Adrian师傅参与了题目的测试(简称赤石),他成功帮我抓出来了两大坨隐藏的地雷,因此才有了Part I这个相对完整详细的Writeup
Writeup正文
Part I
Q1
1 | 请问Rar附件中隐藏的恶意文件的名字是? |
如果各位有完成新生赛中的《double hide》题目,应该能很快发现Rar文件里面存在一个NTFS流,这个流里面有一个exe文件aestra.exe,而这就是我们的恶意文件
1 | 答案:aestra.exe |
Q2
1 | 该恶意RAR利用的CVE的编号是? |
观察Rar文件,我们发现该恶意文件名前面存在大量..\,因此推测该CVE和目录穿越相关,因此搜索rar 目录穿越 cve,经过简单的搜索后可以知道该Rar利用了近期的一个漏洞CVE-2025-8088
1 | 答案:CVE-2025-8088 |
Q3
1 | Rar附件解压后恶意文件被释放到哪个路径下? |
了解漏洞详情,我们可以知道攻击者可以往ADS里面添加恶意文件,并且构造文件名以触发该CVE,最终将文件释放到用户的启动目录中,进而使得释放的恶意程序在受害者电脑启动时自动运行,而附件也证明了这一点,给出的..\是为了回退到系统盘目录下(C:\),因此释放的文件目录就是将..\去掉后改成C:\即可
1 | 答案:C:\Users\10347\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\aestra.exe |
Q4
1 | 隐藏的恶意文件的SHA256是? |
相信这一步会卡住很多人,因为大部分软件无法解压出NTFS流,而为数不多能解压的软件(如WinRar,7Zip)都会因为所谓的语法问题而无法解压出来,从而卡死在这一步,这里将给出一个相对“邪道”的方法
根据CVE我们可以知道该漏洞实际上就是基于WinRar中对解压目录的解析出现了问题,因此我们只需要修改一下恶意Rar中构造的目录即可,比如将aestra.exe前所有的字符(请注意:需要保留第一位的冒号)为1:
此时我们就成功修改了目录解析的问题,但是由于修改文件会导致CRC改变,因此我们还需要重新修改一下NTFS流数据所在的Block[2]的CRC,用010的模板重新跑一下就可以在下方的Output栏中找到你需要更改的结果了:
修改之后就可以正常使用Winrar/7Zip解压出NTFS流,然后使用诸如NTFSStreamEditor等工具提取出exe即可
当然理论上你可以自行新创建一个名称为10347的用户,这会为了该新用户生成一个新的启动目录,此时使用该用户解压Rar即可
至于使用Python等语言从DataArea直接解压数据并保存的方法这里就不展开讲了,总之请各位自行探索,最后将提取出的文件进行SHA256即可
1 | 答案:f250944b7aacb3767367ebf77140c78977f4e5c98485c5abfd7035bd15cf8d69 |
Q5
1 | 此恶意文件使用的Python版本号? |
看到恶意文件的图标,肯定能马上知道它是使用PyInstaller打包的一个exe,因此使用pyinstxtractor进行解包即可,从中找到主文件的编译数据main.pyc后进行pyc反编译即可,推荐使用PyLingual,最后可以很快知道恶意文件使用的Python版本号
请注意:使用PyLingual的时候会出现报错,错误在Semantic Errors里,这一点很重要,我们在之后分析握手那问(Q9)会提及它
1 | 答案:3.11 |
我不清楚你们能不能看到,我解压后得到的链接是这个:main.pyc - PyLingual
当然你们应该也能搜到pycdc和uncompyle6这两个反编译工具,不过前者我试了一下,程序报错还宕机了;后者最高只支持3.8版本的字节码,所以最后还是用的PyLingual
Q6
1 | 该恶意文件里面存在2个完全没有被用上的函数,请问函数名是? |
分析反编译后的代码,我们可以看到如下的一个函数:
1 | def call_func(self, attr, param): |
根据函数名可推断出该函数用于调用功能函数,而功能函数在assets模块中,而一般模块的编译文件会打包在PYZ中,因此我们需要对其进行解密并解压,至于方法各位可以自行搜索,当然如果你和我一样用的是pyinstxtractor/pyinstxtractor-ng: PyInstaller Extractor Next Generation,那么它会自动帮你解压PYZ,直接去找assets模块即可
首先查看__init__.pyc:
1 | # Decompiled with PyLingual (https://pylingual.io) |
此时我们已经知道了from assets import *究竟导入了哪些函数:就是__all__里的函数,而对应一下发现存在一个misc.pyc,理论上对应的是misc.py,并没有被导入进去,此时我们对其进行反编译:
1 | # Decompiled with PyLingual (https://pylingual.io) |
里面有3个函数没有被导入,但是其中force_decode可以在其它pyc中发现被导入,而另外两个没有被其它任何地方导入,因此可以确定答案就是这两个函数:
1 | 答案:parse_function_fork_bomb |
fork_bomb的功能是执行分叉炸弹,各位感兴趣的可以去了解一下,但是不要乱搞,出了问题我一概不负责
Q7
1 | 攻击者的IP和端口是? |
此时我们进入到流量分析环节,根据main.pyc我们发现该恶意文件是一个RAT(Remote Access Trojan,远控木马),其持续开放端口29437进行监听,因此我们只需要过滤一下tcp.port eq 29437即可得到所有的通信流量,而与该端口通信的另一个IP和端口即为攻击者的IP和端口
1 | 答案:172.28.114.103:61594 |
Q8
1 | 恶意文件和攻击者交互前会进行握手,其中握手时使用的预先确定好的AES key是? |
该Key已经被硬编码在了main.pyc下,直接找KEY即可
1 | 答案:$Made$By$Triode$ |
Q9
1 | 分析流量包,请问密钥交换完成后得到的session_key的十六进制字符串(Hex)是? |
此时我们就需要进行对加密通信的分析,我们观察一下handshake中的实现:
1 | def handshake(self, client: socket): |
等等!还记得我们Q5提到的反编译报错吗?让我们展开来看看出现了什么错误:
第39行正好是计算session_key那一步,而下面的字节码比对说明原来的操作码是BINARY_OP 5,而反编译代码里面用的是加法,也就是右边的BINARY_OP 0,自然就出错了啊,所以如果你直接照抄Python代码的话,无论怎么做都做不出来的,因此我们需要找到这个5究竟指代的是什么操作
探索CPython源码实现
以防各位不知道,Python实际上是用C实现的,而CPython就是Python的源码,那么自然而然的,这个CPython一定有我们需要的答案
需要注意的是,不同版本下Python字节码会存在差异,因此我们需要根据之前分析出的Python版本号(3.11)进行筛选,即我们要在Github中筛选版本,由于我们不知道详细的版本号,没法到Tag里面找,因此只好先看看3.11的分支下的源码
首先查看CPython的Github Repo(python/cpython: The Python programming language),选择好后在上方搜索栏搜索字节码的英文opcode,我们能发现有个Modules/_opcode.c,这就是实际的源码,点击进去查看即可,但是首先让我们先在左侧修改分叉为3.11:
接下来看一下代码实现:
这一部分就是解析字节码的部分,很明显我们的5是一个oparg,即Operation Argument,这一点很重要,我们一会儿会需要这个信息
回到代码开头,我们能看到一个#include "opcode.h"的头文件导入操作,而一般头文件里面会有一些宏定义的变量,或许我们可以在里面找到答案,因此我们查看opcode.h(其位置在Include/opcode.h):
我们发现5对应的确实是BINARY_OP_ADD_INT啊?那么是那里出错了呢?欸,我们刚刚提到了这个5应该是一个oparg,而这个BINARY_OP_ADD_INT实际上是一个opcode!因此实际上是PyLingual反编译的时候将这个5误识别了这个opcode,最终导致解析出来的是加法
那么我们的oparg在哪呢?别急,我们往下看:
后面这一部分才是我们真正需要找的oparg,而我们发现5对应的赫然是NB_MULTIPLY,这里的NB指代的是NUMBER之意,也就是说实际上我们的BINARY_OP 5实际上执行的是NB_MULTIPLY,也就是乘法
从Python 3.13.0开始,CPython就将Opcode对应的ID存放到了
opcode_ids.h,而opcode.h专门用于存放OpArg对应的ID,相比之下我还是更喜欢3.13的改动,不然好容易搞混…
Python字节码分析
到此为止我们已经成功解决了错误,但是我们需要具体看看字节码以便于我们进行源码的纠错,推荐搜索字样rand_key以便于我们寻找字节码实现:
首先相信各位已经在搜索BINARY_OP的时候发现了,它会弹出栈顶的两个变量然后进行操作,看图说话的话就是第58部分就是获取rand_key和self.e进行BINARY_OP 5,并保存在server_key中,而第59部分就是首先获取rand_key和client_key进行BINARY_OP 5,然后将得到的结果进行long_to_bytes,最后保留到self.session_key中,而刚刚已经知道了BINARY_OP 5就是乘法,所以这两段的代码如下:
1 | server_key = rand_key * self.e |
也就是说handshake函数真正的源码是这样的:
1 | def handshake(self, client: socket): |
到此我们可以正式进入分析握手的部分:
握手逻辑分析
我们可以知道握手时木马会接收攻击者发送的数据并提取出key(记为client_key),然后木马会生成一个随机的8字节数据并转为int(记为q),将这个int和预设定的e相乘发送给攻击者(记为server_key),而最终的session_key为client_key * q
因此我们只需要提取出攻击者和木马发送的两个key,最终根据木马的代码计算client_key * server_key // e即可
信息经过的加密函数可以直接套用,根据前几问可以锁定在encryption.pyc里:
1 | # Decompiled with PyLingual (https://pylingual.io) |
实际整个加密流程就是先使用AES-CBC加密json字符串,然后在前面拼接iv,最后再使用zlib压缩而已
同时根据handshake函数可以知道攻击者发送的数据只有zlib数据,而木马发送的zlib数据前面有4个字节的数据,表示发送数据的长度,因此我们首先在Wireshark追踪流:(红色为攻击者发送的数据,蓝色的为木马发送的数据,开头这一组就是握手数据)
然后利用Q8的初始AES密钥,最终可以得到如下图的Recipe:(此处以攻击者发送的数据为例,解密木马发送的数据需要你在From Hex和Zlib Inflate之间加一个Drop bytes (Start=0,Length=4))
可能你会问上图解密为什么有乱码,那是因为我懒得去把iv提取出来输入进去,而对于AES-CBC模式,如果key是对的但是iv不对的话,只会有第1个加密块(16字节)会解密错误,剩余的数据都可以正常解密,而我们需要的
client_key和server_key都在很后面,不会受到iv错误的影响
理论上你得到的client_key应该是933180633405271150368497747576338016059223757962112558924,而server_key应该是690903603795425702911875810978864076950167361939213030996,此时我们的session_key也就呼之欲出了
1 | 答案:1065e6b91eadfb9e7ece1b38ea60f6559d436ff9c84e2fd871f7d9e4c6a13ab0 |
本题的握手密钥交换实际上就是存在A和B两方,其中预先约定好一个$e$,A发送$pe$,B发送$qe$,最终确认的最终密钥则是$pqe$
Q10
1 | 分析流量包,请问受害者电脑的产品ID是? |
根据main.pyc,接下来的流量就是使用刚刚提取出来的session_key作为AES密钥进行通信,因此接下来主要就是进行流量分析,对木马的分析可以暂且放一下
加密数据处理脚本
为了方便解析流量包(因为存在大量数据),这里提供一个Python脚本
首先在Wireshark追踪木马进行数据传输的流,理论上过滤器应该是tcp.stream eq 2,即TCP流2,然后在下面将“显示为”后面的选项选择为“原始数据”:
接下来Ctrl+A全选里面的数据复制到一个叫做full.txt的文件里面,最后使用Python运行下面的脚本:(需要提前安装Crypto库:pip install pycryptodome)
1 | import random |
运行结束后你的控制台应该会变成这样:
这里的json就是攻击者发送的请求数据,你可以看到攻击者究竟做了什么
与此同时你的当前目录下会生成一系列txt文件,这些txt文件里面就是木马返回的响应数据,其中数字表示是第几次交互,而英文则是调用的函数,和对应的attr一致
题目正文
好的,接下来让我们回到正题,根据第1次交互(get_system_info)返回的数据,我们可以得到答案
1 | 答案:00342-30428-53677-AAOEM |
Q11
1 | 分析流量包,可以发现受害者在尝试访问Sepolia链的区块浏览器时出现了拼写错误,该拼写错误导致受害者跳转到了另一个页面,请问最终跳转到的页面网址为? |
这一部分我们需要看的是浏览器的历史记录,对应get_history,因为比较长这里就不贴出来了
根据题目描述,搜索Sepolia字样,我们能看到确实有区块链浏览器(Blockchain Explorer)的访问记录,对应的域名为sepolia.etherscan.io,但是历史记录中最下面有一个域名sepolia.ethescan.io,在etherscan里面少了一个r,最终根据URL对应的网页标题(title)我们知道跳转到了一个叫ChangeNOW的网站,搜索一下会发现是一个交易所,域名为changenow.io
1 | 答案:changenow.io |
当然你也可以试出来,历史记录里面有;或者你可以通过cookie发现同样的网站标题里面只有
changenow.io这个域名保存了Cookie,进而解出本题
Q12
1 | 分析流量包,可以发现攻击者关闭了受害者的某个进程,请问该进程名字和PID? |
Q10的截图已经有了,PID是4056,然后在show_processes里找到对应的PID就行了
1 | 答案:Everything.exe_4056 |
Q13
1 | 分析流量包,可以发现攻击者窃取了受害者的区块链钱包插件数据,请问受害者钱包地址为? |
查看get_metamask_log返回的数据,木马返回的数据是经过Base64编码的,解码后查找AccountsController下的internalAccounts,里面的accounts就是钱包内所有的地址,只有一个地址,但是这个地址并非Checksum地址,去找一个Checksum的网站就行了,或者在插件数据中的AddressBookController下的addressBook里面就有
1 | 答案:0xaB0a16230B6685d3dc4be7259Ee2B722564426a4 |
Q14
1 | 分析流量包,可以发现攻击者对受害者的桌面进行了截图,请问桌面截图中隐含的flag内容? |
查看screenshot返回的数据,将数据进行Base64解码后保存为png格式即可看到flag图片
1 | 答案:Aurora{L0ve<3Marin} |
Q15
1 | 分析第13问中提取的区块链钱包插件数据,请尝试提取出受害者钱包的助记词。 |
本小问为最难的一问,因为助记词并非明文保存在插件数据中的,需要你自行去解密
详细的一个过程可以参考本人博客(当大狐狸遇上小狐狸——Win11火狐Metamask取证小记 | 9C±Void’s Blog),这里不再赘述,仅进行解题的操作演示
请注意:上面的文章中提到的26650模块已经被删除,请各位自行寻找其它方法,如手动编译26620模块等
首先找到KeyringController下的vault,里面的就是博客中需要找到的需要进行解密的数据:
接下来从中提取出data,iv和salt三个参数,根据你使用的hashcat模块和博客中对两种模式下哈希的格式进行组装,比如我本人使用的是26650模块,格式就是$metamask$salt$iv$data:
接下来使用hashcat进行爆破即可,使用的字典为rockyou.txt,由于我本人使用的是26650模块,因此输入的命令为hashcat -a 0 -m 26650 crack_metamask2.txt rockyou.txt,其中-a 0表示使用字典爆破
由于此前我已经成功完成过一次爆破,因此这里将直接显示爆破的结果:
由此可以知道钱包的密码为]fk;y]pN,最后使用MetaMask Vault Decryptor进行解密即可
由此我们成功得到了钱包的助记词
1 | 答案:cotton major fresh spend grid father coin jungle wrestle alone desert siren |
本人的游戏本进行hashcat爆破时,消耗的总时长约为2~3分钟,对于其它的轻薄本之类的设备爆破的速度要慢30倍以上,预计爆破时长约为1~2小时
同时,该助记词恢复的钱包里的地址并不包含任何实际资产,整个钱包密码爆破的流程仅用于学习交流用途,请勿用于违法用途
除此之外,因为助记词公开,请勿将该地址/钱包用于实际生产环境中
Part II
Q1
1 | 攻击者一共尝试进行了几次攻击? |
我们进行链上溯源的时候,区块链浏览器是必不可少的,这里我们使用Etherscan来完成本题,链接为sepolia.etherscan.io
需要注意:
etherscan.io是Ethereum主网的浏览器(链ID:1),因此你在上面是找不到我们的交易的,因为我们的交易都在Sepolia链上
在区块链浏览器里面搜索受害者地址,我们可以得到如下的页面:
下面的Transactions和Internal Transactions就是该地址参与的交易,简单来说的话就是前者的交易来自于EOA地址,后者的交易都来自于智能合约,当然这也说明同一交易可能同时存在于Transactions和Internal Transactions中,比如EOA→合约→EOA这种情况
我们发现这个地址并没有Transactions,但是Internal Transactions有6个记录(需要打开Advanced View Mode):
仔细观察后发现最上边4个的Parent Transaction Hash是一致的,也就是说它们属于同一次交易,剩下两个不一样,因此可以判断攻击者尝试攻击了3次
1 | 答案:3 |
实际上这种问题需要每个交易都仔细分析的,各位实操的时候需要注意这一点
Q2
1 | 成功完成攻击的那次交易的交易哈希为? |
上面的图片有那么大两个红色感叹号,鼠标悬在上面就知道交易revert了,搜一下就知道是失败了,所以答案也很明显了
1 | 答案:0xa8acbabc2688eb7e57e138ac5574339e1b11ee857e6aa374c10d280578c6d9d9 |
Q3
1 | 攻击者地址为? |
如果你这里直接提交上图的From字段,那你就大错特错了,因为刚刚我们已经分析过可能出现EOA→合约→EOA的情况了,而一般交易的发起者都是EOA,因此我们需要点进交易详情查看细节:
这里的From才是实际的攻击者地址
1 | 答案:0xfF8B55e73C2F86f3011B6110Cb939238781043A9 |
Q4
1 | 由此次攻击我们可以知道受害者初始拥有若干ERC20资产,请问这些ERC20的symbol(缩写)为? |
在同一个页面我们能看到下面的东西:
这就很明显了:攻击者通过某种手段让受害者向自己发送了ERC20资产,同时用户此时没有任何的资产(如果有的话最开始的地址详情页会有写),因此答案就是这三个
1 | 答案:ATK2024_AUSDT_LINK |
Q5
1 | 攻击者利用了一个特殊的EIP实现了此次攻击,该EIP为? |
Q3截图很直白的告诉你了有个EIP-7702,先照填吧(笑)
1 | 答案:EIP-7702 |
真要写技术细节的话比较麻烦,我找个时间写篇文章或者录个视频吧(等你们看到的时候可能已经有了,如果是这样下面会有链接的)
下面的题目我尽量以通俗易懂的方式写,但是需要注意:实际的分析过程不应该是这篇Writeup这样分析的,这里的解题思路只是尽量让各位以现有的知识储备能做出来才这么写的
Q6
1 | 攻击者部署的恶意合约地址为? |
我们去攻击者的地址界面:
我们发现实际上部署了2个合约,但是最下面的那次部署后面紧跟的交易都失败了,因此是上面这个
1 | 答案:0x16Eaadd7B79d7729E5467Fe7B27782f6c195B98f |
实际上两个合约的逻辑是完全一样的,所以实际上两者都是恶意合约,但是第1次部署的出现了一点小问题,这里不展开技术细节,总之无法以当前攻击者地址进行调用并攻击
Q7
1 | 该恶意合约的某个函数会固定返回一个字符串,请问该函数名和返回的字符串分别是? |
这题需要你们自己去找工具反编译,这里使用的是EVM Bytecode Decompiler | Dedaub Security Suite
这里就只截取片段好了:
1 | // Decompiled by library.dedaub.com |
实际上我们发现只有aggregate和signature有返回值,而aggregate返回的是一个bytes,虽然也有可能是string,但是如果是string,那么它怎么用于攻击呢?而且signature有点太像了是吧
但是我们发现它返回了一个v0,而这个v0又是用了MCOPY操作码从v1复制过来的,也就是说我们的字符串是v1,且它在内存里面,因此我们推测这个字符串是一个常量const,为此我们需要查看Tac(Three-Address Code),这里给出关键片段:
1 | Begin block 0x35a |
可以看到这里有很长的一串Hex,看起来就像是我们需要的东西,From Hex一下就能得到答案了
1 | 答案:signature_Sannoba Witch by Yuzusoft |
针对
aggregate的技术分析这里不展开,你暂时只需要知道它被用来攻击了就行
Q8
1 | 已知攻击者窃取完资产后进行了访问了一个NFT抽卡网站,请问该网站使用哪种ERC20代币作为抽卡费用? |
感谢Marin第8~11小问的解题思路,他的思路更简单一些
既然是NFT抽卡网站,那么我们看后面哪里铸造了NFT就行,因此我们发现有个mint NFT的交易,点击交易的To(0x63a6437958AB091B951fe500F2EDa18A3C72C5a5),查看这个合约的Token Transfers,就可以看到使用的ERC20代币种类了
1 | 答案:AUSDT |
实际上这个合约并非一个ERC721代币合约,而是抽卡的后端逻辑+抽卡结果存储合约
Q9
1 | 每次抽卡的费用为?(以Ether为单位) |
查看攻击者发送的代币数量,发现有2个ERC20支付记录,其中一个支付了40个代币,另一个支付了4代币,因此猜测一次抽卡使用的代币数量为4
1 | 答案:4 |
Q10
1 | 该抽卡网站使用一个可验证随机函数获取随机数,请问该可验证随机函数的名字是? |
本题搜索可验证随机函数基本就能找到答案,严谨的做法这里暂且不展开
1 | 答案:Chainlink VRF |
Q11
1 | 攻击者一共尝试抽了多少张卡? |
根据Q9猜测进行了一次10连抽+一次单抽
1 | 答案:11 |
Q12
1 | 最终攻击者成功铸造了一个NFT,请问该ERC721代币的名字为? |
回到刚才的mint NFT的交易记录,可以看到创建了一个新的NFT,旁边就是它的名字
1 | 答案:Tokenyomi |
Q13
1 | 该NFT的元数据(Metadata)被保存在了IPFS上,请问元数据中的flag内容是? |
一般的,支持Metadata的ERC721合约对每个铸造的NFT都会有个tokenURI属性,该属性为一个指向包含其Metadata的json文件的URI链接,因此我们可以知道这个tokenURI会在NFT被铸造的时候被传入,因此我们需要查看此次交易中被传入的数据
展开交易详情下面的More Details:
Input Data下面有个Decode Input Data的按钮,可以解析传入的数据,点击后获取tokenURI属性即可得到Metadata所在的IPFS链接了
但是IPFS的URI是无法直接访问的,除非你有一个本地的IPFS节点或者有个IPFS服务支持你访问,因此你需要搜索一些能访问IPFS节点上数据的网站,比如IPFS Viewer - View your IPFS files with ease,输入tokenURI即可看到完整的Metadata和里面的flag
1 | 答案:Aurora{A77_1s_f4ir_in_+ag3} |
Q14
1 | 该NFT的图片上的英文内容是? |
我们发现image属性也是一个IPFS URI,再访问一下就可以了
1 | 答案:THE LEGENDARY COLA SWORD!! |
谁要是把衣服上的英文也交了我就要开猎杀了💢
Pia!(o ‵-′)ノ”(ノ﹏<。)
Q15
1 | 最后攻击者在铸造完该NFT后将此NFT转移给了另一个地址,请问该地址为? |
最后查看攻击者中的Transfer From的交易,查看NFT的转移记录即可得到答案
1 | 答案:0x68974187b774708dBb3AA03645ECA38404303b7B |
Q8-复杂版
从此部分开始为对Q8~Q11的复杂版Writeup,需要有一定智能合约的知识
不管是查看Approve的交易还是下一个神秘方法的交易,都能看到相关的代币是AUSDT,具体是什么情况这里不展开
Q9-复杂版
已知ERC20中的Approve函数是提高指定用户的ERC20使用额度的函数,那么基本可以确定下一个神秘小交易就是抽卡的那次交易,点击进去一看,发现发送了40个代币,但是我们还需要知道抽了几次卡,
我们可以猜测每生成一个随机数,其对应的就抽一次卡(当然你不确定的话可以尝试反编译合约,这里就不尝试了,反正这题是这样的),因此我们只需要知道生成了多少个随机数就行了
查看交易的日志(Logs):
我们可以看到有个RandomWordsRequested,说明这个日志告诉了我们抽卡合约请求了多少个随机数,查看Data段的的numWords,里面的结果为10,说明请求了10个随机数,即抽了10次卡,因此单次抽卡的价格为40/4=10
Q10-复杂版
根据上面的日志,我们可以知道抽卡合约是向最上面的Address(即发送事件的地址)请求的随机数,从而使得这个地址返回该事件,因此我们点击查看合约详情,发现其Contract上面有个绿色的✅,说明该合约被验证了,简单来说就是可以在上面看到合约的源码了,因此我们点进去查看有没有新的信息:
我们可以发现合约的名字叫VRFCoordinatorV2_5,有了解的同学应该能很快知道VRF就是可验证随机函数的英文缩写,但是这个名字并非VRF本身,不过我们搜索合约名字就能知道使用的VRF是Chainlink VRF了
Q11-复杂版
根据Q9,查看攻击者两次抽卡小号向抽卡合约发送的代币数量,最后除去4即可得到答案
博客Bonus——Part II的部分技术细节
说句实话,做这题没有出这题难o( ̄┰ ̄*)ゞ
上面那些主要是写给没有啥基础知识的新同学看的,但是从这里开始才是真正的技术细节,需要各位有一定的基础
其实Part II参考了8月末的一次USDT窃取攻击,详情可以参考关于收获休闲黑客松奖金后钱包被盗分析(EIP-7702) - General - LXDAO,那个时候我正好也在规划如何用7702出题,因此稍微借鉴了一下本题中攻击者的攻击手段(其实可以说是90%照搬了)
引子:EIP-7702——被打开的魔盒
知周所众,EIP-7702是一个能让EOA地址临时升级为智能钱包的EIP(中译中:让EOA地址具有智能合约的功能),它提出了一个新的交易类型0x04, 其特殊的点在于添加了一个Authorization List的部分,里面包含链ID、指向的合约地址、EOA当前的nonce,以及secp256k1签名(vrs),所以实际上使用EIP-7702对EOA进行升级是完全不需要被升级的用户发送交易的,只需要有一个签名就行了
除此之外,0x04交易中的To地址和进行EOA升级的地址不需要一致,因此就会出现类似本题的操作:攻击进行的那次交易中,攻击者向Multicall3发送了交易,并且在同个交易中将受害者升级为了智能钱包,同时由此我们又可以发现交易中是先执行的7702升级再执行的交易内容
如果用Web3py,攻击交易的构造如下面的代码所示:
1 | from web3 import Web3, HTTPProvider |
但是最恐怖的一点还是在于EOA地址升级后的危害,比如EOA地址A通过7702指向了合约地址B,那么此时A就有了合约的功能,如果我们向A发送交易,那么此时就会载入B的Runtime Bytecode,然后在A的上下文中执行
可能听起来没啥太大的问题,但是如果我们的B里面有一个这样的函数:
1 | function evil() public payable{ |
那么我们向升级后的A调用这个函数,此时我们可以将整个A视为一个合约,这个函数执行的是一个转移资金的操作,放到EIP-7702的背景下就是受害者A将自己地址上所有的ETH转给了msg.sender
当然,我们还可以更进一步,比如转移ERC20资产、ERC721资产,等等等等,但是让我们先就此打住,等过会儿再展开
题目中攻击链的解析——Multicall和EIP-7702的组合拳
在上面解题的时候,我们能看到攻击者部署的用于升级的恶意合约里面有一个aggregate函数,反编译过后是很大的一串东西,但是实际上如果你去问AI或者自己去分析,就能发现这个函数其实就是在照搬Multicall3的aggregate函数:
1 | struct Call{ |
除去交易失败后返回的信息不一样,还有最开头多了个require(防止其它人调用此函数),其它的都完全一致
Multicall简单来说就是只需要一次交易就能向不同的合约发送并执行批量的操作,而本题中的攻击线路是这样的:攻击者A调用了Multicall3合约B的aggregate,而内容是调用受害者C的aggregate,而后者的内容则是向题目中的3种ERC20调用transfer函数,转移走资金
本次完整交易大致的攻击流程图如下:
为什么用transfer呢?刚刚提到了对于EIP-7702升级后的智能钱包而言,调用钱包后的交易都将在钱包的上下文中执行,因此这次transfer的msg.sender就是钱包本身,在本题背景下就相当于受害者C向这几个ERC20调用了transfer,因此可以正常执行
后面有关Chainlink VRF和IPFS上传的技术细节就不写了,网上搜一下也都有,过段时间会将本题使用的相关代码都传到一个repo中
注:后面有关NFT抽卡的代码基本都是AI写的,我只写了D100和NFT的框架,调了整整10小时还是有一堆Bug,且对实际生产没有任何参考价值,仅仅是我拿来出题用的,而我的要求是能把题出出来就行,所以…
人格解离
感觉账户抽象这个概念就挺人格分裂的…
预期难度:5.0⭐
Docker镜像:cauliweak9/7702:latest
考点
EIP-7702
Writeup正文
相信各位通过前面的题目(Æmeistralitheum——公开链溯源)已经对EIP-7702有一个初步的了解了,而这题其实就是让各位利用EIP-7702实现一次简单的钓鱼攻击,从而简单复刻公开链溯源中攻击者的攻击
但是首先,让我们先来了解一下EIP-7702到底是个什么吧!
Appetizer:EIP-7702速通
简单来说,EIP-7702是一个能让EOA地址具有智能合约功能的EIP,其相较于我们常发送的交易(EIP-1559)只新增了一个authorization_list参数,而这个参数就是让EOA地址获得智能合约功能的关键点
根据官方文档,我们可以知道authorization_list包含如下的信息:[chain_id, address, nonce, y_parity, r, s],其中最后面的3个都是签名参数,因此核心就只有如下的三个参数:
chain_id:当前的链IDaddress:指向的合约地址nonce:EOA地址当前的nonce
而我们需要对这三个参数进行签名,然后将得到的完整的authorization_list传入交易中即可,一个简单的Web3py的例子可以参考这篇文章:web3.py Patterns: EIP-7702 Explainer,可以看到想要让一个EOA升级为智能合约,只需要那个EOA的一个签名即可
变成智能合约后,我们如果向这个EOA地址发送交易,那么这个EOA会使用它所指向的合约地址的代码,在自己的上下文中运行,而我们正可以利用这一点“借刀杀人”:让升级为了合约的EOA自己发起transfer交易,从而实现对EOA地址上资产的窃取
顺带一提:交易的目的地址
destination和进行升级的EOA地址并不需要一致,且对EOA的升级要先一步执行,即先升级再执行交易细节
Main course:构造authorization_list
想利用EIP-7702进行攻击,那么首先我们需要部署一个我们自己的攻击合约,比如本题中我们使用如下的合约:
1 | // SPDX-License-Identifier: MIT |
这样我们升级之后向受害者调用attack()就能完成ERC20的资金窃取了
部署完成后,我们就需要搞到authorization_list的签名,坏消息是我们并没有受害者的私钥,好消息是受害者会对你传入的数据进行签名,并返回签名的数据(vrs),那么接下来就是构造消息用于签名了
根据官方文档,我们需要的消息msg = keccak(MAGIC || rlp([chain_id, address, nonce]),而MAGIC是一个固定的值0x05,因此我们有如下的Python代码:
1 | import rlp |
构造好了之后我们将得到的hex拿去签名就能拿到vrs三个参数了,而v和y_parity的关系是y_parity=v-27,同时为了方便我们进行后面交易的构造,我们需要翻一下eth_account和eth_keys这两个库来构造我们的authorization_list:
1 | from eth_account.datastructures import SignedSetCodeAuthorization |
其实上面这两步就等价于下面的操作(如果你有私钥的话):
1 | victim_account = Account.from_key("...") |
最后得到的signed_auth是完全一致的
Dessert:签署交易,完成攻击
最后就是愉快的交易签署环节了,由于EIP-7702的升级和交易内容不冲突,且升级优先级更高,因此我们可以在一次交易中完成攻击:authorization_list中传入signed_auth,然后交易的data则是一个向受害者调用attack()函数的calldata,即下面的代码:
1 | abi = [ |
自此,我们就成功利用EIP-7702对受害者发起攻击了,相信各位对这个非常新的EIP有一个初步的认识了吧~
据统计,目前80%以上的EIP-7702交易都是恶意攻击,因此请各位在对消息进行签名的时候小心小心再小心
最后附上本题完整的Python代码:
1 | from web3 import Web3, HTTPProvider |
后记
一定要说的话,整个Project: Syndallion系列题目的设计其实在舞萌那边出冰灭135的时候就已经在筹备了,最开始的计划是出一道区块链的入门系列题,外加上一个Web3取证(首先是和区块链有关,其次当时打FIC的时候做到了,也是借着信息差侥幸吃到了大量的分),但是后面越出越多,最后就变成了现在这样(笑)
下面就随便写点题目框架的开发后记吧
“千链万花”开发后记
整个靶机开发的过程其实波折很多,包括但不限于后端题目部署中forge script的账户余额问题、Anvil区块链RPC的异常、前端各种数据传输的问题等,这里简单吐槽几个印象比较深刻的
1. 双助记词问题
一开始进行测试的时候,发现创建anvil时使用的助记词和题目部署时使用的助记词并非同一个,最终导致运行forge script的时候账户余额不足,这个问题卡了我许久,最后和Caterpie师傅经过不断的调试后发现是Flask的问题,没记错的话好像是如果Flask处于debug mode,那么发生500的时候会创建一个新的实例,而当时测试的时候就是创建anvil和后面题目部署时对应的实例不一致,从而导致出现了类似平行世界的情况
谢谢你,Flask,我[CENSORED]
2. RPC宕机问题
目前暂不清楚是否已经解决,总之就是本地调试的时候有时会出现RPC彻底无响应的情况,导致500错误,在内部靶场进行实际测试的时候这种问题被放大,于是根据Github issues设置了出块时间1s,但并未有改善,最后无可奈何编写了一个/debug路由并实时往/tmp/anvil.log写入anvil的运行日志,但是从此RPC再也没有宕过机,具体原因尚未调试出来
计算机玄学说是,观察者效应
3. polyfill投毒
由于签名部分的介绍使用了LaTeX,因此想使用MathJax组件实现LaTeX渲染,同时有去找polyfill组件,但是了解组件的过程中发现这个js被投毒了,据说会直接跳转到一个越南的赌博网站,吓得我直接去github找了一个完全OK的polyfill搞了下来,不过前端用没用上这个我不知道
又是开源项目投毒,想到AList…啧啧啧…
剩下的就不说了,实际上整个靶机的设计和开发都是本人配合DeepSeek完成的(后端和foundry相关的代码则是参考了paradigmCTF infra手改的,感谢RemedyCTF的repo帮助我更好理解这个infra),可能题目质量和靶机质量都算不上多好,但是总的来说我个人比较满意,当然有问题欢迎拷打,我绝对立正挨打
在此感谢Zer0师傅提供的出题思路,Caterpie师傅在Python方面的技术指导,qsdz师傅在合约方面的指导和建议,paradigmCTF和RemedyCTF提供的靶机框架,
舞萌DX提供的灵感来源(潘盒二代:Kaleidxscope),以及柚子厨纸鸽师傅提供的题目名称灵感
“Æmeistralitheum”开发后记
Part I本意就是一个简单的Python木马分析,但是开始实现题目的时候刚好发现满世界都在报道WinRar的CVE漏洞,而且网上有现成的PoC,感觉加进题目里面难度也不会提高太多,而且也比较贴近现实,因此就顺带叠了一层
当然,话是这么说…事后发现了3个预料之外的难点…
1. 恶意文件的解压
当时Adrian师傅测题的时候,发现不管怎么样都无法将木马解压出来,不过我确实有一定心理准备就是了
其实根据对CVE的描述,基本可以推断就是WinRar中对文件解压路径的解析出现了问题,而如果是这样的话我们就可以直接用010去Pad掉文件名里面恶意构造的目录穿越,然后修改CRC让WinRar能正常识别数据块就行了,当然其实最简单的方法就是新建一个名字是10347的用户,然后用这个用户解压就行了(
2. 加密流量的数据提取
最方便的肯定是tshark,但是经过测试,我发现tshark在提取的过程中会缺失3个流量,最终导致在对Metamask日志的解密时因为数据丢失导致无法正常Zlib解压
当然,如果你用scapy或者其它工具那当我没说
3. Python字节码反编译错误
最大最恶劣的未预想到的难点,网上已有的工具/网站里面好像只有PyLingual能进行反编译,而它又在最关键的握手函数里面出现了反编译错误,还好巧不巧是计算session_key的部分,最离谱的是这个反编译错误有两个,很容易就忽略了另一个,闹麻了
这种东西基本上就是要你去硬看CPython源码的,你问GPT5说不准有戏,但是新生赛欸,小登会用Deepseek就不错了,而且DS我试过,问不出来,这也是为什么Part I的预期难度非常“顺利”地上了7⭐(笑)
Part II相比Part I要友善的多,主要是整个溯源过程你不好找问题去问,难点反倒是在怎么出这道题上,有关EIP-7702的交易细节实现我尝试了不下十次,不过最后发现其实难度也没有多大,这是好事,但也是坏事,因为7702如果这么好用,那么恶意攻击者可就有福了
其实难点反倒不在7702的实现,而是NFT抽卡的实现,是的你没看错,因为我脑子一热想搞一个实际的前端和后端,而且我确实这么做了(虽然大部分都是Deepseek写的,我不会设计前端UI),但是经过了连续10个小时的调教,最后的代码还是有一堆Bug…
1. 逆天的数据存储
一般来说每个用户的抽卡数据应该是存储在后端的,比如SQLite数据库里面,再不济为了出题的话存在session里?全局数组里?但是由于我只是写了个抽卡逻辑的框架给了DS,结果导致DS把抽卡的数据存在了tmd智能合约上,这诗人啊?光是那gas就足够逆天了,更别说最后的代码在存储十连抽的时候还出错了(这也是为什么你们看到我请求完10个随机数后那次交易revert了,那次交易是DS写的抽卡数据存储操作),神了,但是单抽没啥问题,算了,随缘吧,反正你们也看不到(笑)
2. 获取用户的抽卡记录
我为了模拟现实场景,让DS在我的抽卡操作基础上能实时获取用户曾经的NFT抽卡记录,以及用户已经铸造的NFT,而后果是…
tmd自从抽完卡生成完随机数后,每次登录页面我Metamask都要弹出好几个交易确认
我靠我请求抽卡数据用个view函数不就行了?而且你获取用户未完成的铸造记录也可以啊,但是不也是应该搞成view函数吗?你几个交易是想干什么?诗人啊?
总之还有很多很多Bug,但是我懒得修了,能把题目出出来已经是胜利了,起码它能真的把我的metadata和图片上传到IPFS上,这就足够了ο(=•ω<=)ρ⌒☆
“人格解离”开发后记
没啥好吐槽的了,一切都是基于“千链万花”靶机进行二次开发的,前端UI靠DS,前后端交互和后端合约部署就自己写,照搬自己魔改的后端逻辑,最后我觉得靶机的效果比“千链万花”好一些,因此我又让AI删掉了签名的界面,这样就是一个不出网的区块链靶机模板了,之后找个时间push到repo上吧
当然还是会有一些小bug,比如Markdown渲染的时候没法渲染引用块(>),但是无伤大雅,不用就行了
其实原计划里面没有这道题目的,但是出这道题的时候我们的Kaleidxscope都完结快2个月了,发现想对应上的话缺个包饺子的题,因此紧急搓了这个题目www
附录(唉舞萌痴)
最后的最后在此给出本系列题目以及流程中相关题目和Kaleidxscope的对应关系(我知道我是wmc,别笑我,如果有不恰当的对应你们可以随意吐槽www)
- Stolen USDT (from AuroraCTF2024) → Cryptarithm
- 千链万花——Stage 1 → 青の扉
- 千链万花——Stage 2 → 紫の扉
- 千链万花——Stage 3 → 赤の扉
- 千链万花——Stage 4 → 黒の扉
- 千链万花——Stage 5 → 白の扉
- 千链万花——Stage 6 → 黄の扉
- 千链万花——Stage 7 → PRiSM塔
- 千链万花——Stage 8 → 縺セ縺ク縺、縺昴d縺ク繧
- 哈急迷 (Made by Marin) → 希望の扉
- Æmeistralitheum → Xaleid◆scopiX
- 人格解离 → Ref:rain (for 7th Heaven)
在此感谢所有在我进行本系列题目设计、开发与测试时提供帮助的师傅们(按各题目的时间先后排序):
- Zer0(“千链万花”考点设计灵感与测试)
- Caterpie771(“千链万花”靶机开发)
- qsdz(“千链万花”题目测试)
- Tokoyomi(“千链万花”题目测试)
- Adrian(“Æmeistralitheum” Part I题目测试)
- Marin(“Æmeistralitheum” Part II题目测试,“Æmeistralitheum”靶机测试)
Ref:rain太好听了,都给我去听(o゜▽゜)o☆,GCDEFEDC这块的情感调动满分
还有,别问为什么我的PRiSM塔和六门一起解禁了,问就是我也不知道
还有个问题:谁家白XX在最开头放大招的,这不是我们sans的梗吗(
- 本文作者: 9C±Void
- 本文链接: https://cauliweak9.github.io/2025/07/04/AuroraCTF2025-Misc-WP/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!
我爹
我E神
式神
三极管全能神
Marin