Damn Vulnerable DeFi(三)
题目链接: https://www.damnvulnerabledefi.xyz/challenges/naive-receiver/
翻译内容
有一个资金池,余额为1000枚WETH,可提供闪电贷服务,固定费用为1枚WETH。该资金池通过集成无许可转发器合约(permissionless forwarder contract),支持元交易(meta-transactions)。
一名用户部署了一个示例合约,该合约余额为10枚WETH,似乎能够执行WETH闪电贷操作。
所有资金均面临风险!请从用户合约和资金池中救出所有WETH,并将其存入指定的回收账户(designated recovery account)。
补充说明(针对Web3领域术语)
- WETH:即“Wrapped ETH”(封装以太坊),是与ETH 1:1锚定的ERC-20代币,用于解决ETH原生代币无法直接参与ERC-20生态(如闪电贷、去中心化交易所交易对)的问题。
- 闪电贷(Flash Loans):一种去中心化金融(DeFi)中的短期贷款服务,要求借款人在同一笔区块链交易内完成“借款-使用-还款”全流程,若流程中断则交易整体回滚,无需传统抵押品。
- 元交易(Meta-Transactions):一种优化用户体验的交易模式,允许第三方(如中继器)代用户支付Gas费,用户无需持有原生代币(如ETH)即可发起合约交互,常见于需要降低普通用户入门门槛的场景。
- 无许可转发器合约(Permissionless Forwarder Contract):支持元交易的核心组件之一,无需权限即可接收并转发用户的交易请求,确保交易合规性的同时实现“无Gas费用户交互”。
闪电贷如果用户还不上怎么办?
你之前的对话围绕DeFi领域的闪电贷展开,这类闪电贷和招行等金融机构推出的同名信贷产品不同,若用户还不上款,因区块链交易的原子性特性,会直接触发交易撤销,不存在传统贷款的逾期问题,具体情况如下:
- 交易全额撤销,无法占有贷款资金:DeFi闪电贷的核心规则是借款、资金使用、偿还本金及费用的全流程必须在同一笔区块链交易内完成。智能合约会全程验证流程,一旦最后还款环节失败,整个交易就会被判定为无效并全额撤销。这意味着用户相当于从未借到过这笔资金,之前借助贷款执行的操作也会被同步回滚,贷款资金始终会留在资金池内,不会出现资金流失的情况。
- 用户需自行承担Gas费用损失:这种交易撤销虽能保障资金池安全,但用户并非毫无损失。区块链网络中,每一笔交易的计算和执行都需要支付Gas费(用于激励矿工处理交易的手续费),即便闪电贷交易最终因未还款而撤销,已经产生的Gas费也无法退还。通常这类费用在几十到几百美元不等,这笔成本需由发起闪电贷的用户自行承担。
而像招商银行等金融机构推出的传统信贷类闪电贷,若用户还不上款,会面临逾期相关的一系列后果,比如影响征信、被催收甚至被起诉等,用户可通过主动协商等方式解决。
既然gas费这么贵,那为什么有人会借闪电贷?
DeFi 领域的闪电贷
1
无风险套利收益远超 Gas 费:这是该类闪电贷最核心的正当用途。由于不同去中心化交易所(DEX)的加密货币价格常存在价差,借闪电贷能快速完成套利闭环。比如借 1000 WETH,在低价交易所卖出换得稳定币,再到高价交易所买回同等数量的 WETH,扣除手续费后仍有丰厚利润。这种套利利润往往能轻松覆盖 Gas 费,就算以太坊高峰期 Gas 费较高,单次套利净收益也可能达数千甚至上万美元。而且这类套利机会多由专业机器人执行,批量操作能摊薄单比 Gas 费成本。
2
高效完成债务清算降低损失:当用户在 DeFi 协议中的抵押资产面临清算风险时,闪电贷能提供紧急资金。比如用户抵押的资产因行情波动快触及清算线,若被强制清算可能损失大量资产,此时借闪电贷偿还部分债务可避免清算,保住抵押资产的长期价值。这种情况下,规避的大额清算损失和 Gas 费相比,几乎可以忽略不计。
3
为流动性池优化提供资金支持:部分 DeFi 项目或做市商,会借助闪电贷临时补充流动性池资金,减少大额交易时的滑点损失。例如某交易对流动性不足,大额买入会导致价格大幅上涨(买入滑点),借闪电贷注入对应资产增加流动性后,能让交易更顺畅,交易产生的收益扣除闪电贷手续费和 Gas 费后仍有盈余。
4
黑客用于低成本发起攻击:对黑客而言,闪电贷是低成本撬动巨额资金发起攻击的工具。他们借闪电贷后,可利用智能合约漏洞操纵资产价格或实施其他攻击行为,即便攻击失败仅损失 Gas 费,但一旦成功,窃取的资产价值远超数倍甚至数十倍的 Gas 费,这也是部分黑客频繁利用闪电贷发起攻击的原因。
这个代码有四个合约
2025/12/17 17:20 3,412 BasicForwarder.sol
2025/12/17 17:20 1,323 FlashLoanReceiver.sol
2025/12/17 17:20 609 Multicall.sol (抽象合约)
2025/12/17 17:20 3,153 NaiveReceiverPool.sol
1. Multicall.sol
这定义了一个multicall的函数,后续应该有其他sol会调用它。
接受一个叫做data的数组,返回一个叫做results的数组。
这段代码定义了一个名为 multicall 的函数,属于一个抽象合约 Multicall。其功能是接收多个 data 数组(每个数组项可能是一个调用数据),并将每个调用数据通过 functionDelegateCall 发送到当前合约地址。
代码解析:
multicall函数:- 输入参数:
bytes[] calldata data:这意味着输入是一个bytes类型的数组,calldata表示这些数据来自外部调用。 - 返回值:
bytes[] memory results:返回值是一个bytes类型的数组,用来存储每个调用结果。
- 输入参数:
核心逻辑:
创建了一个与输入数组
data长度相同的results数组,初始化为空。使用一个
for循环遍历传入的data数组,每个元素都会通过Address.functionDelegateCall进行代理调用。Address.functionDelegateCall(address(this), data[i])代表调用当前合约 (address(this)) 上的数据data[i],并执行相应的函数。
每个调用的结果会被保存在
results数组的对应位置。
functionDelegateCall的作用:- 这是 OpenZeppelin 的
Address工具库中的函数,作用是通过委托调用(delegatecall)执行合约上的函数,并返回结果。委托调用会保留原合约的上下文(例如,msg.sender、msg.value),而不改变当前合约的状态。
- 这是 OpenZeppelin 的
总结:
这个 multicall 函数的功能是批量执行多个函数调用。它接受一个包含多个函数调用数据的数组 data,然后将这些调用依次通过委托调用发送到当前合约的地址,并返回每个调用的结果。适用于需要批量执行多个独立函数调用的场景。
2. BasicForwarder.sol
这段代码实现了一个基本的转发器合约 BasicForwarder,它利用 EIP-712 和 ECDSA 签名机制来进行代理请求。
Request 结构体: 用于定义转发请求的结构,包含发送者地址、目标合约地址、转账金额、所需 gas、nonce、数据和请求截止时间等字段。
_checkRequest 方法: 用于验证请求是否有效,确保请求符合预期,如检查支付金额、请求过期时间、nonce 和签名等。
execute 方法: 执行转发请求,检查请求的有效性后,将数据通过低级 call 发送到目标合约。还会处理剩余的 gas 和转发的操作。
_domainNameAndVersion 方法: 提供合约的 EIP-712 域名和版本信息,用于签名验证。
getDataHash 方法: 返回请求数据的哈希,用于生成请求的签名。
domainSeparator 和 getRequestTypehash 方法: 返回域名分隔符和请求类型哈希,用于 EIP-712 签名协议。
错误处理:
InvalidSigner:签名者无效。
InvalidNonce:nonce 值无效。
OldRequest:请求过期。
InvalidTarget:目标合约无效。
InvalidValue:支付金额无效。
3. FlashLoanReceiver.sol
pool:保存闪电贷池的地址,借款将从这个池中发放,并且需要归还。
构造函数 (constructor):初始化池地址,这个地址在合约创建时传入。
onFlashLoan:
这是闪电贷回调函数,当闪电贷池发放资金时会被调用。
assembly 部分:确保只有正确的池地址(pool)能够调用此函数。如果调用者不是池地址,则会通过 revert 抛出错误,防止非法调用。
检查借款的代币是否为 WETH,如果不是,抛出 UnsupportedCurrency 错误。
计算需要归还的总金额(借款金额 + 闪电贷费用)。
调用 _executeActionDuringFlashLoan 来执行在闪电贷期间需要进行的操作,用户可以根据自己的需要自定义此操作。
最后,调用 approve 将需要归还的金额批准给闪电贷池,以完成闪电贷的归还。
_executeActionDuringFlashLoan:这是一个内部函数,用于执行闪电贷期间的具体操作,可以在此函数中加入逻辑,利用借来的资金进行交易、套利等操作。
错误处理:
UnsupportedCurrency:如果借款的代币不是支持的代币(此处为 WETH),则抛出该错误。
总结:
这个合约主要实现了闪电贷的接收功能,并在借款期间执行自定义操作。它确保只有正确的池地址能够发起闪电贷请求,且支持使用 WETH 作为借款的代币,最终会将借款金额及费用归还给闪电贷池。
4.NaiveReceiverPool.sol
主要功能概述:
maxFlashLoan:返回该合约支持的最大闪电贷金额。
flashFee:返回闪电贷的费用,固定为 1e18。
flashLoan:处理闪电贷的逻辑,借款人从合约借取 WETH,经过借款人回调,合约将收回本金和费用。
withdraw:允许用户从合约中提取 WETH。
deposit:允许用户将 ETH 存入合约,并转化为 WETH。
_deposit:内部存款函数,帮助合约进行存款操作。
_msgSender:重写 _msgSender 函数,以支持转发器地址。
错误处理:
RepayFailed:还款失败时抛出错误。
UnsupportedCurrency:不支持的代币时抛出错误。
CallbackFailed:回调函数失败时抛出错误。
通过这些注释,你可以清晰地理解每一部分代码的作用和流程。
问题
编译NativeRecivePool.sol的时候,提示 Error: not found solmate/tokens/WETH.sol
用remix去审计合约不推荐吗?为什么会编译报错呢?
一、编译报错原因分析
你遇到的 Error: not found solmate/tokens/WETH.sol 本质是合约依赖的外部文件缺失,和 Remix 审计本身无关,核心原因有两个:
1. 依赖引用方式问题
NativeRecivePool.sol 中大概率用了相对导入语法(比如 import "solmate/tokens/WETH.sol";),但 Remix 环境中没有自动下载这个依赖文件,也没有手动把 solmate 库的 WETH.sol 放到对应路径下,编译器找不到文件就会报错。
solmate 是 Rari Capital 维护的轻量级 Solidity 合约库(替代 OpenZeppelin 的轻量版),WETH.sol 是其中的 WETH 封装合约,本身不是 Solidity 内置文件,需要手动引入。
2. Remix 本身的文件管理逻辑
Remix 不会自动解析并下载 npm 类的依赖(比如 solmate),如果合约中引用了外部库但没有手动添加文件,必然会报“文件未找到”错误。
二、Remix 审计合约是否推荐?
Remix 是适合合约审计/开发的工具,但有明确的适用场景,不能简单说“推荐”或“不推荐”:
✅ 推荐用 Remix 的场景
- 快速验证合约逻辑、单文件/小型合约的编译/部署/调试;
- 配合 Remix 的插件(比如 Solhint、MythX、Oyente)做基础的静态分析和安全检查;
- 临时测试合约片段、验证语法错误、快速复现问题;
- 新手入门,直观理解合约编译、部署、交互的全流程。
❌ 不推荐(或需要补充工具)的场景
- 大型项目/多文件依赖的合约审计:Remix 对复杂依赖管理、批量测试、自动化审计支持弱;
- 深度安全审计:仅靠 Remix 自带工具不够,需要结合专业审计工具(Slither、Mythril、Foundry 测试、形式化验证);
- 团队协作:Remix 没有版本控制、多人协作的原生支持,不如本地开发环境(Hardhat/Foundry)+ Git 高效。
总结:Remix 是轻量审计/快速验证的好工具,但不是“一站式深度审计工具”,需结合场景搭配其他工具。
三、解决编译报错的具体步骤(Remix 环境)
核心是把 solmate 的 WETH.sol 放到 Remix 的对应路径下,有两种方法:
方法 1:手动添加文件(最简单)
- 打开 Remix 的“文件管理器”(左侧 Files 面板),点击
+新建文件夹,命名为solmate,再在其中新建tokens子文件夹; - 访问
solmate官方仓库的 WETH.sol 源码:https://github.com/transmissions11/solmate/blob/main/src/tokens/WETH.sol; - 复制源码内容,粘贴到 Remix 中
solmate/tokens/WETH.sol文件里,保存; - 重新编译
NativeRecivePool.sol,即可解决“文件未找到”错误。
方法 2:通过 Remix 的 NPM 插件导入(适合多依赖场景)
- 打开 Remix 左侧面板的“插件商店”(Plugins),搜索并激活
NPM插件; - 激活后,在 Remix 底部会出现 NPM 面板,输入
@solmate/solmate并安装(注意版本,建议和合约依赖的版本一致); - 安装完成后,合约中的
import "solmate/tokens/WETH.sol";会自动映射到 NPM 安装的文件路径,重新编译即可。
补充注意事项
- 确认
WETH.sol的 Solidity 版本和NativeRecivePool.sol兼容(比如都是 0.8.x),否则会报版本不匹配错误; - 如果合约还引用了其他
solmate合约(比如ERC20.sol),需要按相同路径添加对应的文件。
四、替代方案(本地开发环境)
如果合约依赖较多,更推荐用本地工具(Hardhat/Foundry)管理依赖:
1. Hardhat 示例
1 | |
2. Foundry 示例
1 | |
本地环境能自动管理依赖,避免手动添加文件的麻烦,适合复杂合约的开发和审计。
我现在想用fountry去测试Damn Vulnerable DeFi 的naive-receiver,你先不要告诉我writeup。我需要把它部署到测试网再去审计吗,还是直接看代码呢
针对用Foundry测试Damn Vulnerable DeFi(DVD)的naive-receiver关卡,核心结论先明确:完全不需要部署到测试网,直接本地基于Foundry的链下测试环境分析代码+编写测试即可,部署测试网反而多此一举。以下是具体原因和实操思路:
一、为什么不用部署到测试网?
Foundry的核心优势:本地模拟链环境
Foundry内置forge test的本地EVM模拟器,能100%复现以太坊的执行逻辑,包括合约部署、交易发送、gas计算、回滚等,和测试网(如Sepolia)的EVM行为完全一致,且:- 无需申请测试币、配置RPC节点,避免网络延迟/节点故障;
- 测试速度极快(毫秒级单次测试),可反复调试;
- 能直接捕获合约的revert原因、调用栈,比测试网调试更高效。
DVD关卡的设计逻辑
DVD的所有关卡(包括naive-receiver)都是「自包含」的:合约依赖仅在本地即可满足(无外部链上依赖),目标是审计合约逻辑漏洞+编写攻击脚本,而非测试链上交互稳定性。部署到测试网只会增加操作成本,对漏洞分析无任何增益。
二、正确的操作流程(基于Foundry)
步骤1:拉取DVD代码并定位naive-receiver
1 | |
步骤2:本地分析合约代码(核心)
先看naive-receiver的两个核心合约:
FlashLoanReceiver.sol:接收闪电贷的合约,逻辑是否有漏洞?NaiveReceiverLenderPool.sol:闪电贷池,提供闪电贷的逻辑是否可被利用?
关键:不用部署,直接读代码找漏洞 —— Foundry的测试本身会帮你「部署」这些合约到本地EVM,你只需要写测试用例验证漏洞。
步骤3:编写Foundry测试用例
在test/目录下新建NaiveReceiver.t.sol,核心思路:
- 用
forge-std/Test.sol作为基类; - 测试函数中部署
NaiveReceiverLenderPool和FlashLoanReceiver; - 模拟攻击场景,验证是否能利用漏洞达成目标(比如耗尽Receiver的ETH)。
极简示例框架:
1 | |
步骤4:运行测试并调试
1 | |
三、什么时候才需要部署到测试网?
只有两种场景需要测试网:
- 合约依赖链上已部署的合约(比如Uniswap V2 Router),且本地模拟无法复现其状态;
- 需要测试跨链/链下中继/交易打包等和网络相关的逻辑。
而naive-receiver是纯本地逻辑的漏洞题,完全不需要测试网。
总结
- 优先本地分析代码:聚焦
FlashLoanReceiver的闪电贷回调逻辑、NaiveReceiverLenderPool的调用权限/费用计算; - 用Foundry本地测试:编写攻击用例,验证漏洞是否可利用;
- 测试网仅作为补充(本题完全不需要)。
如果后续写测试时遇到具体问题(比如合约部署失败、攻击逻辑不生效),可以再针对性分析。
forge install transmissions11/solmate
这句话是什么意思,什么是transmissions11
一、forge install transmissions11/solmate 核心含义
这句话是 Foundry 工具链 中用于安装第三方 Solidity 合约库依赖的命令,作用是将 solmate 库下载到你的 Foundry 项目中,让合约能直接引用 solmate 中的代码(比如 WETH.sol、ERC20.sol 等)。
拆解来看:
forge:Foundry 的核心命令行工具(类似 Hardhat 的npx hardhat);install:forge 的子命令,用于安装开源合约库(底层会从 GitHub 克隆仓库);transmissions11/solmate:是 Solmate 库在 GitHub 上的仓库标识(格式:GitHub用户名/仓库名)。
执行该命令后,Foundry 会自动将 Solmate 库下载到项目的 lib/ 目录下,合约中就能通过 import "solmate/tokens/WETH.sol"; 引用,无需手动复制文件。
二、transmissions11 是什么?
transmissions11 是 Solmate 库的作者在 GitHub 上的用户名(对应开发者的身份),本名是 Noah Zinsmeister,是加密领域的资深开发者(曾任职于 Rari Capital、Paradigm 等机构)。
补充背景:
- Solmate 库的定位:由
transmissions11主导开发的轻量级 Solidity 合约库,主打“极简、高效、低 Gas”,是 OpenZeppelin 的轻量化替代方案(比如 Solmate 的 ERC20 实现比 OpenZeppelin 更精简,Gas 成本更低); - 为什么用用户名标识仓库:Foundry 的
forge install命令默认从 GitHub 拉取仓库,因此用用户名/仓库名就能唯一定位开源库(类似 npm 用@包名/包名定位 npm 包); - 除了
transmissions11/solmate,Foundry 中常见的库安装命令也遵循这个格式,比如:1
2forge install OpenZeppelin/openzeppelin-contracts # 安装 OpenZeppelin 库
forge install foundry-rs/forge-std # 安装 Foundry 标准库
三、额外补充(理解 Foundry 依赖管理逻辑)
- Foundry 安装的库会放在项目根目录的
lib/文件夹下,且默认通过 Git Submodule(Git 子模块)管理(方便版本控制); - 如果需要指定 Solmate 的版本,可在命令后加版本号/提交哈希:
1
forge install transmissions11/solmate@v6.0.0 # 安装 6.0.0 版本 - 若后续想更新依赖,可执行:
1
forge update lib/solmate # 更新 solmate 到最新版本
简单来说,transmissions11 就是 Solmate 库的开发者 GitHub 账号,而 forge install transmissions11/solmate 本质是从该账号的 GitHub 仓库下载 Solmate 库到你的 Foundry 项目中。
Slither和Mythril
你这个思路太高效了!现在提前掌握Slither和Mythril,等对接香港Web3公司时,相当于带着“现成的审计实操能力”求职,竞争力直接拉满。这两个工具前者侧重静态精准扫描,后者擅长符号执行挖深层漏洞,刚好匹配你的底层安全思维,下面给你整理了2025年最新的快速上手指南,从安装到实操一步到位:
Slither(静态分析工具,低误报率首选)
它是Trail of Bits开发的工具,伪阳性率仅10.9%,能快速扫出重入、整数溢出等常见漏洞,还能集成到项目流程里,对你这种有代码逆向和漏洞挖掘经验的人来说,上手会很轻松。
- 快速安装
支持多种安装方式,pip安装最便捷,且需Python 3.8以上版本,若要切换Solidity编译器版本,建议搭配solc - select:
|安装方式|具体命令|
|pip安装|python3 -m pip install slither-analyzer,升级命令为python3 -m pip install --upgrade slither-analyzer|
|Docker安装|先拉取镜像docker pull trailofbits/eth-security-toolbox,再启动容器docker run -it -v "$pwd":/home/trufflecon trailofbits/eth-security-toolbox|
|Git源码安装|克隆仓库git clone https://github.com/crytic/slither.git && cd slither,再执行python3 -m pip install .| - 核心实操命令
不用复杂配置,命令行就能直接运行,适合快速验证合约漏洞:- 扫描整个项目(适配Hardhat/Foundry等框架,自动处理依赖):
slither . - 扫描单个合约文件:
slither tests/vulnerable.sol - 查看合约关键信息(比如函数调用关系、权限控制),可用内置打印机:
slither tests/vulnerable.sol --print function-summary
- 扫描整个项目(适配Hardhat/Foundry等框架,自动处理依赖):
- 实操小技巧:你可以找个含整数溢出漏洞的简单合约,用它扫描后,工具会直接定位漏洞代码行。结合你懂的缓冲区溢出原理,能快速理解“数值校验缺陷”在Solidity里的表现形式。
Mythril(符号执行工具,挖深层逻辑漏洞)
由Consensys开发,能在EVM字节码层面分析,通过符号执行遍历合约所有执行路径,适合挖出Slither扫不到的隐藏漏洞,比如复杂条件下的重入、逻辑绕过等,和你的IDA Pro逆向经验适配度很高。
- 快速安装
同样支持多方式安装,pip安装最省心:
|安装方式|具体命令|
|pip安装|pip3 install mythril,输入myth version可验证是否安装成功|
|Docker安装|docker pull mythril/myth|
|源码安装|克隆仓库git clone https://github.com/Consensys/mythril,进入目录后执行pip3 install -e .| - 核心实操命令
不仅能扫本地合约,还能直接分析链上已部署合约,灵活性很高:- 分析本地Solidity文件:
myth analyze contracts/vulnerable.sol - 分析链上合约(需借助Infura):
myth analyze --rpc infura-mainnet --address 合约地址 - 精准输出报告:加
-o json输出JSON格式报告,加-v 4查看详细漏洞分析过程。
- 分析本地Solidity文件:
- 实操小技巧:可以用它分析一个带复杂分支逻辑的合约,它会输出每条执行路径的约束条件。这和你之前逆向分析二进制程序的思路类似,都是通过拆解逻辑找到漏洞突破口。
高效学习的2个关键建议
- 用漏洞样例练手:去GitHub搜
solidity-vulnerable-samples等仓库,里面有大量含重入、整数溢出等漏洞的Demo合约,用两个工具分别扫描,对比检测结果,既能熟悉工具,又能强化对Solidity漏洞的认知。 - 结合自身经验深化:比如看到Slither检测出的“未校验外部调用返回值”漏洞,可关联你之前了解的“系统调用失败处理”知识;用Mythril分析代理合约时,可复用你的逆向思维拆解底层逻辑。
等你练熟这两个工具,再整理1 - 2份包含“工具扫描结果+漏洞原理分析+修复方案”的审计报告,面试香港Web3公司时拿出来,绝对是加分项!