🙆

Ethernaut完走の感想 その3

2023/05/10に公開

はじめに

この記事はEthernaut完走の感想 その2の続きです。ネタバレがあるのでご注意ください。なお、このあたりからだんだんと難しくなってきます。

18. MagicNumber

呼ばれたら42を返すwhatIsTheMeaningOfLife()関数をつくりなさい、ただし10opcodes以内で、という無茶振りです。これは全くわからなかったので解説を見ました。

解説を理解する上でいくつか予備知識が必要です。

  • コンストラクタではreturnによりruntime bytecodeのmemory上の場所を返す(そしてEVMはこの情報をもとにruntime bytecodeをデプロイすると思われます)
  • 関数セレクタはSolidityのコードをコンパイルした際にコードの先頭に自動生成される。つまりEVMが処理しているわけではなく、コントラクトがcallのパラメータを見て自分で実行するコードにジャンプしている。ということで、セレクタのないコントラクトをデプロイするとcallされたらその引数に関係なくデプロイされたruntime bytecodeの先頭から順番に実行する

これを踏まえると解説サイトの以下の解答は理解できますね。

    constructor() {
        assembly {
            mstore(0x0, 0x602a60005260206000f3) //memoryの0x0の場所に0x60...というコードを配置。このコードは`42`をmemoryに配置するコード
            return(0x16, 0x0a) //mstoreは32バイト単位で使用されるため、0パディングされている0x16をoffsetとし、コード長である0x0aを返す
        }
    }

そしてデプロイされたruntime bytecodeの0x602a60005260206000f3callからの呼び出しに対して実行されます。

むずっ!

参考資料:

19. Alien Codex

ownerを奪取する課題です。ぱっと見ownerはありませんが、こちらのコードで宣言されています。

ということでスロット0に_ownerがいらっしゃるのでサクッと上書きしたいところですが、いつものようなdelegatecallで上書きのようなテクニックは使えません。だた、solidityのバージョを見ると0.5.0と古いので、オーバーフローとか使えるんじゃないのかなぁ?と予想できます。

実際、codexが空のときに以下を実行するとcodexの長さが2の256乗-1となります。

  function retract() contacted public {
    codex.length--;
  }

また公式ドキュメントに記載されているように、動的配列(ここではcodex)はそのストレージが宣言されているスロットに配列長、keccak256(p)スロットから順番に配列の中身が格納されます。なので0-keccak256(p)がスロット0になるのでcodex[0-keccak256(p)]に自分のアドレスを書き込めば、_ownerが上書きできます。作業としてはretractを一回呼んで長さをMAXにしてからreviseでスロット0の場所にアドレスを書き込むでOKです。

と偉そうにいってますが、この解説サイトをカンニングしました。

ちなみに、Solidity v0.6.0のBreaking Changeでlengthがリードオンリーになったため、このコードはv0.5.0となっていました。

20. Denial

自分がpartnerとなり、ownerwitdrawできない状態にすることが課題です(0.001ETHを頂くことが課題ではありません)。攻撃用コントラクトを準備しpartnerに設定します。このコントラクトにfallbackreceiveを準備し、その中で無限ループを実行するとwithdrawは必ずfailするようになり、目的達成です。

SWC-113として知られる脆弱性です。

21. Shop

11. Elevatorの亜種です。攻撃用コントラクトにprice()を実装し、一回目と二回目で違う応答にすればよいのですが、今回はviewが指定されているため攻撃用コントラクト側でストレージに呼ばれた回数を保存するようなコードは書けません。

しかし、Shop側にisSoldというそれっぽい変数があり、しかも一回目と二回目で値が変わるのでこれを見てpriceの応答を変えればOKです。

22. Dex

タイトルからして、なんだか現実の課題に近づいてきたような気がします!この課題、コードの脆弱性を突くのではなくgetSwapPriceの実装上の問題(バグ)を利用します。token1/2共にDexが100、自分が10持っているのですが、以下のような交換を続けるだけでトークンが増えていきます。

操作 Dex token1 Dex token2 自分 token1 自分 token2
持っているtoken1すべてををtoken2にswap 110 90 0 20
持っているtoken2すべてををtoken1にswap 86 110 24 0
持っているtoken1すべてををtoken2にswap 110 80 0 30
持っているtoken2すべてををtoken1にswap 69 110 41 0
持っているtoken1すべてををtoken2にswap 110 45 0 65

23. Dex Two

Dexとほとんど同じコードで課題が異なります。今回はtoken1/2両方をすべて頂かないといけないのでDexの解法ではダメです。そこで、Dexとのコードの違いを見るとswapから以下のコードが削除されています。

require((from == token1 && to == token2) || (from == token2 && to == token1), "Invalid tokens");

これは、スワップするトークンは何でも良いということになります。つまり、自分で作ったトークンとtoken1/2をswapできます。そこで、常にbalanceOfが100を返すオレオレトークンをデプロイし、from: オレオレトークン, to: token1/2, amount: 100でスワップすると全部GETできます。

関連記事

GitHubで編集を提案

Discussion