为何在构造函数中执行重入攻击失败?
嘿,我来帮你一步步分析为什么你的攻击合约fallback函数完全没触发的问题:
先确认漏洞合约的提款逻辑顺序
很多新手踩坑的点是:如果漏洞合约在调用msg.sender.call{value: amount}()之前,就已经把balance[msg.sender]清零了,那就算后续call触发了fallback,攻击合约再调用提款函数时,会因为余额为0而被拒绝。不过你说fallback完全没触发,那更可能是下面的原因,但先确认这个逻辑顺序很重要。检查攻击合约的Fallback/Receive函数定义是否正确
Solidity对接收ETH的函数有严格要求:- 如果你用的是Solidity 0.6.x及更早版本,必须定义
function() external payable作为fallback函数; - 0.7.x及之后版本,要区分
fallback() external payable(处理没有匹配到函数签名的调用+接收ETH)和receive() external payable(专门处理单纯的ETH转账)。
如果你的攻击合约里的fallback函数没有加payable修饰符,或者写法不符合版本要求,那么当漏洞合约用call转ETH过来时,会因为函数无法接收ETH而直接失败,fallback自然不会触发。
- 如果你用的是Solidity 0.6.x及更早版本,必须定义
确认你执行了正确的攻击流程
重入攻击的前提是:攻击合约必须先向漏洞合约存入ETH,让balance[msg.sender]有值,然后再调用提款函数。如果跳过了存款步骤,漏洞合约里你的余额为0,提款逻辑根本不会执行msg.sender.call,fallback当然不会被触发。检查
msg.sender.call的调用细节
虽然msg.sender.call()默认会附带2300 gas的stipend,但如果漏洞合约里的调用是msg.sender.call{value: amount}("")(或者没有正确传递value),或者amount为0,那也不会触发fallback函数——因为只有当有ETH转账(amount>0)且没有匹配到函数签名时,才会触发fallback/receive。另外,如果你在Solidity 0.8.x版本里用了call但没有处理返回值,虽然不会影响触发,但如果call失败了,你可能没察觉到。
举个例子,正确的攻击合约receive函数写法(Solidity 0.8.x)应该是这样的:
// 专门处理ETH转账的receive函数,也可以用fallback,不过receive更明确 receive() external payable { // 在这里调用漏洞合约的提款函数,实现重入 VulnContract(payable(vulnAddr)).withdraw(); }
最后再检查一遍:先存款到漏洞合约,再调用提款函数,同时确保fallback/receive函数有payable修饰,并且逻辑正确。
内容的提问来源于stack exchange,提问作者user9785654

