Appearance
丢失的Ether何处寻?CTF挑战“Recovery”带你踏上区块链寻宝之旅
在区块链的世界里,智能合约就像一台精密的机器,一旦部署就难以修改。而今天,我们将一同探索一个名为“Recovery”的CTF(Capture The Flag)挑战,它将带领我们深入理解智能合约的运作机制,并学会如何从一个“丢失”的合约中“捞出”宝贵的Ether。
故事的开端:简单的代币工厂与遗失的地址
想象一下,一位开发者创建了一个名为 Recovery 的代币工厂合约。这个工厂非常简单,任何人都可以调用 generateToken 函数,轻松创建一个新的代币。在部署了第一个代币合约 SimpleToken 后,开发者为了获得更多的代币,向这个 SimpleToken 合约发送了 0.001 Ether。
然而,不幸的是,开发者随后 丢失了 SimpleToken 合约的地址。这0.001 Ether就如同沉入了大海,再也无法直接访问。我们的任务,就是成为一名区块链侦探,找回(或者说转移) 这个隐藏在遗失合约地址中的0.001 Ether。
深入源码:揭秘Recovery与SimpleToken
让我们来仔细看看这两份合约的源码:
Recovery.sol (代币工厂)
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Recovery {
// generate tokens
function generateToken(string memory _name, uint256 _initialSupply) public {
new SimpleToken(_name, msg.sender, _initialSupply);
}
}
Recovery 合约非常直观,它只提供了一个 generateToken 函数,用于部署新的 SimpleToken 合约。SimpleToken 的创建者(_creator)和初始供应量(_initialSupply)会在部署时被记录。
SimpleToken.sol (代币合约)
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SimpleToken {
string public name;
mapping(address => uint256) public balances;
// constructor
constructor(string memory _name, address _creator, uint256 _initialSupply) {
name = _name;
balances[_creator] = _initialSupply;
}
// collect ether in return for tokens
receive() external payable {
balances[msg.sender] = msg.value * 10;
}
// allow transfers of tokens
function transfer(address _to, uint256 _amount) public {
require(balances[msg.sender] >= _amount);
balances[msg.sender] = balances[msg.sender] - _amount;
balances[_to] = _amount;
}
// clean up after ourselves
function destroy(address payable _to) public {
selfdestruct(_to);
}
}
SimpleToken 合约则更具看点:
constructor: 记录代币名称、创建者以及创建者拥有的初始代币数量。receive() external payable: 这是关键!当有人向SimpleToken合约发送Ether时,这个函数会被触发。它会将接收到的Ether 乘以10,并将其作为代币数量奖励给发送者。开发者就是通过向合约发送0.001 Ether,获得了0.01个代币。transfer: 允许代币的转移。destroy(address payable _to): 这个函数允许任何人调用,并将合约本身以及合约中剩余的 Ether 全部转移到指定的地址_to。
寻宝的线索:selfdestruct 的魔力
通过阅读 SimpleToken 合约,我们看到了一个非常强大的函数:destroy。它的作用是调用 selfdestruct(_to)。selfdestruct 函数是Solidity中一个非常特殊的操作码,它会销毁当前合约,并将合约中剩余的Ether全部发送到指定的地址。
这意味着,只要我们能够找到那个“丢失”的 SimpleToken 合约地址,并且拥有调用 destroy 函数的权限,我们就能将合约中存储的0.001 Ether转移出来。
难题:如何找到“丢失”的合约地址?
CTF 挑战的核心往往在于找到那个隐藏的细节。在这个挑战中,最棘手的问题就是 如何找到 SimpleToken 合约的地址。
Solidity 合约的地址并非完全随机生成,而是与部署者的地址以及一次性随机数(nonce)有关。通过一种称为 “地址预测” 的技术,我们可以根据部署者的地址和部署的顺序来推算出合约的地址。
解决方案:AddressRecovery.sol 与地址预测
题目提供的答案中,AddressRecovery.sol 合约正是解决了这个问题:
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract AddressRecovery {
constructor() {}
function cAddrRecover(address _creator) external pure returns (address) {
return address(
uint160(
uint256(
keccak256(
abi.encodePacked(
bytes1(0xd6),
bytes1(0x94),
_creator,
bytes1(0x01) // This is the nonce, indicating the first contract deployed by the creator
)))));
}
}
这个 AddressRecovery 合约中的 cAddrRecover 函数,正是利用了Solidity的地址生成算法。它接收 _creator(代币工厂的部署者)的地址,然后通过 keccak256 哈希算法,结合特定的字节(0xd6, 0x94, _creator, 0x01),计算出一个预期的合约地址。这里的 0x01 可以理解为是部署者创建的第一个合约的 nonce。
通关之路:一步步找回Ether
- 部署
AddressRecovery合约: 首先,你需要部署AddressRecovery合约。 - 获取
Recovery合约的部署者地址: 在实际的CTF环境中,你需要知道部署Recovery合约的那个账户地址(在测试代码中通常称为CREATOR)。 - 预测
SimpleToken合约地址: 调用AddressRecovery合约的cAddrRecover函数,传入Recovery合约的部署者地址,即可预测出SimpleToken合约的地址。 - 获取
SimpleToken合约实例: 使用预测出的地址,以及SimpleToken合约的ABI(Application Binary Interface),创建一个SimpleToken合约的实例。 - 调用
destroy函数: 调用SimpleToken合约实例的destroy函数,并将你的地址作为参数传入,这样SimpleToken合约中剩余的0.001 Ether就会被转移到你的地址。
总结
“Recovery”挑战巧妙地结合了Solidity的 receive 函数、 selfdestruct 操作码以及合约地址的生成机制。通过理解这些核心概念,并运用地址预测的技巧,我们就能成功地从一个“丢失”的合约中“捞出”宝贵的Ether,完成挑战。
这不仅仅是一场游戏,更是一次宝贵的学习经历,它让我们更深刻地理解了区块链智能合约的运作原理,以及在实际应用中可能遇到的安全风险和技术挑战。希望这次寻宝之旅,能让你对区块链技术有更深的认识!