fr33f0r4ll

自分用雑記

X64 pwn

x64でのpwn

Cでの話、goとかだと違う。 基本はx86と同じだが、いくつかの点で違いがある。
https://blog.techorganic.com/2015/04/10/64-bit-linux-stack-smashing-tutorial-part-1/を参考にしている。

引数の渡し方

x86では関数呼び出しするときにスタックに値をpushして引数を渡していたため、スタックの値を書き変えれば引数を操作できた。 x64ではレジスタに引数の値を指定するため、引数の操作がやりにくくなっている。

引数の順番はWinとgccで違ったりする、CTFだとだいたいgcc版になる。 gcc版だと一番目からrdi, rsi, rdx, rcx, r8, r9になる。 exe版だとrcx, rdx, r8, r9になる。 4個以上の引数がある関数はあまり見ないので知らない。

レジスタ

64bit長になった。 つまり、pushやpopは8バイト単位で動作し、アドレス長も8バイトになっている。 さらにレジスタ自体の数も増えて、R8やR9などが追加されている。 x86レジスタは、RAXやRSPのようにRを付けると64ビットでアクセスできる。 EAXやEBXで下位32ビットにアクセスできる。 それ以外はx86と同じようにアクセスできる(下位32ビットに対して)。

アドレス

バッファオーバーフローでリターンアドレスを書き換えEIPを奪うのはpwnではx86での上等手段だった。 例えば長い文字列を渡しバッファを溢れさせると、eipの値が0x41414141などになっているのがgdbなどで確認できる。 しかし、x64ではアドレス空間が64bitに拡張され、有効な命令アドレスの範囲は0x00007FFFFFFFFFFFまでに制限されている。 したがって、リターンアドレスを書き換え過ぎるとripが書き換えられないでプログラムがSIGSEGVされる。 このままだと確認しにくいので、ret命令時のスタックトップがリターンアドレスとして読み込まれるので、その値からオフセットを割り出し適切な長さに調節するとよい。

環境変数

環境変数はプログラム実行時にスタックに積まれているので、環境変数が設定できるなら文字列を設定し、そのアドレス引数にしたりするテクニックが使える。

ROP

レジスタを介して引数を渡すため、ROPを使う場面が増える。 例えばpop rsi; retという命令へのアドレスをリターンアドレスとして設定すると、スタック上でリターンアドレスの次にある値がRSIに設定できる。 これを利用してレジスタに値を設定することで関数に引数を渡せる。