ボンジョルノー、たけけんです。
やっていて思うところ、まだまだスキル不足感が否めませんが、リターンアドレスから関数を実行するという事をやってみました。
参考サイト
OSSSME
システム奮闘記:その44
プログラム内のお話ですが、リターンアドレスというのは、呼びだし側の処理を再開するアドレスのことを指します。
リターンアドレスを書き換えるという事は、通常の領域を超えた先へ書きこみを行う事になりますが、手順については多々あるようです。
IPAさんで紹介しているのは、バイナリエディタを使って、アドレスを書き換えたテキストファイルを読んでコマンドを実行させるものです。
前々回やったのが、Kenjiさんのサイトを参考に、Kernel再構築の際に初期化をおこなう関数の中に脆弱性のある関数を実行するように改ざんし、その関数を利用してバッファオーバーフローを起こしてLinuxをフリーズさせました。
前回やったのは、アドレスを書き換える事とは違うけど、改ざんという事では仲間のような感じなので一緒にしちゃいますが、IPAさんのサイトを参考にsetuidとcreateの脆弱性を利用してroot所有ファイルを書き換えました。
でもって、リターンアドレスの探し方ですが、IPAさんではプログラムとバイナリエディタを使ってリターンアドレスを調べるという高度なことをやってますが、こちらは一番原始的な方法でやってみました。
とても簡単な方法ですが、これでもオーバーフローを利用した実験は可能なので、ぜひ見ていただきたいと思います。
一番原始的な方法とは何か・・・
0から順に試していくのです!
順番にやっていくだけなので、メモリーの使われ方にそれほど詳しくなくても十分です。
ただ、もちろん詳しい方が良いと思います。
今回参考にさせて頂いたOSSSMEの菅さんのウェブサイトですが、こちらのサイトでは、プログラムを交えてメモリーに関する考察をされてますので、自分でプログラムも動かしてヒープ領域やスタックについて知識を深めることを是非ともおすすめします。
自分も何回も読んで、プログラムを実行しました。
日本語の説明を繰り返し読むよりも、実際にプログラムを読んで書いて動かした方がよっぽど頭に入るのはなにか不思議に感じますが、それが事実なのだね。
リターンアドレスを探すソース
1 #include <stdio.h> 2 3 void messages(void) 4 { 5 printf("hello flow\n"); 6 } 7 8 void func(void) 9 { 10 int a[1]; 11 12 a[0] = 5; 13 a[1] = 4; 14 a[2] = 3; 15 a[3] = 2; 16 a[4] = 1; 17 a[5] = 0; 18 a[6] = (int)messages; 19 20 21 } 22 23 int main(void) 24 { 25 26 func(); 27 28 29 return(0); 30 31 }
実行結果
# ./a.out [/root/C] hello flow zsh: segmentation fault ./a.out
セグメンテーションエラーが出ているものの、呼び出していないmessages関数のアドレスになって実行されているようです。
原始的ですが、実行されないはずの関数が実行されていることは分かりますね。
実際にエンターキーをたたいて表示された時はちょっとびっくりしました。
では、中の様子を見てみよう。
まずはさっきのプログラムに若干の書き加えをして、領域違反している変数a[6]の書き換え前のアドレスと書き換え後のアドレスを見てみます。
参考サイトに習って、hallo を表示するものからシェルを起動するプログラムに変更してみました。(シェルコードはつかってません・・・というか動きませんでした。)
1 #include <stdio.h> 2 3 void messages(void) 4 { 5 system("/bin/sh"); 6 putchar('\n'); 7 } 8 9 void func(void) 10 { 11 int a[1]; 12 printf("%p\n", a[6]); 13 14 a[6] = (int)messages; 15 16 printf("%p\n", a[6]); 17 18 } 19 20 int main(void) 21 { 22 23 func(); 24 25 26 return(0); 27 28 }
実行結果
[takeken@testserver_centos ~]$ ./stack2 0x4005bc ←書き換え前 0x400554 ←書き換え後 sh-4.1$
それでは、このプログラムをobjdump -d で逆アセンブリしてみます。
実際はもっともっと長くて、必要な部分だけを載せています。
[takeken@testserver_centos ~]$ objdump -d stack2 | less stack2: file format elf64-x86-64 Disassembly of section .init: 0000000000400408 <_init>: 400408: 48 83 ec 08 sub $0x8,%rsp 40040c: e8 8b 00 00 00 callq 40049c <call_gmon_start> 400411: e8 1a 01 00 00 callq 400530 <frame_dummy> 400416: e8 55 02 00 00 callq 400670 <__do_global_ctors_aux> 40041b: 48 83 c4 08 add $0x8,%rsp 40041f: c3 retq 0000000000400554 <messages>: 400554: 55 push %rbp 400555: 48 89 e5 mov %rsp,%rbp 400558: bf c8 06 40 00 mov $0x4006c8,%edi 40055d: b8 00 00 00 00 mov $0x0,%eax 400562: e8 f9 fe ff ff callq 400460 <system@plt> 400567: bf 0a 00 00 00 mov $0xa,%edi 40056c: e8 cf fe ff ff callq 400440 <putchar@plt> 400571: c9 leaveq 400572: c3 retq 0000000000400573 <func>: 400573: 55 push %rbp 400574: 48 89 e5 mov %rsp,%rbp 400577: 48 83 ec 10 sub $0x10,%rsp 4005b2: c3 retq 00000000004005b3 <main>: 4005b3: 55 push %rbp 4005b4: 48 89 e5 mov %rsp,%rbp 4005b7: e8 b7 ff ff ff callq 400573 <func> 4005bc: b8 00 00 00 00 mov $0x0,%eax 4005c1: c9 leaveq 4005c2: c3 retq 4005c3: 90 nop
書き換え前のアドレスは、main関数内のfuncを呼び出したスタックの下にありますね。
書き換え後はmessages関数のアドレスになっているのが分かります。
先入れ先出しとか学校で習った気がしますけど、すっかり抜けてますね。
と言う感じで、本来ならばmain関数にもどって処理されるはずが、アドレスがmessages関数のアドレスなので、messagesを実行してしまってますね。
コンピュータはプログラム通りに動いているので、してしまっているという言い方はおかしいすね。
ちなみに、シェルコートに関しては
sysctl -w kernel.exec-shield=0
にしても、動作せず。
シェルコードではなく、そのままexeclで/bin/bashを動かしても、実行ファイルにsetuidのフラグを立ててもroot権限を不正に使うことはできませんでした。
と、ここまでダダダ!っと進んだわけですが、落ち着いてみればリターンアドレスはループで探せばいいんじゃないか。
もう、バカかとアホかと。
実行結果
HELLO WORLD a[0] : address = (nil) a[1] : address = (nil) a[2] : address = 0x400420 a[3] : address = 0x3 a[4] : address = 0xafa14110 a[5] : address = 0x7fff a[6] : address = 0x400564 a[7] : address = (nil) a[8] : address = (nil) a[9] : address = (nil) a[10] : address = 0x71b26d1d a[11] : address = 0x7f4c a[12] : address = (nil) a[13] : address = (nil) a[14] : address = 0xafa141f8 a[15] : address = 0x7fff a[16] : address = (nil) ーー省略ーー a[87] : address = 0x7fff a[88] : address = 0xafa14f5c a[89] : address = 0x7fff a[90] : address = 0xafa14f6d a[91] : address = 0x7fff a[92] : address = 0xafa14f84 a[93] : address = 0x7fff a[94] : address = 0xafa14f8c a[95] : address = 0x7fff a[96] : address = 0xafa14f9f a[97] : address = 0x7fff a[98] : address = 0xafa14faf a[99] : address = 0x7fff
視野がせまくなってた証拠ですわ。反省。
いったいなんのアドレスか分からないので、何か起きそうなアドレスをつついてみる。
以下つつく候補。
400444: e8 c7 ff ff ff callq 400410 <__libc_start_main@plt> 400449: f4 hlt a[58] : address = 0x400449 000000000040055b <main>: 40055b: 55 push %rbp a[18] : address = 0x40055b
実行してみた結果、なにも起こらない。
ふーん、という結果ですが、だいたいセグメントエラーが起こる事をやってるはずなので、なにも起きない事もおかしいので、おかしいことは起こってる訳ですね。
このアドレスがメモリリージョンかしら?
やっぱりコンピュータをするにはアセンブラもやらないとだめかなあ。
なんとなくhttpdを逆アセンブリしてみました。
35008: 48 83 3d 80 24 22 00 cmpq $0x0,0x222480(%rip) # 257490 <ap_max_mem_free+0x20> 350ac: 48 83 3d dc 23 22 00 cmpq $0x0,0x2223dc(%rip) # 257490 <ap_max_mem_free+0x20> 3514e: c7 07 00 00 00 00 movl $0x0,(%rdi) 35500: c7 05 26 0b 22 00 00 movl $0x0,0x220b26(%rip) # 256030 <ap_accept_lock_mech> 35cc8: c7 05 b6 17 22 00 00 movl $0x0,0x2217b6(%rip) # 257488 <ap_max_mem_free+0x18> 35f42: c7 04 18 00 00 00 00 movl $0x0,(%rax,%rbx,1) 35fc2: c7 44 24 20 00 00 00 movl $0x0,0x20(%rsp)
もう、なんのこっちゃです。
なんじゃこりゃってのもありましたけど
219f0: c7 05 ae 45 23 00 00 movl $0x0,0x2345ae(%rip) # 255fa8 <ap_hack_unixd_accept+0x8>
ググっても日本語はなくて、リンク先で何が起きるか分からないので、これ以上進めませんでした。
子供のころの海みたいなもんです。
うーん。
いまのところはここまでですね。
できるならもっといろいろな事がしたいですが。
何故アタック側の事を勉強しているかというと、いたちごっこの世界なので、アタック側を追いかけた方が先頭に近いと思ったんです。
ここさいきんの3回分の日記くらい脆弱性関連を続けていたわけですが
・root権限のファイルに脆弱性は厳禁
・スクリプト、プログラムに脆弱性のある関数やシステムコールは使わない事
・不要なデーモン、サービスを動かさない。
・不要なポートを開けない(↑と同じだけど)
・アップデートをする。パッチをあてる。(アプデ語の検証もしておく)
などなど。
こういったことは基本だけど、実際体験して必要なことはなんだろうと考えてみると、やっぱり基本と同じことを思ったりするわけで。
だけど本で読むだけよりも、ちょっと一段高いところから考えられるんじゃないかなぁと思う。
小学生の夏休みの宿題みたいな感想になってるな(笑)
この流れでいくと、rootで動いているSQLの脆弱性やウェブサーバーの脆弱性といったようなサーバーのミドルウェアに目がいくと思うんだけど、このまま進んでしまうと、こないだ立てた予定からずんずんぐいぐい離れて行ってしまうので、ぼちぼちTCP/IPプログラミングに戻りつつ、やっぱり気になるようだったらアプリケーションの脆弱性に手をだすか~。
な感じです。