Ethernaut: 13. 게이트키퍼 원
10766 단어 ethereumopenzeppelinsecuritysolidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract GatekeeperOne {
using SafeMath for uint256;
address public entrant;
modifier gateOne() {
require(msg.sender != tx.origin);
_;
}
modifier gateTwo() {
require(gasleft().mod(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(tx.origin), "GatekeeperOne: invalid gateThree part three");
_;
}
function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}
와우 이것은 도전적이었습니다! 수정자로 구현된 3개의 장애물(게이트)을 통과해야 합니다.
msg.sender != tx.origin
. gasLeft().mod(8191) == 0
. require
시리즈는 게이트 키가 어떻게 생겼는지 알려줍니다. 게이트 1
첫 번째 게이트에 대한 해결책은 간단합니다. 계약을 중개인으로 사용하면 됩니다. 이전 퍼즐에서 우리는
msg.sender
가 계약일 수 있는 트랜잭션의 즉각적인 발신자임을 배웠습니다. 그러나 tx.origin
는 일반적으로 귀하인 거래의 발신자입니다.게이트 2
여기서 트랜잭션에 사용되는 가스를 조정해야 합니다. 우리는 에테르 값을 지정하는 방법과 유사하게 전달할 가스를 지정하여 이를 수행할 수 있습니다:
foo{gas: ...}()
. 적절한 가스 양을 찾는 것은 까다로운 부분입니다. 그때까지 얼마나 많은 가스를 갖게 될지 정확히 알 수 없기 때문입니다. 우리가 할 수 있는 일은 다음과 같습니다. 좋은 대략적인 가스 값을 찾은 다음 그 주변의 값 범위를 무자비하게 시도합니다. 이를 수행하는 단계는 다음과 같습니다. function enterOnce(uint _gas) public {
bytes memory callbytes = abi.encodeWithSignature(("enter(bytes8)"),key);
(bool success, ) = target.call{gas: _gas}(callbytes);
require(success, "failed my boy.");
}
GAS
가 백그라운드에서 수행하는 작업인 gasleft()
opcode에 도달하기 위해 Remix에서 트랜잭션을 디버그합니다. 여기에서 "Step Details"의 remaining gas
필드를 살펴보겠습니다. 여러 가지 방법으로 쉽게 갈 수 있습니다.gasleft()
가 있는 줄에 중단점을 놓고 디버거에서 오른쪽 화살표를 클릭하면 해당 opcode에 매우 가깝게 이동합니다. GAS
opcode에서 바로 9748이 남았습니다. 그것은 내가 거기에 도착하기 위해 252 가스를 사용했음을 의미합니다. 전체 가스 요구 사항을 충족하기에 충분히 큰 "k"를 위해 8191 * k + 252 가스로 시작하면 괜찮을 것입니다! 문제는 컴파일러 버전과 관련하여 가스 사용량이 변경될 수 있지만 퍼즐에서 ^0.6.0
가 위에서 사용되었음을 알 수 있으므로 해당 버전으로 위의 모든 단계를 수행할 것입니다. function enter(uint _gas, uint _margin) public {
bytes memory callbytes = abi.encodeWithSignature(("enter(bytes8)"),key);
bool success;
for (uint g = _gas - _margin; g <= _gas + _margin; g++) {
(success, ) = target.call{gas: g}(callbytes);
if (success) {
correctGas = g; // for curiosity
break;
}
}
require(success, "failed again my boy.");
}
그것은 성공적이었고 나는 또한 41209로 밝혀진 정확한 가스 양을 기록했습니다.
게이트 3
우리는 8바이트 키를 사용하고 있으므로 각 문자가 2바이트(16비트)인 키가
ABCD
라고 가정합니다.CD == D
so C
: 모두 0이어야 합니다. CD != ABCD
그래서 AB
는 모두 0이 아니어야 합니다. CD == uint16(tx.origin)
: C
는 이미 0이며 이제 D
가 tx.origin
의 마지막 16비트가 될 것임을 알고 있습니다. 따라서 내
uint16(tx.origin)
는 C274
입니다. AB = 0x 0000 0001
를 얻기 위해 _gateKey = 0x 0000 0001 0000 C274
를 설정합니다. 또는 &
를 tx.origin
로 비트 단위로 마스킹(0x FFFF FFFF 0000 FFFF
)하여 비트 단위 마스킹을 사용할 수 있습니다.그게 다야 여러분 :)
Reference
이 문제에 관하여(Ethernaut: 13. 게이트키퍼 원), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/erhant/ethernaut-13-gatekeeper-one-11dm텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)