7月の損益とSolidity勉強メモ、仮想通貨コミュとの付き合い方

7月は仮想通貨-554万円、株-191万円でした。
 BOT自体はUSD建てでプラスなのですが、円高が酷くてものすごい円建てでの毀損をくらいました。仕方ないですね。

 昔はヘッジしていたのですが、ヘッジコストや税金絡みのめんどくささがあって今はしていません。
 自分の許容量以上のドルなのですが、円持ちたくないなあ、という気持ちもあってしばらくは放置します。


お触り周り

 弊鯖に書いた通り、次のお触り、お削り系は何にしようかなあ、と悩んでいます。meet48とBeraくらいしかやっていないので、何かのミリドロを狙いたい、が労働はしたくない。
 
 市況があまりよくないし、差別化要素が大きいお触りじゃないとやる気が出ないので、何かいい案がないか考え中・・・。

 毎日行く価値のある魔界チェーンが生えていた時代に比べて、今は良いのが本当にないです。
 HashkeyのStakingやBybitのStakingなど、今までだったらやる気が出ないものも一応やっていますが、100万とかそういう金額を稼げるものではないので、うーんって感じです。
 でかいイベント戦がほしい・・・本当に・・・。


コミュニティとの付き合い方

 Kudasai、DEG鯖を仮想通貨絡みのコミュとしては推しているのですが、これらに所属しているからと言って勝てるわけではないです。

 BBとかOtakulabとか流星街とかも見てはいるけど、今はROM専になってます。パウチの会、NOT DAO、Crypto hirobaあたりは全然見れていない。しろいの鯖は美味しい二郎を報告する場所なので、仮想通貨とはほとんど関係ありません。
 他になんかおすすめのコミュあったら教えてください。上記のコミュはそれなりに有名だと思う。
 
   
 結局、仮想通貨絡みでお金を出すのは個人ですし、情報は玉石混合で、適切なリスク量を見誤ったら普通に死にます。
 コミュのおかげで勝てました、は情報アグリゲーター的な面が強く、実際は個人の力量に左右されるでしょう。一番良いのは一次情報にあたることなのですが、時間がいくらあっても足りないんですよね…。そういう面でコミュは便利。

 まあ、こんなこと書いているのは最近投資家バーに行ってみたらあまりにも酷い他責思考の養分とかちあってしまい、負けるべくして負ける人はいるんだなあ、としみじみと感じました。

 雛鳥じゃあるまいし、美味しい情報が口を開けたら降ってきて、なにもリスク取らずにお金が増える、というのは甚だ夢を見すぎだなあ、と。

 最近は名乗るリスクも怖いので、仮想通貨ちょっとわかるサラリーマンとしてその辺で飲んだくれている方が気は楽です。あとはドースーさんフィルターを通した後のBotter会とかはかなり気楽。知らない人はやはり怖い。

 まあ、DeFiで稼ぐパターンはある程度決まっているので、Kudasai、DEG鯖などで言及されていたことを、独自の期待値と調査・技術でもって攻略し、収益化することが本当に必要なことでしょう。
 パルスニキとかkudasaiで壁え、とかって皆揶揄していたけど5000万円は拾えましたからね。あのチェーン。

 でかいDiscord系の鯖はオープンですし、あまり表立って言えないこともあるでしょう。本当に良い情報はClosed化していくのは当然です。
 このNote/Blogも毎月書いて3年くらい続けているのですが、界隈での認知度をあげ、誘ってもらったClosedな鯖で技術的協力をしつつ稼ぐ、というルートが私にとってかなり魅力的です。
 最近やることがあんまりないのでなんか面白いことあれば教えてください。個人鯖は見つけたら入るようにしてます。今月はたまたま見つけた二つに入った。(ジョアンナさんとりょんさん)


SolidityのBytecodeを読みたい

 SolidityのByteCodeについて自分が全然知らないなあ、と思ったので、何をやっているかを出来る限り理解しようと思い立ちました。
 欲を言えば、VerifyされていないつよつよBotterのコントラクトがどんな感じなのかを知りたいし、解析できるだけの能力を身に着けたいと思ってます。
 ただ、この章はほぼメモ書きに近いし、99%がエッジとも感じない不要な文章です。
 読み飛ばしてOKです。

(0)やること

 以下、超簡単なコントラクトをEVM Playgroundを使ってMEMORYを監視しながら何をやっているのか見ていきます。

pragma solidity >0.6.6;
contract test {
    function test1()public{
        
    }
}

Bytecodeは以下となる。

6080604052348015600f57600080fd5b50606d80601d6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c80636b59084d14602d575b600080fd5b60336035565b005b56fea2646970667358221220663ee5b3b6cad9cb1233b3133d348a4d8e337e84f240b08021e7199fa23f066764736f6c63430006060033

(1)初期化

下記のサイトを見ると、

0x6080604052

は、

PUSH1 0x80
PUSH1 0x40
MSTORE

となる。上記が何をしているかを調べていく。

 PUSH1とかは黄色い紙を見るとオペコードというものらしい。
 0x60はPUSH1に相当するもので、この60の次にある2文字の位置のものをStackに保持する、という意味らしい。

 Stackには、0x80,0x40のデータが入っている状態で、MSTOREを実行すると、Stackの2番目の項目を、スタックの1番目の項目が示すアドレスにメモリに格納するらしい。
 つまり、0x40のメモリ位置に0x80のデータを入れるそうだ。

 なるほど?全くわからんので、実際にPlayGroundで見てみると、

 確かに、STACKに40,80が入っていて、メモリには192個の0が入っているものが、MSTOREを実施すると

 MEMORYの末尾が80に変わった。
 0x40の位置がそもそもどこかわからないが、10進数だと0x40は64であるため、32文字ずつ上記のMEMORYを改行すると、64番目は()をつけた所で、

0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
(0)000000000000000000000000000000000000000000000000000000000000080

(0)を起点に0000000000000000000000000000000000000000000000000000000000000080
の32文字が書き込まれたようだ。

 0x80が書き込まれる、といいつつ32バイトずつ書き込みが起きるので、実際は0x0000000000000000000000000000000000000000000000000000000000000080が0x40から書き込まれるようだ。

 そもそも0x40に0x80を書き込む理由は、SolidityのDocsに書かれており、空きメモリの初期化をしたいかららしい。
 どうやら、一度メモリを書き込んだらメモリの上書きをしない、という仕様らしく、空きメモリをうまく管理してくれるらしい。

・0x00- 0x3f(64バイト): ハッシュメソッド用のスクラッチスペース
・0x40- 0x5f(32 バイト): 現在割り当てられているメモリ サイズ (別名、空きメモリ ポインタ)
・0x60- 0x7f(32バイト): ゼロスロット

SolidityのDocs

 という割当のようだ。なるほど・・・。

(2)Valueの検証

:6080604052:348015600f57600080fd5b50606d80601d6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c80636b59084d14602d575b600080fd5b60336035565b005b56fea2646970667358221220663ee5b3b6cad9cb1233b3133d348a4d8e337e84f240b08021e7199fa23f066764736f6c63430006060033

というBytecodeで0x6080604052まで終わった。
次は、348015600f57600080fd5bまでを見る。
読みやすくすると以下になる。

[05]	CALLVALUE	
[06]	DUP1	
[07]	ISZERO	
[08]	PUSH1	0f
[0a]	JUMPI	
[0b]	PUSH1	00
[0d]	DUP1	
[0e]	REVERT	
[0f]	JUMPDEST

まず、34が実行されていて、OPCODEを見るとCALLVALUEに相当するようだ。これは、送信したTXがETHを送金した場合に値が入るらしい。
 よくわからないのでPlaygroundを見ると、

value:1wei

 たしかに、1がStackに確保された。
 次は、80が実施されており、DUP1というものらしく、
Duplicate 1st stack itemという操作らしい。
 

DUP1実施後

 実施すると、STACKにあった1という値がDuplicate(複製)された。 
 さらにこの次は15が実施されており、ISZEROというOPCODEで、これはSTACKのTOPにあるものが0であるかをチェックするようだ。
 0ではないのでSTACKのTOPが0に置換された。

 この後、60 0fがいるので、PUSH1で0x0fメモリ位置をSTACKに入れる操作をし、

 57(JUMPI)で2 番目のスタック項目(今回は1となっている部分)がゼロ以外の場合にのみ実行される条件付きジャンプを行うらしい。
 今回1が入っているので条件付きジャンプが起こり、STACKにあったf,0は消去される。

PUSH1で0がSTACKに入り、DUP1で0を複製する操作が起き、STACKは以下のように0,0,1となる。

REVERT

 最後に、REVERT(fd)が実施される。

 なるほど、なんか難しく書いているが、結局、payableではないコントラクトにETHを送信する場合、Revertされるという操作をここで行うらしい。
 スタックの最上位 2 つの項目が戻り値の定義に使用されるスタックの最初の項目はオフセット、2 番目の項目は長さと定義されているが、今回は何も定義されない(0,0)ので、Etherscanではfailと表示されるだけなようだ。

 では、ここまではETH送金量を1weiとしていたが、0としてJUMPI-REVERTが実行されないようにしよう。
 そうすると、JUMPDEST(5B)に飛び、STACKは0となるようだ。 

JUMPDEST

(3)状態変数の初期化

:6080604052348015600f57600080fd5b:50606d80601d6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c80636b59084d14602d575b600080fd5b60336035565b005b56fea2646970667358221220663ee5b3b6cad9cb1233b3133d348a4d8e337e84f240b08021e7199fa23f066764736f6c63430006060033

というBytecodeで0x6080604052348015600f57600080fd5bまで終わった。
次は、50606d80601d6000396000f3までを見る。

[10]	POP	
[11]	PUSH1	6d
[13]	DUP1	
[14]	PUSH1	1d
[16]	PUSH1	00
[18]	CODECOPY	
[19]	PUSH1	00
[1b]	RETURN	

 最初は、POP(50)でSTACKに入っているものを削除する。実行すると、先程まで入っていた値が消え、STACKは空になった。

POP

 次に、PUSH1 6d からPUSH1 00まで実行すると、

PUSH1 6d からPUSH1 00

STACKにこのような数字が入る。この時、MEMORYは、

0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000080
0000000000000000000000000000000000000000000000000000000000000000

のように0000000000000000000000000000000000000000000000000000000000000000が追加されている。

 これは、PUSH1 00を実行したときに、メモリに書き込まれたわけではなく、スタック操作で間接的に使用されただけらしい。

この次に、CODECOPY(39)を実施している。これは、Stack上位3つを使って、コピーを行うOPCODEらしい。

CODECOPY
destOffset: byte offset in the memory where the result will be copied.
offset: byte offset in the code to copy.
size: byte size to copy.

具体的には、
開始位置:コードの 0x1d バイト目(60)
コピー先:メモリの 0x6d バイト目
長さ:次の命令までのバイト数がコピーされる。

CODECOPYを実施


左Contract creation code 右 Memory

 これを理解するのは正直難しいが、以下のように整理される。

初期メモリ
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000080
0000000000000000000000000000000000000000000000000000000000000000

実施後
6080604052348015600f57600080fd5b506004361060285760003560e01c8063
6b59084d14602d575b600080fd5b60336035565b005b56fea264697066735822
1220

7c786aeb42d05120bba629cdbe53a9f700c8b9a50a76f864e18ebf67bdb1
9e6264736f6c6343000815003300000000000000000000000000000000000000

Contract creation code
0x1d(29文字目)以降で、次の命令があるまでを抜き出し
6080604052348015600f57600080fd5b506004361060285760003560e01c8063
6b59084d14602d575b600080fd5b60336035565b005b56fea264697066735822
1220
不明
7c786aeb42d05120bba629cdbe53a9f700c8b9a50a76f864e18ebf67bdb19e
6264736f6c63430008150033
0000000000000000000000000000000000000000

ChatGPTに解説させる

 7c786aeb42d05120bba629cdbe53a9f700c8b9a50a76f864e18ebf67bdb19eという謎のコードがいたが、これはIPFSハッシュとか、メタデータらしい。本当なのだろうか・・・?
 この部分は正直理解しきれなかった。

 最後は、PUSH1 00 をしてSTACKをきれいにした後、RETURN(F3)をかけて、コントラクトを終了させている。

 このコードではバイトコードをメモリにコピーして返すこと以外は何も行っていないことがわかる。

(4)参考


Decompilerをうまく使う

 なんとなく、Bytecodeでやっていることがわかってきたが毎回読むのはつらすぎるし、OPCODE自体をいじることはないだろう。

Solidity

pragma solidity >0.6.6;
contract test {
    function test1()public{
        
    }
}

Bytecode

constructor prefix:6080604052348015600f57600080fd5b50606d80601d6000396000f3fe
本体:6080604052348015600f57600080fd5b506004361060285760003560e01c80636b59084d14602d575b600080fd5b60336035565b005b56fea2646970667358221220663ee5b3b6cad9cb1233b3133d348a4d8e337e84f240b08021e7199fa23f066764736f6c63430006060033

上記コードをOnline Solidity Decompilerを使ってDecompileすると、(constructor prefixは削除している)

contract Contract {
    function main() {
        memory[0x40:0x60] = 0x80;//コントラクトの初期化
        var var0 = msg.value;//TXのETH量の取得
    
        if (var0) { revert(memory[0x00:0x00]); }
        //TXのETH量が0より上の場合Revertをする
    
        if (msg.data.length < 0x04) { revert(memory[0x00:0x00]); }
        //Inputに含まれるMethodIDの長さをチェックする。8文字以下はRevertする
        var0 = msg.data[0x00:0x20] >> 0xe0;
        //Inputデータの最初の32バイト(64文字)を224ビットシフトして、MethodIDを取得する
        if (var0 != 0x6b59084d) { revert(memory[0x00:0x00]); }
        //0x6b59084dはtest1という関数のmethodIDで、それと一致しなければRevertする
        var var1 = 0x33;//変数を定義しているが何も使っていない。
        func_0035();//空の関数の実行
        stop();//実行の停止
    }
    
    function func_0035() {}
}

 のように、まだ読める状態になった。

 今回は中身がないコードであるため、これに色々な操作を付け加えたらどのように変わるかを実験してみた。

(i)変数の設定

pragma solidity 0.6.6;
contract test {
    function test1()public{
        uint amount=10000;
    }
}
contract Contract {
    function main() {
        memory[0x40:0x60] = 0x80;
        var var0 = msg.value;
    
        if (var0) { revert(memory[0x00:0x00]); }
    
        if (msg.data.length < 0x04) { revert(memory[0x00:0x00]); }
    
        var0 = msg.data[0x00:0x20] >> 0xe0;
    
        if (var0 != 0x6b59084d) { revert(memory[0x00:0x00]); }
    
        var var1 = 0x33;
        func_0035();
        stop();
    }
    
    function func_0035() {}
}

Decompileしたコードはuint amount=10000;の情報が一切入っていない。これは、メモリ操作がないせいだと思うけど、なんでだろう・・・。

(ii)変数の設定+Return

pragma solidity 0.6.6;
contract test {
    function test1()public returns (uint){
        uint amount=10000;
        return amount;
    }
}
contract Contract {
    function main() {
        memory[0x40:0x60] = 0x80;
        var var0 = msg.value;
    
        if (var0) { revert(memory[0x00:0x00]); }
    
        if (msg.data.length < 0x04) { revert(memory[0x00:0x00]); }
    
        var0 = msg.data[0x00:0x20] >> 0xe0;
    
        if (var0 != 0x6b59084d) { revert(memory[0x00:0x00]); }
    
        var var1 = 0x33;
        var1 = func_0045();
        var temp0 = memory[0x40:0x60];
        memory[temp0:temp0 + 0x20] = var1;
        var temp1 = memory[0x40:0x60];
        return memory[temp1:temp1 + temp0 - temp1 + 0x20];
    }
    
    function func_0045() returns (var r0) { return 0x2710; }
}

以下が変わった。

     var1 = func_0045();//func_0045での値をvar1に代入する。
        //0x2710は10000
        var temp0 = memory[0x40:0x60];
        //フリーメモリポンタを取得し、memory[0x40:0x60] = 0x80;なので実際は0x80
        memory[temp0:temp0 + 0x20] = var1;
        //0x80~0x80+0x20(0xa0) 128~160の32文字を確保しvar1とする
        var temp1 = memory[0x40:0x60];
     //フリーメモリポンタを再取得するだけ
        return memory[temp1:temp1 + temp0 - temp1 + 0x20];
     //temp0:0x80 temp1:0x80 なので0x80から0x80+0x20を返却する。
     //実質、0x20分(32文字)の返却でvar1が返ってきているだけ。
    }
    
    function func_0045() returns (var r0) { return 0x2710; }

 非常に読みにくいが、結局func_0045を呼び出して10000を返しているだけのようだ。

(iii)変数を入れる+return

pragma solidity 0.6.6;
contract test {
    function test1(uint amount)public returns (uint){       
        return amount;
    }
}
contract Contract {
    function main() {
        memory[0x40:0x60] = 0x80;
        var var0 = msg.value;
    
        if (var0) { revert(memory[0x00:0x00]); }
    
        if (msg.data.length < 0x04) { revert(memory[0x00:0x00]); }
    
        var0 = msg.data[0x00:0x20] >> 0xe0;
    
        if (var0 != 0x19ae8994) { revert(memory[0x00:0x00]); }
    
        var var1 = 0x47;
        var var2 = 0x04;
        var var3 = msg.data.length - var2;
    
        if (var3 < 0x20) { revert(memory[0x00:0x00]); }
    
        var1 = func_0041(var2, var3);
        var temp0 = memory[0x40:0x60];
        memory[temp0:temp0 + 0x20] = var1;
        var temp1 = memory[0x40:0x60];
        return memory[temp1:temp1 + temp0 - temp1 + 0x20];
    }
    
    function func_0041(var arg0, var arg1) returns (var r0) { return msg.data[arg0:arg0 + 0x20]; }
}

以下が変わった。

        var var1 = 0x47;//変数データ初期値
        var var2 = 0x04;//MethodIDを読み込んだあとのデータ位置指定
        var var3 = msg.data.length - var2;//MethodIDを除いたデータの長さ
    
        if (var3 < 0x20) { revert(memory[0x00:0x00]); }//データ長さが32以下の場合はRevertする
    
        var1 = func_0041(var2, var3);//var1にfunc_0041実行結果を入れる
        var temp0 = memory[0x40:0x60];//位置の指定
        memory[temp0:temp0 + 0x20] = var1;//指定位置のメモリ書き換え
        var temp1 = memory[0x40:0x60];//位置の指定
        return memory[temp1:temp1 + temp0 - temp1 + 0x20];//var1を返す。
    }
    
    function func_0041(var arg0, var arg1) returns (var r0) 
//データ位置arg0(var2 0x04のmethodIDを読み込んだ後の位置)、arg1MehodIDを除いたデータの長さ)
を指定してvar r0で定義される一つのデータを返却
      { return msg.data[arg0:arg0 + 0x20]; }
//msg.dataで指定された、TX内のInputデータの中身32文字をreturnで返却する。
}

 少し複雑になったが、func_0041(var arg0, var arg1)は何かしらの値を指定しているわけではなく、メモリ位置を指定しているだけの様子。
  ちなみにview修飾子をいれてもコードは変わらなかった。

(iv)変数を2ついれてReturnする

pragma solidity 0.6.6;
contract test {
    function test1(uint amount,address addr)public returns (uint,address){       
        return (amount,addr);
    }
}
contract Contract {
    function main() {
        memory[0x40:0x60] = 0x80;
        var var0 = msg.value;
    
        if (var0) { revert(memory[0x00:0x00]); }
    
        if (msg.data.length < 0x04) { revert(memory[0x00:0x00]); }
    
        var0 = msg.data[0x00:0x20] >> 0xe0;
    
        if (var0 != 0x24fe6d5f) { revert(memory[0x00:0x00]); }
    
        var var1 = 0x56;
        var var2 = 0x04;
        var var3 = msg.data.length - var2;
    
        if (var3 < 0x40) { revert(memory[0x00:0x00]); }
    
        var1, var2 = func_0041(var2, var3);
        var temp0 = memory[0x40:0x60];
        memory[temp0:temp0 + 0x20] = var1;
        memory[temp0 + 0x20:temp0 + 0x20 + 0x20] = var2 & (0x01 << 0xa0) - 0x01;
        var temp1 = memory[0x40:0x60];
        return memory[temp1:temp1 + temp0 - temp1 + 0x40];
    }
    
    function func_0041(var arg0, var arg1) returns (var r0, var arg0) {
        var temp0 = arg0;
        arg0 = msg.data[temp0:temp0 + 0x20];
        arg1 = msg.data[temp0 + 0x20:temp0 + 0x20 + 0x20] & (0x01 << 0xa0) - 0x01;
        var temp1 = arg0;
        arg0 = arg1;
        r0 = temp1;
        return r0, arg0;
    }
}

以下が大きく変わった。

       var1, var2 = func_0041(var2, var3);//2つの変数を受け取る
        var temp0 = memory[0x40:0x60];
        memory[temp0:temp0 + 0x20] = var1;
        memory[temp0 + 0x20:temp0 + 0x20 + 0x20] = var2 & (0x01 << 0xa0) - 0x01;
        var temp1 = memory[0x40:0x60];
        return memory[temp1:temp1 + temp0 - temp1 + 0x40];
     //32ではなく64を返却する=2つの変数が返ってくることがわかる
    }
    
    function func_0041(var arg0, var arg1) returns (var r0, var arg0) {
        var temp0 = arg0;
        arg0 = msg.data[temp0:temp0 + 0x20];//32byte
        arg1 = msg.data[temp0 + 0x20:temp0 + 0x20 + 0x20] & (0x01 << 0xa0) - 0x01;
     //arg0でamountをいれた次から値を入れ、0x20 + 0x20(=64)までを確保する。
     //& (0x01 << 0xa0) - 0x01;では
     //0x01 << 0xa0 は、1を160ビット左にシフトした値
     //160ビット=20byte=40文字
        //0x01は1を引くので0x0ffffffffffffffffffffffffffffffffffffffff
     //となる。&でビット論理積をとるので、結果的に0を詰めたアドレスの値を取ることができる
        var temp1 = arg0;
        arg0 = arg1;
        r0 = temp1;
        return r0, arg0;
    }
}

 何やら複雑だが、やっていることは簡単。アドレスもuintも関係なく32byteを確保しているだけの様子。return r0, arg0;と2つの変数が返ってきている。

(v)関数の呼び出し

pragma solidity 0.6.6;
contract test {
    function test1()public{
        test2();
    }
    function test2()public{  
    }
}
contract Contract {
    function main() {
        memory[0x40:0x60] = 0x80;
        var var0 = msg.value;
    
        if (var0) { revert(memory[0x00:0x00]); }
    
        if (msg.data.length < 0x04) { revert(memory[0x00:0x00]); }
    
        var0 = msg.data[0x00:0x20] >> 0xe0;
    
        if (var0 == 0x66e41cb7) {//test2
            // Dispatch table entry for test2()
            var var1 = 0x3d;//何もしていない
            test2();//test2を呼び出す
            stop();//終了
        } else if (var0 == 0x6b59084d) {//test1
            // Dispatch table entry for test1()
            var1 = 0x3d;//何もしていない
            var var2 = 0x45;//何もしていない
            test2();//test2を呼び出し
            test2();//なぜか二回呼んでいてデコンパイルエラー
            // Error: Could not resolve method call return address!
        } else { revert(memory[0x00:0x00]); }
    }
    
    function test2() {}
}

 なぜかtest2を二回呼ぶ謎はあるが、大体意味はわかる。

(vi)コントラクトの入れ子

pragma solidity 0.6.6;
contract address_list {
    address public constant wallet=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045;
}
contract test is address_list{
    function test1()public{   
    }
}
contract Contract {
    function main() {
        memory[0x40:0x60] = 0x80;
        var var0 = msg.value;
    
        if (var0) { revert(memory[0x00:0x00]); }
    
        if (msg.data.length < 0x04) { revert(memory[0x00:0x00]); }
    
        var0 = msg.data[0x00:0x20] >> 0xe0;
    
        if (var0 != 0x521eb273) { revert(memory[0x00:0x00]); }
    
        var var1 = 0x33;
        var var2 = func_004F();
        var temp0 = memory[0x40:0x60];
        memory[temp0:temp0 + 0x20] = var2 & (0x01 << 0xa0) - 0x01;
        var temp1 = memory[0x40:0x60];
        return memory[temp1:temp1 + temp0 - temp1 + 0x20];
    }
    
    function func_004F() returns (var r0) { return 0xd8da6bf26964af9d7eed9e03e53415d37aa96045; }
}

コントラクトの構造はあんまり気にしないでよい様子

(vii)メモリ操作

contract test{
    function test1()public{
        address[] memory path;
        path = new address[](2);
        path[0] = address(1);
        path[1] = address(2);
        }
    
}


contract Contract {
    function main() {
        memory[0x40:0x60] = 0x80;
        var var0 = msg.value;
    
        if (var0) { revert(memory[0x00:0x00]); }
    
        if (msg.data.length < 0x04) { revert(memory[0x00:0x00]); }
    
        var0 = msg.data[0x00:0x20] >> 0xe0;
    
        if (var0 != 0x6b59084d) { revert(memory[0x00:0x00]); }
    
        var var1 = 0x33;
        func_0035();
        stop();
    }
    
    function func_0035() {
        var temp0 = memory[0x40:0x60];
        memory[temp0:temp0 + 0x20] = 0x02;//128(0x80)から160までに2を入れる
        memory[0x40:0x60] = temp0 + 0x60;//128+96 32文字が入るのでフリーメモリを更新
        memory[temp0 + 0x20:temp0 + 0x20 + 0x40] 
= msg.data[msg.data.length:msg.data.length + 0x40];
//path = new address[](2); のコピー?
        var var0 = temp0;
        var var1 = 0x01;//address1つ目
        var var2 = var0;
        var var3 = 0x00;//path0個目
    
        if (var3 >= memory[var2:var2 + 0x20]) { assert(); }
    
        memory[var3 * 0x20 + 0x20 + var2:var3 * 0x20 + 0x20 + var2 + 0x20] = var1 & (0x01 << 0xa0) - 0x01 & (0x01 << 0xa0) - 0x01;
        var1 = 0x02;//アドレス2つ目
        var2 = var0;
        var3 = 0x01;//path1個目
    
        if (var3 >= memory[var2:var2 + 0x20]) { assert(); }
    
        memory[var3 * 0x20 + 0x20 + var2:var3 * 0x20 + 0x20 + var2 + 0x20] = var1 & (0x01 << 0xa0) - 0x01 & (0x01 << 0xa0) - 0x01;
    }
}

 address[] memory path;でメモリを確保した場合、複雑なメモリ操作が発生していそう。アドレスの値から推察するしかなさげ。

(viii)外部コントラクト呼び出し

pragma solidity 0.6.6;

interface Itest{
    function ex_test(uint amount) external;
}

contract test{
    function test1()public{
        address  wallet=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045;
        Itest(wallet).ex_test(uint(256));
}
}
contract Contract {
    function main() {
        memory[0x40:0x60] = 0x80;
        var var0 = msg.value;
    
        if (var0) { revert(memory[0x00:0x00]); }
    
        if (msg.data.length < 0x04) { revert(memory[0x00:0x00]); }
    
        var0 = msg.data[0x00:0x20] >> 0xe0;
    
        if (var0 != 0x6b59084d) { revert(memory[0x00:0x00]); }
    
        var var1 = 0x33;
        func_0035();
        stop();
    }
    
    function func_0035() {
        var temp0 = memory[0x40:0x60];
        memory[temp0:temp0 + 0x20] = 0xc3225737 << 0xe0;
        memory[temp0 + 0x04:temp0 + 0x04 + 0x20] = 0x0100;
        var var0 = 0xd8da6bf26964af9d7eed9e03e53415d37aa96045;//wallet
        var var1 = var0;
        var var2 = 0xc3225737;//ex_test
        var var3 = temp0 + 0x24;
        var var4 = 0x00;
        var var5 = memory[0x40:0x60];
        var var6 = temp0 - var5 + 0x24;//
        var var7 = var5;
        var var8 = var4;
        var var9 = var1;
        var var10 = !address(var9).code.length;
    
        if (var10) { revert(memory[0x00:0x00]); }
    
        var temp1;
        temp1, memory[var5:var5 + var4] 
= address(var9).call.gas(msg.gas).value(var8)
(memory[var7:var7 + var6]);//MethodID+amountの指定
//ここでvar9=var1=var0でwalletのアドレスをcallしている。valueをvar4で指定。
//tem1にcallの成功可否を設定し、失敗した場合はvar4を1にする。
        var4 = !temp1;
    
        if (!var4) { return; }
    
        var temp2 = returndata.length;
        memory[0x00:0x00 + temp2] = returndata[0x00:0x00 + temp2];
        revert(memory[0x00:0x00 + returndata.length]);
    }
}

 外部関数を呼び出すのに0xc3225737を指定せずにcallしているが、実際はtemp+0x24で36文字(methodID4文字、amount32文字)を指定しているので、これで呼び出せている。
 Unverifiedなコントラクトで他のアドレスを呼び出しているかを判断する場合は、callまわりとmethodIDを見るしかなさそう。

(X)kudasai minterコントラクトを用いてMK貫通関数を実行する

 MoreKudasaiというKudasaiがやっているNFTがある。これはホワリスでMintできるもの以外に貫通関数が用意されていて、ホワリスなしでもMintをすることができた。遊び心ですね。

 MKで貫通可能なことはTelegramで示唆されてはいたのですが、当時のスキルではBytecodeから適切なInputを用意し、貫通関数を動かすことはできなかったです。

https://burry.co.jp/articles/0xwagmican-3/

 上記のミカニキの記事でVerifyされていないコントラクトを読んで適切な戦略を取れる、というのはかなり面白いなあと思ったので、せっかくなのでミカニキが作ったコントラクトを(今更)貫通しようと思う。現状はコードもVerifyされているから検証も簡単だし。

Minter Contract:0x2abbaf5687be52ae179fb65358bab4299d6b3829
Deployed Bytecode
0x6080604052600436106101755760003560e01c8063a5a4caf5116100cb578063bdb4b8481161007f578063ed8dcd8f11610059578063ed8dcd8f146103dc578063f19e75d4146103f1578063f2fde38b1461041157600080fd5b8063bdb4b8481461039e578063d02b9f63146103b4578063e086e5ec146103c757600080fd5b8063b4e33144116100b0578063b4e331441461033a578063b7b1ae0914610350578063ba44442d1461038857600080fd5b8063a5a4caf5146102fa578063a79892a01461031a57600080fd5b806338ba746f1161012d578063715018a611610107578063715018a61461029d5780638da5cb5b146102b257806397145273146102da57600080fd5b806338ba746f1461024a57806364febc381461025d578063672ef7721461027d57600080fd5b80630fd157471161015e5780630fd15747146101e357806314962764146102135780631b4692fc1461023557600080fd5b8063033f7d451461017a578063065e510f146101a3575b600080fd5b34801561018657600080fd5b5061019060025481565b6040519081526020015b60405180910390f35b3480156101af57600080fd5b506101d36101be36600461171b565b60046020526000908152604090205460ff1681565b604051901515815260200161019a565b3480156101ef57600080fd5b506101d36101fe36600461171b565b60036020526000908152604090205460ff1681565b34801561021f57600080fd5b5061023361022e366004611734565b610431565b005b34801561024157600080fd5b506101d361044a565b610233610258366004611760565b610463565b34801561026957600080fd5b50610233610278366004611890565b61072e565b34801561028957600080fd5b50610233610298366004611890565b610af8565b3480156102a957600080fd5b50610233610ea7565b3480156102be57600080fd5b506000546040516001600160a01b03909116815260200161019a565b3480156102e657600080fd5b506102336102f53660046118e2565b610ebb565b34801561030657600080fd5b50610233610315366004611890565b610ef2565b34801561032657600080fd5b506102336103353660046118ff565b610f62565b34801561034657600080fd5b5061019060075481565b34801561035c57600080fd5b5061019061036b366004611944565b600160209081526000928352604080842090915290825290205481565b34801561039457600080fd5b5061019060065481565b3480156103aa57600080fd5b5061019060085481565b6102336103c2366004611970565b6110bb565b3480156103d357600080fd5b50610233611410565b3480156103e857600080fd5b50610233611455565b3480156103fd57600080fd5b5061023361040c36600461171b565b61147c565b34801561041d57600080fd5b5061023361042c3660046118e2565b611540565b6104396115cd565b600792909255600891909155600655565b6000600a600254101561045d5750600190565b50600090565b6040516bffffffffffffffffffffffff193360601b166020820152829082906000906034016040516020818303038152906040528051906020012090506104e1838380806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250506007549150849050611627565b6105325760405162461bcd60e51b815260206004820152601a60248201527f596f7520617265206e6f742061204b756461736169206c69737400000000000060448201526064015b60405180910390fd5b3233146105815760405162461bcd60e51b815260206004820152601c60248201527f5265656e7472616e6379204775617264206973207761746368696e67000000006044820152606401610529565b8560085461058f91906119a8565b34146105dd5760405162461bcd60e51b815260206004820152601960248201527f4d696e7420636f737420697320696e73756666696369656e74000000000000006044820152606401610529565b33600090815260016020908152604080832060065484529091529020547f0000000000000000000000000000000000000000000000000000000000000001906106279088906119bf565b11156106755760405162461bcd60e51b815260206004820152600f60248201527f4e6f204d6f7265204b75646173616900000000000000000000000000000000006044820152606401610529565b3360009081526001602090815260408083206006548452909152812080548892906106a19084906119bf565b9091555050604051630450ac2560e41b8152336004820152602481018790527f000000000000000000000000ecd0cbbdbfb07986e22981c8d78e17a9526058546001600160a01b03169063450ac25090604401600060405180830381600087803b15801561070e57600080fd5b505af1158015610722573d6000803e3d6000fd5b50505050505050505050565b8060005b815181101561091c5760036000838381518110610751576107516119d2565b60209081029190910181015182528101919091526040016000205460ff16156107bc5760405162461bcd60e51b815260206004820152600f60248201527f416c726561647920636c61696d656400000000000000000000000000000000006044820152606401610529565b336001600160a01b03167f0000000000000000000000003fa3bc4aa2874977130e5f4d92fd96a03367efbb6001600160a01b0316636352211e848481518110610807576108076119d2565b60200260200101516040518263ffffffff1660e01b815260040161082d91815260200190565b602060405180830381865afa15801561084a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061086e91906119e8565b6001600160a01b0316146108c45760405162461bcd60e51b815260206004820152601c60248201527f596f7520646f206e6f742068617665204b756461736169204e465473000000006044820152606401610529565b6001600360008484815181106108dc576108dc6119d2565b6020026020010151815260200190815260200160002060006101000a81548160ff021916908315150217905550808061091490611a05565b915050610732565b5032331461096c5760405162461bcd60e51b815260206004820152601c60248201527f5265656e7472616e6379204775617264206973207761746368696e67000000006044820152606401610529565b6040517fb1b46fc00000000000000000000000000000000000000000000000000000000081526001600160a01b037f000000000000000000000000ecd0cbbdbfb07986e22981c8d78e17a952605854169063b1b46fc0906109d39033908690600401611a1e565b600060405180830381600087803b1580156109ed57600080fd5b505af1158015610a01573d6000803e3d6000fd5b5050505060005b8251811015610af3577f0000000000000000000000003fa3bc4aa2874977130e5f4d92fd96a03367efbb6001600160a01b03166342842e0e3361dead868581518110610a5657610a566119d2565b60209081029190910101516040517fffffffff0000000000000000000000000000000000000000000000000000000060e086901b1681526001600160a01b0393841660048201529290911660248301526044820152606401600060405180830381600087803b158015610ac857600080fd5b505af1158015610adc573d6000803e3d6000fd5b505050508080610aeb90611a05565b915050610a08565b505050565b8060005b8151811015610ce65760046000838381518110610b1b57610b1b6119d2565b60209081029190910181015182528101919091526040016000205460ff1615610b865760405162461bcd60e51b815260206004820152600f60248201527f416c726561647920636c61696d656400000000000000000000000000000000006044820152606401610529565b336001600160a01b03167f000000000000000000000000a6ba2b2ab66d25cddaa6ea5dce24edd56da33a456001600160a01b0316636352211e848481518110610bd157610bd16119d2565b60200260200101516040518263ffffffff1660e01b8152600401610bf791815260200190565b602060405180830381865afa158015610c14573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c3891906119e8565b6001600160a01b031614610c8e5760405162461bcd60e51b815260206004820152601c60248201527f596f7520646f206e6f742068617665204b756461736169204e465473000000006044820152606401610529565b600160046000848481518110610ca657610ca66119d2565b6020026020010151815260200190815260200160002060006101000a81548160ff0219169083151502179055508080610cde90611a05565b915050610afc565b50323314610d365760405162461bcd60e51b815260206004820152601c60248201527f5265656e7472616e6379204775617264206973207761746368696e67000000006044820152606401610529565b8151604051630450ac2560e41b815233600482015260248101919091527f000000000000000000000000ecd0cbbdbfb07986e22981c8d78e17a9526058546001600160a01b03169063450ac25090604401600060405180830381600087803b158015610da157600080fd5b505af1158015610db5573d6000803e3d6000fd5b5050505060005b8251811015610af3577f000000000000000000000000a6ba2b2ab66d25cddaa6ea5dce24edd56da33a456001600160a01b03166342842e0e3361dead868581518110610e0a57610e0a6119d2565b60209081029190910101516040517fffffffff0000000000000000000000000000000000000000000000000000000060e086901b1681526001600160a01b0393841660048201529290911660248301526044820152606401600060405180830381600087803b158015610e7c57600080fd5b505af1158015610e90573d6000803e3d6000fd5b505050508080610e9f90611a05565b915050610dbc565b610eaf6115cd565b610eb9600061163d565b565b610ec36115cd565b6005805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0392909216919091179055565b610efa6115cd565b60005b8151811015610f5e57600160036000848481518110610f1e57610f1e6119d2565b6020026020010151815260200190815260200160002060006101000a81548160ff0219169083151502179055508080610f5690611a05565b915050610efd565b5050565b610f6a6115cd565b60005b8251811015610af35760007f0000000000000000000000003fa3bc4aa2874977130e5f4d92fd96a03367efbb6001600160a01b0316636352211e858481518110610fb957610fb96119d2565b60200260200101516040518263ffffffff1660e01b8152600401610fdf91815260200190565b602060405180830381865afa158015610ffc573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061102091906119e8565b604051630450ac2560e41b81526001600160a01b038083166004830152602482018690529192507f000000000000000000000000ecd0cbbdbfb07986e22981c8d78e17a9526058549091169063450ac25090604401600060405180830381600087803b15801561108f57600080fd5b505af11580156110a3573d6000803e3d6000fd5b505050505080806110b390611a05565b915050610f6d565b32331461110a5760405162461bcd60e51b815260206004820152601c60248201527f5265656e7472616e6379204775617264206973207761746368696e67000000006044820152606401610529565b8160085461111891906119a8565b34146111665760405162461bcd60e51b815260206004820152601960248201527f4d696e7420636f737420697320696e73756666696369656e74000000000000006044820152606401610529565b6005546001600160a01b03161580159061120e57506005546040517f0c0d074900000000000000000000000000000000000000000000000000000000815233600482015260248101849052604481018390526001600160a01b0390911690630c0d074990606401602060405180830381865afa1580156111ea573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061120e9190611a75565b61125a5760405162461bcd60e51b815260206004820152601c60248201527f596f7572206164647265737320697320426c61636b6c697374656421000000006044820152606401610529565b33600090815260016020908152604080832060065484529091529020547f0000000000000000000000000000000000000000000000000000000000000001906112a49084906119bf565b11156112f25760405162461bcd60e51b815260206004820152600f60248201527f4e6f204d6f7265204b75646173616900000000000000000000000000000000006044820152606401610529565b6112fa61044a565b6113465760405162461bcd60e51b815260206004820152600e60248201527f4e6f204d6f7265204b616e7473750000000000000000000000000000000000006044820152606401610529565b3360009081526001602090815260408083206006548452909152812080548492906113729084906119bf565b90915550506002805490600061138783611a05565b9091555050604051630450ac2560e41b8152336004820152602481018390527f000000000000000000000000ecd0cbbdbfb07986e22981c8d78e17a9526058546001600160a01b03169063450ac25090604401600060405180830381600087803b1580156113f457600080fd5b505af1158015611408573d6000803e3d6000fd5b505050505050565b6114186115cd565b600080546040516001600160a01b03909116914780156108fc02929091818181858888f19350505050158015611452573d6000803e3d6000fd5b50565b61145d6115cd565b6005805473ffffffffffffffffffffffffffffffffffffffff19169055565b6114846115cd565b7f000000000000000000000000ecd0cbbdbfb07986e22981c8d78e17a9526058546001600160a01b031663450ac2506114c56000546001600160a01b031690565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b1681526001600160a01b03909116600482015260248101849052604401600060405180830381600087803b15801561152557600080fd5b505af1158015611539573d6000803e3d6000fd5b5050505050565b6115486115cd565b6001600160a01b0381166115c45760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f64647265737300000000000000000000000000000000000000000000000000006064820152608401610529565b6114528161163d565b6000546001600160a01b03163314610eb95760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610529565b600082611634858461169a565b14949350505050565b600080546001600160a01b0383811673ffffffffffffffffffffffffffffffffffffffff19831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b600081815b84518110156116df576116cb828683815181106116be576116be6119d2565b60200260200101516116e9565b9150806116d781611a05565b91505061169f565b5090505b92915050565b6000818310611705576000828152602084905260409020611714565b60008381526020839052604090205b9392505050565b60006020828403121561172d57600080fd5b5035919050565b60008060006060848603121561174957600080fd5b505081359360208301359350604090920135919050565b60008060006040848603121561177557600080fd5b83359250602084013567ffffffffffffffff8082111561179457600080fd5b818601915086601f8301126117a857600080fd5b8135818111156117b757600080fd5b8760208260051b85010111156117cc57600080fd5b6020830194508093505050509250925092565b634e487b7160e01b600052604160045260246000fd5b600082601f83011261180657600080fd5b8135602067ffffffffffffffff80831115611823576118236117df565b8260051b604051601f19603f83011681018181108482111715611848576118486117df565b60405293845285810183019383810192508785111561186657600080fd5b83870191505b848210156118855781358352918301919083019061186c565b979650505050505050565b6000602082840312156118a257600080fd5b813567ffffffffffffffff8111156118b957600080fd5b6118c5848285016117f5565b949350505050565b6001600160a01b038116811461145257600080fd5b6000602082840312156118f457600080fd5b8135611714816118cd565b6000806040838503121561191257600080fd5b823567ffffffffffffffff81111561192957600080fd5b611935858286016117f5565b95602094909401359450505050565b6000806040838503121561195757600080fd5b8235611962816118cd565b946020939093013593505050565b6000806040838503121561198357600080fd5b50508035926020909101359150565b634e487b7160e01b600052601160045260246000fd5b80820281158282048414176116e3576116e3611992565b808201808211156116e3576116e3611992565b634e487b7160e01b600052603260045260246000fd5b6000602082840312156119fa57600080fd5b8151611714816118cd565b600060018201611a1757611a17611992565b5060010190565b6000604082016001600160a01b03851683526020604081850152818551808452606086019150828701935060005b81811015611a6857845183529383019391830191600101611a4c565b5090979650505050505050565b600060208284031215611a8757600080fd5b8151801515811461171457600080fdfea2646970667358221220d13c04429c6771be4f96c9bb89301a6f77039eb877a628e79f3c06743d31a49e64736f6c63430008110033

 このコードをまずはonline solidity decompilerで分析すると、public methodsで色々引っかかる

 今回、どのような関数で貫通ができるかわからないので、Unknownの特定をする必要がある。
 Ethereum Signature Databaseを使って、これらのmethodIDがなにかを出来る限り確かめ、関係なさそうなものを排除する。

0x033f7d45 Unknown
0x065e510f Unknown
0x0fd15747 Unknown
0x14962764 Unknown
0x1b4692fc Unknown
0x38ba746f Unknown
0x64febc38 Unknown->holderClaim(uint256[])	
0x672ef772 Unknown
0x715018a6 renounceOwnership()
0x8da5cb5b owner()
0x97145273 setHidden(address)
0xa5a4caf5 Unknown
0xa79892a0 Unknown
0xb4e33144 Unknown
0xb7b1ae09 Unknown
0xba44442d Unknown
0xbdb4b848 mintCost()
0xd02b9f63 Unknown
0xe086e5ec withdrawETH()
0xed8dcd8f Unknown
0xf19e75d4 ownerMint(uint256)
0xf2fde38b transferOwnership(address)

 holderClaim(uint256[]) しか特定できなかった。 
 ただ、ownerMint関数(0xf19e75d4)がわかっているので、これをヒントにする(NFTの移動まわりの関数があるはず。)

 } else if (var0 == 0xf19e75d4) {
            // Dispatch table entry for ownerMint(uint256)
            var1 = msg.value;
        
            if (var1) { revert(memory[0x00:0x00]); }
        
            var1 = 0x0233;
            var2 = 0x040c;
            var3 = msg.data.length;
            var4 = 0x04;
            var2 = func_171B(var3, var4);
            func_040C(var2);
            stop();

func_040C func_171Bが関係ありそうなことがわかる

    function func_171B(var arg0, var arg1) returns (var r0) {
        var var0 = 0x00;
    
        if (arg0 - arg1 i>= 0x20) { 
      return msg.data[arg1:arg1 + 0x20]; }
        else { revert(memory[0x00:0x00]); }
    }
    function func_040C(var arg0) {
        var var0 = 0x1484;
        func_15CD();
        var0 = (0x01 << 0xa0) - 0x01 & 0x000000000000000000000000ecd0cbbdbfb07986e22981c8d78e17a952605854;
        var var1 = 0x450ac250;
        var var2 = storage[0x00] & (0x01 << 0xa0) - 0x01;
        var temp0 = memory[0x40:0x60];
        memory[temp0:temp0 + 0x20] = (var1 << 0xe0) & 0xffffffff00000000000000000000000000000000000000000000000000000000;
        memory[temp0 + 0x04:temp0 + 0x04 + 0x20] = var2 & (0x01 << 0xa0) - 0x01;
        memory[temp0 + 0x24:temp0 + 0x24 + 0x20] = arg0;
        var2 = temp0 + 0x44;
        var var3 = 0x00;
        var var4 = memory[0x40:0x60];
        var var5 = var2 - var4;
        var var6 = var4;
        var var7 = 0x00;
        var var8 = var0;
        var var9 = !address(var8).code.length;
    
        if (var9) { revert(memory[0x00:0x00]); }
    
        var temp1;
        temp1, memory[var4:var4 + var3] = address(var8).call.gas(msg.gas).value(var7)(memory[var6:var6 + var5]);
        var3 = !temp1;
    
        if (!var3) { return; }
    
        var temp2 = returndata.length;
        memory[0x00:0x00 + temp2] = returndata[0x00:0x00 + temp2];
        revert(memory[0x00:0x00 + returndata.length]);
    }

func_171Bは(iii)変数を入れる+returnのfunc_0041と同じなのでデータ位置arg0(methodIDを読み込んだ後の位置)、arg1(MehodIDを除いたデータの長さ)を指定して32文字の何かを返却している。
これは、他のアドレスなどなにかしらの定数を指定していると推察(ownermintなので、MintするNFTアドレスを指定)できる。
func_040Cは

   temp1, memory[var4:var4 + var3] =
 address(var8).call.gas(msg.gas).value(var7)
(memory[var6:var6 + var5]);
    

という部分で、他のコントラクトを呼んでMintをしているように読める。
 var8=var0= (0x01 << 0xa0) - 0x01 & 0x000000000000000000000000ecd0cbbdbfb07986e22981c8d78e17a952605854;であり、

https://etherscan.io/address/0xecd0cbbdbfb07986e22981c8d78e17a952605854

のOwnerがKudasai.ethであること、及びMKのNFTアドレスであることから、ERC721のmint関数のようなものと推定できる。

ここまでの情報から、0xecd0cbbdbfb07986e22981c8d78e17a952605854を 0x450ac250というMethodIDでcallしているものが貫通関数、またはホワリス関数であることがわかる。

0x450ac250を含む関数は、func_0258 func_0298 func_03C2 func_040Cであり、これらはinternal functionなので、呼び出しているものを特定すると

0x715018a6 renounceOwnership()
0x672ef772 Unknown
0xd02b9f63 Unknown
0xf19e75d4 ownerMint(uint256)

となる。どちらが貫通関数なのかはわからないが、0x672ef772と0xd02b9f63のどちらかを実行することができれば勝ち、ということがわかった。

では、それぞれ何をしているかを見る。

(i)0x672ef772

    } else if (var0 == 0x672ef772) {
    // Dispatch table entry for 0x672ef772 (unknown)
    var1 = msg.value;

    if (var1) { revert(memory[0x00:0x00]); }

    var1 = 0x0233;
    var2 = 0x0298;
    var3 = msg.data.length;
    var4 = 0x04;
    var2 = func_1890(var3, var4);
    func_0298(var2);
    stop();

0x672ef772のCall周りを見るとfunc_0298内部で3回呼び出されている。
var3,var9,var10のアドレスは
0x000000000000000000000000a6ba2b2ab66d25cddaa6ea5dce24edd56da33a45
0x000000000000000000000000ecd0cbbdbfb07986e22981c8d78e17a952605854
0x000000000000000000000000a6ba2b2ab66d25cddaa6ea5dce24edd56da33a45
であり、var9が関係ありそうに読める。
 さらに、このcallは0x0450ac25を使っているのでmintしていることは間違いない。

temp16, memory[temp15:temp15 + 0x20] = address(var3).staticcall.gas(msg.gas)(memory[temp15:temp15 + var5 - temp15]);
temp2, memory[var5:var5 + var4] = address(var9).call.gas(msg.gas).value(var8)(memory[var7:var7 + var6]);
temp6, memory[var6:var6 + var5] = address(var10).call.gas(msg.gas).value(var9)(memory[var8:var8 + var7]);

 ちなみにvar3 では https://etherscan.io/address/0xa6ba2b2ab66d25cddaa6ea5dce24edd56da33a45 の0x6352211e(ownerOf(uint256))を呼んでおり、var10では0x42842e0e('safeTransferFrom(address,address,uint256)')))を呼んでいるのでKudasaiTicketのOwner確認NFT Mint->転送ということがわかる。
 よって、0x672ef772は貫通関数ではなく、チケットを用いたMKとの交換関数と確定できるので、この関数を攻略する意味はない。

(ii)0xd02b9f63

_______________________________________________________

    } else if (var0 == 0xd02b9f63) {
    // Dispatch table entry for 0xd02b9f63 (unknown)
    var1 = 0x0233;
    var2 = 0x03c2;
    var3 = msg.data.length;
    var4 = 0x04;
    var2, var3 = func_1970(var3, var4);
    func_03C2(var2, var3);
    stop();
_______________________________________________________
 function func_1970(var arg0, var arg1) returns (var r0, var arg0) {
        var var0 = 0x00;
        var var1 = var0;
    
        if (arg0 - arg1 i< 0x40) { revert(memory[0x00:0x00]); }
    
        var temp0 = arg1;
        r0 = msg.data[temp0:temp0 + 0x20];
        arg0 = msg.data[temp0 + 0x20:temp0 + 0x20 + 0x20];
        return r0,arg0 ;
    }

func_1970は2つの変数を返却していて、それを用いてfunc_03C2を呼んでいる。さらに、

r0 = msg.data[temp0:temp0 + 0x20];
arg0 = msg.data[temp0 + 0x20:temp0 + 0x20 + 0x20];

から、r0においてmsg.data(TXのInput)は32文字のものが1つ必要であること、arg0にtemp0(おそらく始点)から32バイトずらした32バイトのデータが必要であることが読み取れる。

 上記は日本語的に何か変だが、要は、msg.dataで送られる変数は2つであることが確定する。
 つまり、貫通関数(変数1、変数2)であることが確定し、変数1、変数2をうまく入れつつ適切なETHを送金すればMintが可能であることがここでわかる。

 では、どんな変数が必要で、どれだけのETHが必要かを特定する

func_03C2

    function func_03C2(var arg0, var arg1) {
        if (msg.sender == tx.origin) {
            var var0 = 0x1118;
            var var2 = storage[0x08];
            var var1 = arg0;
            var0 = func_19A8(var1, var2);
        
            if (msg.value == var0) {
                var0 = !!(storage[0x05] & (0x01 << 0xa0) - 0x01);
            
                if (!(storage[0x05] & (0x01 << 0xa0) - 0x01)) {
                label_120E:
                
                    if (var0) {
                        memory[0x00:0x20] = msg.sender;
                        memory[0x20:0x40] = 0x01;
                        var temp0 = keccak256(memory[0x00:0x40]);
                        memory[0x00:0x20] = storage[0x06];
                        memory[0x20:0x40] = temp0;
                        var0 = 0x0000000000000000000000000000000000000000000000000000000000000001;
                        var1 = 0x12a4;
                        var var3 = storage[keccak256(memory[0x00:0x40])];
                        var2 = arg0;
                        var1 = func_19BF(var2, var3);
                    
                        if (var1 <= var0) {
                            var0 = 0x12fa;
                            var0 = func_044A();
                        
                            if (var0) {
                                memory[0x00:0x20] = msg.sender;
                                memory[0x20:0x40] = 0x01;
                                var temp1 = keccak256(memory[0x00:0x40]);
                                memory[0x00:0x20] = storage[0x06];
                                memory[0x20:0x40] = temp1;
                                var1 = keccak256(memory[0x00:0x40]);
                                var0 = arg0;
                                var2 = 0x00;
                                var3 = 0x1372;
                                var var5 = storage[var1];
                                var var4 = var0;
                                var3 = func_19BF(var4, var5);
                                storage[var1] = var3;
                                var0 = storage[0x02];
                                var1 = 0x02;
                                var2 = 0x00;
                                var3 = 0x1387;
                                var4 = var0;
                                var3 = func_1A05(var4);
                                storage[var1] = var3;
                                var temp2 = memory[0x40:0x60];
                                memory[temp2:temp2 + 0x20] = 0x0450ac25 << 0xe4;
                                memory[temp2 + 0x04:temp2 + 0x04 + 0x20] = msg.sender;
                                memory[temp2 + 0x24:temp2 + 0x24 + 0x20] = arg0;
                                var0 = (0x01 << 0xa0) - 0x01 & 0x000000000000000000000000ecd0cbbdbfb07986e22981c8d78e17a952605854;
                                var1 = 0x450ac250;
                                var2 = temp2 + 0x44;
                                var3 = 0x00;
                                var4 = memory[0x40:0x60];
                                var5 = var2 - var4;
                                var var6 = var4;
                                var var7 = 0x00;
                                var var8 = var0;
                                var var9 = !address(var8).code.length;
                            
                                if (var9) { revert(memory[0x00:0x00]); }
                            
                                var temp3;
                                temp3, memory[var4:var4 + var3] = address(var8).call.gas(msg.gas).value(var7)(memory[var6:var6 + var5]);
                                var3 = !temp3;
                            
                                if (!var3) { return; }
                            
                                var temp4 = returndata.length;
                                memory[0x00:0x00 + temp4] = returndata[0x00:0x00 + temp4];
                                revert(memory[0x00:0x00 + returndata.length]);
                            } else {
                                var temp5 = memory[0x40:0x60];
                                memory[temp5:temp5 + 0x20] = 0x461bcd << 0xe5;
                                memory[temp5 + 0x04:temp5 + 0x04 + 0x20] = 0x20;
                                memory[temp5 + 0x24:temp5 + 0x24 + 0x20] = 0x0e;
                                memory[temp5 + 0x44:temp5 + 0x44 + 0x20] = 0x4e6f204d6f7265204b616e747375000000000000000000000000000000000000;
                                var0 = temp5 + 0x64;
                            
                            label_0529:
                                var temp6 = memory[0x40:0x60];
                                revert(memory[temp6:temp6 + var0 - temp6]);
                            }
                        } else {
                            var temp7 = memory[0x40:0x60];
                            memory[temp7:temp7 + 0x20] = 0x461bcd << 0xe5;
                            memory[temp7 + 0x04:temp7 + 0x04 + 0x20] = 0x20;
                            memory[temp7 + 0x24:temp7 + 0x24 + 0x20] = 0x0f;
                            memory[temp7 + 0x44:temp7 + 0x44 + 0x20] = 0x4e6f204d6f7265204b7564617361690000000000000000000000000000000000;
                            var0 = temp7 + 0x64;
                            goto label_0529;
                        }
                    } else {
                        var temp8 = memory[0x40:0x60];
                        memory[temp8:temp8 + 0x20] = 0x461bcd << 0xe5;
                        memory[temp8 + 0x04:temp8 + 0x04 + 0x20] = 0x20;
                        memory[temp8 + 0x24:temp8 + 0x24 + 0x20] = 0x1c;
                        memory[temp8 + 0x44:temp8 + 0x44 + 0x20] = 0x596f7572206164647265737320697320426c61636b6c69737465642100000000;
                        var0 = temp8 + 0x64;
                        goto label_0529;
                    }
                } else {
                    var temp9 = memory[0x40:0x60];
                    memory[temp9:temp9 + 0x20] = 0x0c0d074900000000000000000000000000000000000000000000000000000000;
                    memory[temp9 + 0x04:temp9 + 0x04 + 0x20] = msg.sender;
                    memory[temp9 + 0x24:temp9 + 0x24 + 0x20] = arg0;
                    memory[temp9 + 0x44:temp9 + 0x44 + 0x20] = arg1;
                    var0 = storage[0x05] & (0x01 << 0xa0) - 0x01;
                    var1 = 0x0c0d0749;
                    var2 = temp9 + 0x64;
                    var temp10 = memory[0x40:0x60];
                    var temp11;
                    temp11, memory[temp10:temp10 + 0x20] = address(var0).staticcall.gas(msg.gas)(memory[temp10:temp10 + var2 - temp10]);
                    var3 = !temp11;
                
                    if (!var3) {
                        var temp12 = memory[0x40:0x60];
                        var temp13 = returndata.length;
                        memory[0x40:0x60] = temp12 + (temp13 + 0x1f & ~0x1f);
                        var0 = 0x120e;
                        var2 = temp12;
                        var1 = var2 + temp13;
                        var0 = func_1A75(var1, var2);
                        goto label_120E;
                    } else {
                        var temp14 = returndata.length;
                        memory[0x00:0x00 + temp14] = returndata[0x00:0x00 + temp14];
                        revert(memory[0x00:0x00 + returndata.length]);
                    }
                }
            } else {
                var temp15 = memory[0x40:0x60];
                memory[temp15:temp15 + 0x20] = 0x461bcd << 0xe5;
                memory[temp15 + 0x04:temp15 + 0x04 + 0x20] = 0x20;
                memory[temp15 + 0x24:temp15 + 0x24 + 0x20] = 0x19;
                memory[temp15 + 0x44:temp15 + 0x44 + 0x20] = 0x4d696e7420636f737420697320696e73756666696369656e7400000000000000;
                var0 = temp15 + 0x64;
                goto label_0529;
            }
        } else {
            var temp16 = memory[0x40:0x60];
            memory[temp16:temp16 + 0x20] = 0x461bcd << 0xe5;
            memory[temp16 + 0x04:temp16 + 0x04 + 0x20] = 0x20;
            memory[temp16 + 0x24:temp16 + 0x24 + 0x20] = 0x1c;
            memory[temp16 + 0x44:temp16 + 0x44 + 0x20] = 0x5265656e7472616e6379204775617264206973207761746368696e6700000000;
            var0 = temp16 + 0x64;
            goto label_0529;
        }
    }
    

 まず、if (msg.value == var0) {の部分から、ETHの送金すべき量がわかる。
 var0を見るとfunc_19A8を呼んでいて、temp1 * temp0;の値を入れている。これはarg0,arg1の積なので、var1,var2の積となる。これをどんどん遡る。

 function func_19A8(var arg0, var arg1) returns (var r0) {
        var temp0 = arg1;
        var temp1 = arg0;
        var var0 = temp1 * temp0;
    
        if ((temp1 == var0 / temp0) | !temp0) { return var0; }
       //以下はオーバーフローのハンドリングのため関係ない
        var var1 = 0x16e3;
        memory[0x00:0x20] = 0x4e487b71 << 0xe0;
        memory[0x04:0x24] = 0x11;
        revert(memory[0x00:0x24]);
    }
    

var var1 = arg0; var var2 = storage[0x08]; func_03C2(var2, var3);から、
func_1970のr0 = msg.data[temp0:temp0 + 0x20];とstorage[0x08]の掛け算であることがわかる。

 貫通関数において、Mintする数がr0、一つあたりのNFT単価がstorage[0x08]である、とすれば矛盾はなく、1つ目の変数はMint数と確定(temp0:temp0 + 0x20なので、初期位置+32なので1つ目)できる。

 storage[0x08]の値は、以下のようなpublic宣言した定数であるため、Contract creation codeのどこかにいる。

pragma solidity ^0.6.6;

contract StorageExample {
    uint256 public var0;      // storage slot 0
    uint256 public var1;      // storage slot 1
    uint256 public var2;      // storage slot 2
    uint256 public var3;      // storage slot 3
    uint256 public var4;      // storage slot 4
    uint256 public var5;      // storage slot 5
    uint256 public var6;      // storage slot 6
    uint256 public var7;      // storage slot 7
    uint256 public var8;      // storage slot 8
}

なので、以下のPythonコードを使ってスロットの値を読み取る。

from web3 import Web3

def read_storage_slot_at_block(contract_address, slot, block_number, infura_project_id):
    # Infuraのエンドポイントを設定
    infura_url = f'https://ethereum.blockpi.network/v1/rpc/apikey'
    web3 = Web3(Web3.HTTPProvider(infura_url))

    # コントラクトアドレスを設定
    contract_address = Web3.toChecksumAddress(contract_address)

    # ストレージスロットを指定したブロック番号で読み取るためのRPCリクエストを手動で作成

    # ストレージスロットを読み取る
    slot_value = web3.eth.get_storage_at(contract_address, slot, block_identifier=block_number)

    # 結果を表示
    print(f'Storage slot {slot} at block {block_number}: {slot_value.hex()}')

# 使用例
contract_address = '0x2ABBAf5687bE52ae179Fb65358baB4299D6b3829'  # デプロイされたコントラクトのアドレス
slot = 8  # 読み取るストレージスロット番号
block_number = 16240501  # 読み取るブロック番号
infura_project_id = 'YOUR_INFURA_PROJECT_ID'  # InfuraプロジェクトIDを入力

read_storage_slot_at_block(contract_address, slot, block_number, infura_project_id)

Storage slot 8: 0x000000000000000000000000000000000000000000000000016345785d8a0000
という値が返ってきて、100000000000000000ということがわかる。これは0.1ETHに相当する。
(Block番号を指定しないと、今の価格である0.5ETHが取得されてしまうので注意)

よって送金金額0.1ETH、変数1を1(0x0000000000000000000000000000000000000000000000000000000000000001)
とすればよいことがわかる。

変数2個目の特定

 msg.data[temp0:temp0 + 0x20]のようなものから変数1個目を特定することができたので、func_03C2内部のmsg.dataを呼び出している箇所を参照するが、存在しない。
  しかし、そもそもfunc_03C2を呼び出す時、func_1970を呼んでいて、var2が1つ目の引数, var3が2つ目の引数であることから、func_03C2(var arg0, var arg1)のarg1が満たす条件を特定すれば勝ちであることがわかる。

} else if (var0 == 0xd02b9f63) {
    // Dispatch table entry for 0xd02b9f63 (unknown)
    var1 = 0x0233;
    var2 = 0x03c2;
    var3 = msg.data.length;
    var4 = 0x04;
    var2, var3 = func_1970(var3, var4);
    func_03C2(var2, var3);
    stop();

 これはこれで厄介だが、コードを見ているとarg1が使われている箇所は

func_03C2

var temp9 = memory[0x40:0x60];
memory[temp9:temp9 + 0x20] = 0x0c0d074900000000000000000000000000000000000000000000000000000000;
memory[temp9 + 0x04:temp9 + 0x04 + 0x20] = msg.sender;
memory[temp9 + 0x24:temp9 + 0x24 + 0x20] = arg0;
memory[temp9 + 0x44:temp9 + 0x44 + 0x20] = arg1;
var0 = storage[0x05] & (0x01 << 0xa0) - 0x01;
var1 = 0x0c0d0749;
var2 = temp9 + 0x64;
var temp10 = memory[0x40:0x60];
var temp11;
temp11, memory[temp10:temp10 + 0x20] = address(var0).staticcall.gas(msg.gas)(memory[temp10:temp10 + var2 - temp10]);
var3 = !temp11;

しかなく、goto label_120E;でlabel_120Eにジャンプし、NFTをMintしている。

var temp9 = memory[0x40:0x60];であり、
memory[temp9 + 0x44:temp9 + 0x44 + 0x20]の位置にarg1がいるので、フリーメモリポインタの位置から68バイト進んだ位置にarg1の値を32バイト分書き込んでいる。どこや??意味わからん。

 さらにstaticcall(状態を変えないCall)をしているので、var0のアドレスに対してarg1を使ってなにかのチェックをしている。
 この部分がarg1のCheck機構と考え、storage[0x05]のアドレスに対して関数0x0c0d0749を呼び出していることがわかるため、該アドレスをチェックすればarg1の正体がわかる。

storage[0x05]を取得すると、0x000000000000000000000000946b1dd77e291c1ab1fbec43fed5abdee3e2af85

https://etherscan.io/address/0x946b1dd77e291c1ab1fbec43fed5abdee3e2af85

であり、これも当時はverifyされていなかったので、BytecodeをDecompileする。

Contract Creation Code
60a060405234801561001057600080fd5b5060405161054c38038061054c83398101604081905261002f91610080565b600061003b83826101e3565b506001600160a01b0316608052506102a2565b634e487b7160e01b600052604160045260246000fd5b80516001600160a01b038116811461007b57600080fd5b919050565b6000806040838503121561009357600080fd5b82516001600160401b03808211156100aa57600080fd5b818501915085601f8301126100be57600080fd5b8151818111156100d0576100d061004e565b604051601f8201601f19908116603f011681019083821181831017156100f8576100f861004e565b8160405282815260209350888484870101111561011457600080fd5b600091505b828210156101365784820184015181830185015290830190610119565b600084848301015280965050505061014f818601610064565b925050509250929050565b600181811c9082168061016e57607f821691505b60208210810361018e57634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156101de57600081815260208120601f850160051c810160208610156101bb5750805b601f850160051c820191505b818110156101da578281556001016101c7565b5050505b505050565b81516001600160401b038111156101fc576101fc61004e565b6102108161020a845461015a565b84610194565b602080601f831160018114610245576000841561022d5750858301515b600019600386901b1c1916600185901b1785556101da565b600085815260208120601f198616915b8281101561027457888601518255948401946001909101908401610255565b50858210156102925787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b6080516102906102bc6000396000608401526102906000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80630c0d074914610030575b600080fd5b61004361003e366004610135565b610057565b604051901515815260200160405180910390f35b6000610063848461007c565b820361007157506001610075565b5060005b9392505050565b6000828260007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663033f7d456040518163ffffffff1660e01b8152600401602060405180830381865afa1580156100e0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101049190610176565b604051602001610117949392919061018f565b60405160208183030381529060405280519060200120905092915050565b60008060006060848603121561014a57600080fd5b83356001600160a01b038116811461016157600080fd5b95602085013595506040909401359392505050565b60006020828403121561018857600080fd5b5051919050565b6bffffffffffffffffffffffff198560601b168152836014820152600060346000855481600182811c9150808316806101c957607f831692505b602080841082036101e857634e487b7160e01b86526022600452602486fd5b8180156101fc576001811461021557610246565b60ff1986168a89015284151585028a0188019650610246565b60008c81526020902060005b8681101561023c5781548c82018b0152908501908301610221565b505087858b010196505b50988552505050940197965050505050505056fea26469706673582212204217d8e66b2a9c0645aa3c88d0345aa09a86ef7962501e033111b67ad12cbd1a64736f6c6343000811003300000000000000000000000000000000000000000000000000000000000000400000000000000000000000002abbaf5687be52ae179fb65358bab4299d6b382900000000000000000000000000000000000000000000000000000000000000074b55444153414900000000000000000000000000000000000000000000000000

これに、0x0c0d0749が含まれることはわかるが、Online Solidity Decompilerでは適切な関数として表示されない。
6080をしている所まで削ってDecompilationを見ると、main()内に関数0x0c0d0749が出てくるので、これを追えばいいことがわかる。
(前半はおそらくコントラクトの初期化なので削る、黒魔術ですね)

608060405234801561001057600080fd5b506004361061002b5760003560e01c80630c0d074914610030575b600080fd5b61004361003e366004610135565b610057565b604051901515815260200160405180910390f35b6000610063848461007c565b820361007157506001610075565b5060005b9392505050565b6000828260007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663033f7d456040518163ffffffff1660e01b8152600401602060405180830381865afa1580156100e0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101049190610176565b604051602001610117949392919061018f565b60405160208183030381529060405280519060200120905092915050565b60008060006060848603121561014a57600080fd5b83356001600160a01b038116811461016157600080fd5b95602085013595506040909401359392505050565b60006020828403121561018857600080fd5b5051919050565b6bffffffffffffffffffffffff198560601b168152836014820152600060346000855481600182811c9150808316806101c957607f831692505b602080841082036101e857634e487b7160e01b86526022600452602486fd5b8180156101fc576001811461021557610246565b60ff1986168a89015284151585028a0188019650610246565b60008c81526020902060005b8681101561023c5781548c82018b0152908501908301610221565b505087858b010196505b50988552505050940197965050505050505056fea26469706673582212204217d8e66b2a9c0645aa3c88d0345aa09a86ef7962501e033111b67ad12cbd1a64736f6c6343000811003300000000000000000000000000000000000000000000000000000000000000400000000000000000000000002abbaf5687be52ae179fb65358bab4299d6b382900000000000000000000000000000000000000000000000000000000000000074b55444153414900000000000000000000000000000000000000000000000000
    function main() {
        memory[0x40:0x60] = 0x80;
        var var0 = msg.value;
    
        if (var0) { revert(memory[0x00:0x00]); }
    
        if (msg.data.length < 0x04) { revert(memory[0x00:0x00]); }
    
        var0 = msg.data[0x00:0x20] >> 0xe0;
    
        if (var0 != 0x0c0d0749) { revert(memory[0x00:0x00]); }
    
        var var1 = 0x0043;
        var var2 = 0x003e;
        var var3 = msg.data.length;
        var var4 = 0x04;
        var2, var3, var4 = func_0135(var3, var4);
        var1 = func_003E(var2, var3, var4);
        var temp0 = memory[0x40:0x60];
        memory[temp0:temp0 + 0x20] = !!var1;
        var temp1 = memory[0x40:0x60];
        return memory[temp1:temp1 + (temp0 + 0x20) - temp1];
    }

(if (var0 != 0x0c0d0749) { revert(memory[0x00:0x00]); }なので、0x0c0d0749以外はRevertされるらしい。関数が一個しかないパターンはこういう書かれ方するらしい。

    function func_0135(var arg0, var arg1) returns (var r0, var arg0, var arg1) {
        var var0 = 0x00;
        var var1 = var0;
        var var2 = 0x00;
    
        if (arg0 - arg1 i< 0x60) { revert(memory[0x00:0x00]); }
    
        var temp0 = msg.data[arg1:arg1 + 0x20];//arg1:0x04
        var var3 = temp0;
    
        if (var3 != var3 & (0x01 << 0xa0) - 0x01) { revert(memory[0x00:0x00]); }
    
        r0 = var3;
        var temp1 = arg1;
        arg0 = msg.data[temp1 + 0x20:temp1 + 0x20 + 0x20];
        arg1 = msg.data[temp1 + 0x40:temp1 + 0x40 + 0x20];
        return r0, arg0, arg1;
    }

 func_0135ではr0が1番目の変数arg0が2番目の変数, arg1が3番目の変数のように読める。
 これらの変数のうち,今回必要な変数2(arg1 上記 memory[temp9 + 0x44:temp9 + 0x44 + 0x20] = arg1;)は3番目の変数である。
 r0はmsg.sender arg0はMint数なのがわかるので、このarg1の構成要素をなんとか特定する。

次に、main関数のfunc_003Eを追う。
これは、msg.sender、Mint数、何らかのコードを引数としていて、内部で比較を行っている。

    function func_003E(var arg0, var arg1, var arg2) returns (var r0) {
        var var0 = 0x00;
        var var1 = 0x0063;
        var var2 = arg0;
        var var3 = arg1;
        var1 = func_007C(var2, var3);
    
        if (arg2 - var1) { return 0x00; }
        else { return 0x01; }
    }
    
    function func_007C(var arg0, var arg1) returns (var r0) {
        var var0 = 0x00;
        var var1 = arg0;
        var var2 = arg1;
        var var3 = 0x00;
        var var4 = (0x01 << 0xa0) - 0x01 & 0x0000000000000000000000000000000000000000000000000000000000000000;
        var var5 = 0x033f7d45;
        var temp0 = memory[0x40:0x60];
        memory[temp0:temp0 + 0x20] = (var5 & 0xffffffff) << 0xe0;
        var var6 = temp0 + 0x04;
        var temp1 = memory[0x40:0x60];
        var temp2;
        temp2, memory[temp1:temp1 + 0x20] = address(var4).staticcall.gas(msg.gas)(memory[temp1:temp1 + var6 - temp1]);
        var var7 = !temp2;
    
        if (!var7) {
            var temp3 = memory[0x40:0x60];
            var temp4 = returndata.length;
            memory[0x40:0x60] = temp3 + (temp4 + 0x1f & ~0x1f);
            var4 = 0x0104;
            var6 = temp3;
            var5 = var6 + temp4;
            var4 = func_0176(var5, var6);
            var temp5 = var1;
            var1 = 0x0117;
            var temp6 = var2;
            var2 = temp5;
            var temp7 = var3;
            var3 = temp6;
            var temp8 = var4;
            var4 = temp7;
            var5 = temp8;
            var6 = memory[0x40:0x60] + 0x20;
            var1 = func_018F(var2, var3, var4, var5, var6);
            var temp9 = memory[0x40:0x60];
            var temp10 = var1;
            memory[temp9:temp9 + 0x20] = temp10 - temp9 - 0x20;
            memory[0x40:0x60] = temp10;
            return keccak256(memory[temp9 + 0x20:temp9 + 0x20 + memory[temp9:temp9 + 0x20]]);
        } else {
            var temp11 = returndata.length;
            memory[0x00:0x00 + temp11] = returndata[0x00:0x00 + temp11];
            revert(memory[0x00:0x00 + returndata.length]);
        }
    }

 arg2と var1 = func_007C(var2, var3);が同じ時は0、違うときは1を返すことが書かれている。また、アドレス、Mint数を引数として、func_007Cを実行している。今、arg2はfunc_007C(var2, var3)と同じであれば通りそうに読めるので、この関数の返り値が求めるべき答えである。

 func_007Cを読むと、結局keccak256(memory[temp9 + 0x20:temp9 + 0x20 + memory[temp9:temp9 + 0x20]]);をしているので、これの長さと中身を特定すればOK。kcccak256をしているので、(msg.sender,1,なにか1、なにか2、なにか3・・・・)というなにかの数と中身を特定すればOK。

ここで、本コントラクトのストレージを見ると、

Storage slot 0 at block 16240501: 0x4b5544415341490000000000000000000000000000000000000000000000000e
Storage slot 1 at block 16240501: 0x0000000000000000000000000000000000000000000000000000000000000000
Storage slot 2 at block 16240501: 0x0000000000000000000000000000000000000000000000000000000000000000
Storage slot 3 at block 16240501: 0x0000000000000000000000000000000000000000000000000000000000000000
Storage slot 4 at block 16240501: 0x0000000000000000000000000000000000000000000000000000000000000000
Storage slot 5 at block 16240501: 0x0000000000000000000000000000000000000000000000000000000000000000
Storage slot 6 at block 16240501: 0x0000000000000000000000000000000000000000000000000000000000000000
Storage slot 7 at block 16240501: 0x0000000000000000000000000000000000000000000000000000000000000000
Storage slot 8 at block 16240501: 0x0000000000000000000000000000000000000000000000000000000000000000
Storage slot 9 at block 16240501: 0x0000000000000000000000000000000000000000000000000000000000000000

となっているので、

を使ってslot0を変換するとKUDASAIという文字がでてくる。
ここで、storageを使っているのはfunc_018F内部の
var temp1 = storage[arg2];
memory[var1 + temp7 + arg4:var1 + temp7 + arg4 + 0x20] = storage[temp6];
の2つしかなく、
arg2=func_007C内部のvar4=temp7=var3=0x00から、arg2自体は0,
var temp1 = storage[arg2];からtemp1はstorage[0]であることがわかる。
つまり、ここはKUDASAIという文字を読み込んでいる。

 func_018Fのreturn文はreturn var7 + temp5;しかなく、func_018F内のkeccak256(memory[0x00:0x20]);しか、keccak256は登場しない。memory[0x00:0x20] = arg2;から、keccak256(0)をこの部分はしており、KUDASAIという文字は関係なさそうであるため、func_018FはKUDASAIという文字をkeccakせずに単純に返すだけのように見える。これは、string型なのかbytes32なのかわからない。

また、temp2, memory[temp1:temp1 + 0x20] = address(var4).staticcall.gas(msg.gas)(memory[temp1:temp1 + var6 - temp1]);
でなにかのコントラクトをstaticcallで呼んでいて、
0x033f7d45のmethodIDを持つものを呼んでいる。
このmethodIDは、上記MKのコントラクトで0x033f7d45 Unknownとなっているものと一致しているので、アドレスはわからなくても容易類推できる。

from web3 import Web3

# Connect to an Ethereum node
infura_url = f'https://ethereum.blockpi.network/v1/rpc/apikey'

web3 = Web3(Web3.HTTPProvider(infura_url))

# Check if connected
if not web3.isConnected():
    raise Exception("Failed to connect to Ethereum node")

# Contract address and function signature
contract_address = "0x2ABBAf5687bE52ae179Fb65358baB4299D6b3829"
function_signature = "0x033f7d45"
block_number = 16240462  # Replace with the desired block number

# Create a contract object
contract = web3.eth.contract(address=contract_address, abi=[])

# Call the function at the specified block
try:
    result = web3.eth.call({
        "to": contract_address,
        "data": function_signature
    }, block_identifier=block_number)
    print(result.hex())
except Exception as e:
    print(f"An error occurred: {e}")

というコードでReadをすると、0x0000000000000000000000000000000000000000000000000000000000000000ということがわかる。(現状は0x000000000000000000000000000000000000000000000000000000000000000a であり、Blocknumber指定を16240477などにするとこの数が1ずつ増える)

以上から、これも多分使う値で、0x0000000000000000000000000000000000000000000000000000000000000000がいれるものと考えられる。値の取り扱い方的にもuintであることが推定される。

ここまでの結果から、求めるべき変数2は、
keccak256(msg.sender,1,KUDASAI,0)か、keccak256(msg.sender,1,0,KUDASAI)のどちらか、または、

keccak256(msg.sender,1,0)か、keccak256(msg.sender,1,KUDASAI)あたりだろう、とあたりがつく。これ以上の変数はなさそう。

pragma solidity ^0.8.15;

contract Test {

    function test(address user, uint256 amount,string memory moji,uint256 nanika) public view returns (bytes32) {
        return keccak256(abi.encodePacked(user, amount, moji, nanika));
    }
    function test2(address user, uint256 amount, bytes32 moji,uint256 nanika) public view returns (bytes32) {
        return keccak256(abi.encodePacked(user, amount, moji, nanika));
    }

}

上記のコードで、keccak256したものを返すようにして、色々inputデータ用の中身を用意すると、
以下の最初に貫通関数を動かした人の、
https://etherscan.io/tx/0x781e5c1b39a3ac92667cf7d4846c7798f1b76bc39c0429539fd495caec11a598

Function: kantsu(uint256 _quantity, bytes32 _code) ***

MethodID: 0xd02b9f63
[0]:  0000000000000000000000000000000000000000000000000000000000000001
[1]:  469ca0c2c238c681cccc01d18b21fa7006cb92c78441261de267f3539cba9c8f

469ca0c2c238c681cccc01d18b21fa7006cb92c78441261de267f3539cba9c8fと同じものがtest関数から手に入る。

 実際に貫通するときは、どれが正解かわからないのでvalueを0.1ETH、mint数を1、変数2をtest関数などで思いつくパターンを返り値にしてTransaction simulationするのが無難であろう。

https://etherscan.io/address/0x7a8841ca0bbc69777e017a9e989f3bb6e16d4b13

 実は,minterコントラクトがもう一つあるので、ワンちゃんいまでも貫通できないかな、って見ていたけどhiddenが設定されていないしminterでもないから無理そう。

最後に

 ほぼメモ書きに近いし、このNoteを読んでもほとんどの人は意味がわからないなぞの呪文でしょう。誰に需要があるのだろうか?って感じだけど次に大きく稼ぐための自己満足的記事です。需要なんか関係ない。

 自分が読み返しても追うのが大変なんだけど、DeFi Hackerはこういうものを丁寧に追ってラグプルしているのでしょう。

 簡単な署名をするコントラクトのくせに、Verifyされていないと死ぬほど読むのが難しいですね・・・・。いい勉強になったので、次は個人的につよつよBotterのコントラクトを解析しようと思う。 

 もっとやりやすいツールとかないのかな・・・。二日くらいずっとこの記事書いていたのだけど、変数の数の特定とか、型の特定、順番あたりがまだすぐにわからないから研鑽が必要そう。

 なにか質問、コメントあればQASH鯖あたりか、Twitterにでも書き込んでおいてください。大体読んでます。




この記事が気に入ったらサポートをしてみませんか?