脆弱性に関する実験:スタックオーバーフロー(その06)
スタック上のプログラムを実行する方法
前回、スタックオーバーフロー編は一旦終了、と書きましたが、ちょっと進展があったので追記します。
このシリーズの1回目で「スタックに書き込んだプログラムは実行できない」と書きました。でも、それを回避する方法がありました。
スタック オーバーフローを使って任意のロジックを実行するというパズルは解けたので、「では、皆さんはどうやっているのだろう?」とGoogleってみました。
まぁ、色んな人が書いています。その中にこんなのがありました。(詳しく書いてあるので大変参考になります。)
Googleでヒットしたのが途中のページだったので「なんだ、これって私が最初に試した方法じゃん。これで実行できるのかな」と思っていたのですが、次のページでプログラムをコンパイルする時に“-z execstack”というオプションを付けてしました。
(最初から読みたい人は Buffer Overflows and You から )
早速、私も試してみました。まずは、第01回目に紹介したオプション無しの方法です。
ubuntu@ubuntu1304d64:~/Temp$ cc -o fake2 fake2.c -fno-stack-protector ubuntu@ubuntu1304d64:~/Temp$ ./fake2 Segmentation fault (core dumped) ubuntu@ubuntu1304d64:~/Temp$
NXビット(と思われる)機能のためにアクセス違反になります。では、オプションを付けてコンパイル、実行してみましょう。
ubuntu@ubuntu1304d64:~/Temp$ cc -o fake2 fake2.c -fno-stack-protector -z execstack ubuntu@ubuntu1304d64:~/Temp$ ./fake2 $ ps PID TTY TIME CMD 3745 pts/2 00:00:00 bash 4692 pts/2 00:00:00 sh 4693 pts/2 00:00:00 ps $ ps -f UID PID PPID C STIME TTY TIME CMD ubuntu 3745 3744 0 19:18 pts/2 00:00:00 -bash ubuntu 4692 3745 0 21:07 pts/2 00:00:00 [sh] ubuntu 4694 4692 0 21:07 pts/2 00:00:00 ps -f $ env PWD=/home/ubuntu/Temp $ exit ubuntu@ubuntu1304d64:~/Temp$
おぉ、動きますね。このオプションを付けてコンパイルしたプログラムであれば、スタックに書き込んだ命令で何でも実行できます!
調べみるとこのオプションはコンパイラではなくリンカに対するオプションでした。
しかし、普通、このオプションを付けてソフトウェアを作成することはないでしょうから「なんだかなぁ」という感じです。(それを言ってしまえば、-fno-stack-protector オプションの指定も禁じ手みなたいなものですが。)
とは言っても、そのソフトウェアが“-z execstack”付きでコンパイルされてのかどうか知りたくなるというのは人情です。さて、どうすれば。fileコマンドでもnmコマンドでもそれらしい違いは出て来ません。objdumpで逆アセンブルした結果も同じです。(ただし、ビルドIDは除きます。)オプション有り無しの二つのオブジェクトをダンプした結果、0x1ccバイト目(460バイト目)の第一ビットが1か0かだけの違いでした。
460バイト目というファイルのごくごく最初の部分ですから、コンパイルされたプログラム側ではなく、実行ファイルのファイルヘッダ情報として入っているようです。であれば、ELF関連のコマンドで情報がとれそうです。ということで、まずはreadelfを使ってみました。
オプション無しの fake2.n とオプション有りの fake2.y の情報を表示してみましょう。
ubuntu@ubuntu1304d64:~/Temp$ readelf -l fake2.n | grep -A 1 GNU_STACK GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 8 ubuntu@ubuntu1304d64:~/Temp$ readelf -l fake2.y | grep -A 1 GNU_STACK GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RWE 8 ubuntu@ubuntu1304d64:~/Temp$
どうも“GNU_STACK”に対して“E”フラグ立っているとスタック上のプログラムを実行できるようです。
更に調べるとLinuxのコマンドで GNU_STACK が正常な状態かどうか(つまりスタックが実行可能で無いかどうか)を調べるコマンドがありました。
ubuntu@ubuntu1304d64:~/Temp$ scanelf -qe . RWX --- --- ./fake2.y ubuntu@ubuntu1304d64:~/Temp$
ん、ということは、このビットを強制的に書き換えると任意のバイナリでスタックでのプログラム実行ができてしまうのでしょうか? では実験してみましょう。fake2に関してはGNU_STACKのフラグの位置が分かっているので、それを使います。
ubuntu@ubuntu1304d64:~/Temp$ ./fake2.n Segmentation fault (core dumped) ubuntu@ubuntu1304d64:~/Temp$ sed -e "s/\(\x51\xe5\x74\x64\)\x06/\1\x07/" < fake2.n > fake2.m ubuntu@ubuntu1304d64:~/Temp$ chmod +x fake2.m ubuntu@ubuntu1304d64:~/Temp$ ./fake2.m $ ps PID TTY TIME CMD 3745 pts/2 00:00:00 bash 26962 pts/2 00:00:00 sh 26963 pts/2 00:00:00 ps $ exit ubuntu@ubuntu1304d64:~/Temp$
おや、実行できてしまいました。セキュリティ観点からすると、ちょっと驚きです。結局、ELFファイルはSHA1などのシグネチャで改竄されていないことを確認していないようです。
勿論、システムの保護はもっと総合的に行いいますから、単に改竄されたELFが動いたからと言って騒ぐことの程ではありません。上の例ではsedを使って書き換えているので“改ざん”の様に見えますが、リンカなどのコマンドで“設定”することも有るわけですから。
なお、このビットの書き換えについては、クラッキングの対象となる可能性は低いと考えます。なぜなら、このビットを変更できるということは、システム上で他のコマンドを実行できる訳ですから。とは言え、定期的に scanelfなどで確認しておいた方が良いかも知れません。
脆弱性に関する実験:スタックオーバーフロー編(リンク)
脆弱性に関する実験:スタックオーバーフロー(その01) - AIDEMOIRE
スタックへのマシン語の書き込みとその実行に関する実験
脆弱性に関する実験:スタックオーバーフロー(その02) - AIDEMOIRE
戻りアドレスの書き換えによるロジックの変更に関する実験
脆弱性に関する実験:スタックオーバーフロー(その03) - AIDEMOIRE
プログラムによる自分自身のプログラムの変更に関する実験
脆弱性に関する実験:スタックオーバーフロー(その04) - AIDEMOIRE
スタックの書き換えによるシェルの起動
脆弱性に関する実験:スタックオーバーフロー(その05) - AIDEMOIRE
スタックオーバーフローの脆弱性に関する考察
脆弱性に関する実験:スタックオーバーフロー(その06) - AIDEMOIRE
スタック上のプログラムを実行する方法