30日OS自作本22日目1セキュリティ編
22日目の内容になります。
長すぎるので3つに分けます。 3日目以降でやや難易度が高くなっている章だと思われます。
今回はセキュリティ編です。
バグ発見のための拡張
- スタック例外の実装
- スタック例外(配列に対して大きすぎるインデックス等)は0x0cでハンドルされる。ここに割り込みハンドラを登録すればOK
- QEMUではうまく動かないようだ
- 強制終了の実現方法
- bashのctrl + c 的なやつ
- 強制終了(asm_end_app)の命令を作成し、この命令をtask.eipに書き込む
- 本書ではctrl + F1を押したタイミングで発動
_asm_end_app, ; EAXはtss.esp0の番地 MOV ESP ,[EAX] MOV DWORD [EAX+4] ,0 POPAD RET ; cmd_appへ帰る
- 元のESPをEAXに代入した状態で呼びます。
- このサブルーチン内でESPや各レジスタの値を退避してあるスタックからPOPADで戻してreturn(呼び出し元cmd_appへ戻る)します。
個人的にはバグ発見のツールとしては以下のものが欲しい
- ホスト側のOSでメモリやレジスタをリアルタイムでダンプする
- ホスト側のOSからゲストOSの機械語を1命令ずつステップ実行する
- かつ、任意のタイミングでレジスタ、メモリが参照できる
- ホスト側のOSにログが残せる
log.println("message")
みたいにかけばホスト側のtxtファイルにログが残せる
- デバッグ時の詳細出力
なお最後のものは自力で作れる範囲で作ってみた。 一般保護例外捕捉時にスタックに自動的に積まれる値を表示しているだけ。
一応この部分のソースを貼っておく
int *inthandler0d(int *esp) { struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec); struct TASK *task = task_now(); char s[30]; cons_putstr0(cons, "¥n0D General Protected Exception¥n"); sprintf(s, "CS:EIP = %04X:%08X¥n", esp[12],esp[11]); cons_putstr0(cons, s); sprintf(s, "SS:ESP = %04X:%08X¥n",esp[15],esp[14]); cons_putstr0(cons, s); sprintf(s, "EFLAGS = %08x¥n",esp[13]); cons_putstr0(cons, s); sprintf(s, "ERROR CODE = %08x¥n",esp[10]); cons_putstr0(cons, s); return &(task->tss.esp0); /* 異常終了させる */ }
様々な攻撃を防ぐ
crack3,アプリケーションからタイマーを不正に弄る
GUI(マウス等)の描画崩壊を狙ったタイマーに不正な値を書き込むプログラムです。 アプリケーション領域からのIOポートの利用を制限されているのでこのプログラムはフォールトの例外が発生します。
EFLAGSレジスタのIOPLフィールドが動作レベル(CPL)より高い特権レベルを持っているのでIOポートの利用が制限されているということになります。
アプリケーション用のセグメントの特権レベルは21日目に3(一番弱い)で設定してあります。
EFLAGSレジスタはPOPFD命令で設定できるようです。 都合の良いことにnaskfunc.nasにPOPFD専用のサブルーチンがあったので利用してみます。
_io_store_eflags, ; void io_store_eflags(int eflags); MOV EAX ,[ESP+4] PUSH EAX POPFD ; POP EFLAGS という意味 RET
権限の高いセグメントで動いているPGでELGASを書き換えてcrack3を実行してみました。
//ファイル先頭らへんに定義 #define EFLAGS_IOPL_3 0x00003000 (中略) //これをhariMainで実行 int eflg; eflg = io_load_eflags(); eflg |= EFLAGS_IOPL_3; /* IOPL=3 */ io_store_eflags(eflg);
OSの方で画面出力してみましたが、
- 改修前,0246
- 改修後,3246
でうまく変更できているように思えます。
hex | binary |
---|---|
eflags | 00!!000000000000 |
0246 | 0000001001000110 |
3246 | 0011001001000110 |
eflagsの!
のところがIOPL。上から3,4bit目
4バイト目の下位2ビットが1になっていることを確認できたのですが、
EFLAGSレジスタの値は正しく更新できているのですが、 特に変化はなしです。なんか俺が試したこと全部失敗してるな~
と思いましたが、例外補足時のログで実行時にIOPLが変化してしまっていることが発覚。 おそらくhariMain内で書いた後にどこかで変わっていたのだと思います。探しても見つかりませんでした。
IOPLが変化した理由
並行して読んでいるはじめて読む486がちょうどマルチタスクの章に入ったところで気づきました。
- EFLAGSレジスタはOS全体で共有しているものではなくて、タスクごとに用意されている
そうです。hariMainでEFLAGSを変えても、別タスクであるconsoleのEFLAGSには変化はないのです。 TSSにEFLAGSが含まれているのをみて気付きましたが、これに気付いた時かなりスッキリしました。
...ということでなんとかしてconsoleのタスクからeflagsを変更する必要があります。
IOPLを変更できるコマンドを作成
とりあえずIOPLを変更できるコマンドを作ってみました。
IOPL SET <Privilege level>
最強の0から最弱の3まで全て設定可能です。これで実行時にアプリケーションがIOポートにアクセスすることができます。
cracks4,CLI + HLT命令で割り込みを無効化する
[INSTRSET "i486p"] [BITS 32] CLI fin, HLT JMP fin
こちらも正しく例外でストップします。アプリケーションの権限で動いているときにHLTとCLI命令を実行すると例外扱いになります。 HLT命令をアプリケーションが直接実行するのはまずいので、タスクをスリープさせるAPIを呼ぶべきです。
crack5,OSの領域にあるCLI命令を含むサブルーチンを呼ぶ
[INSTRSET "i486p"] [BITS 32] CALL 2*8,0xac1 MOV EDX ,4 INT 0x40
アプリのセグメントからOSのセグメントのPGを呼び出すfarCALLは失敗します。 コールゲート(ゲートディスクリプタ)、という仕組みを利用してCLIを含むサブルーチンがロードされているセグメントをアプリケーションから呼べるように設定すればうまくいく気がしますが、気が向いたら試してみます。
(おまけ),crack6
osのルートディレクトリを破壊するようなAPIを呼ぶcrack6です。 そんなAPI定義するなよ!!! という感じですが、プロテクトモードのセグメントの設定をきちんとすれば、このくらいしかこのOSを攻撃する方法はないということです。 これは結構すごいことじゃないでしょうか。と思いましたが、多分まだまだ僕の知らない攻撃方法はあるのでしょう。
各攻撃バイナリのエラーを表示してみる
悪意のあるアプリ | 攻撃内容 | 備考 |
---|---|---|
crack1(p428) | C言語でルートディレクトリにヌル文字(0x00)を書き込む | QEMUで例外捕捉せず |
crack2 | アプリからOS用のセグメントにアクセスし、1と同じくルートディレクトリを壊す | QEMUで例外捕捉 |
crack3 | アプリからIOを不正利用 | QEMUで例外捕捉 |
crack4 | アプリからCLI HLT命令を実行 | QEMUで例外補足 |
crack5 | アプリからOSの命令をfar Call | QEMUで例外捕捉 |
ログ内容
バイナリ | CS,EIP | SS,ESP | FLAGS | ERROR CODE |
---|---|---|---|---|
crack2 | 1F5B,00000005 | 1F63,00010000 | 00000206 | 00000008 |
crack3 | 1F5B,00000002 | 1F63,00010000 | 00000206 | 00000000 |
crack4 | 1F5B,00000000 | 1F63,00010000 | 00000206 | 00000000 |
crack5 | 1F5B,00000000 | 1F63,00010000 | 00000206 | 00000010 |
せっかく作ったログを使ってデータを参照したいのですが、どうやら.mapファイルがないと難しいようです。nasmでmapファイルを作る方法などをググって試してみましたが、うまくいきませんでした。(アセンブル時にエラー)
次回は.hrbファイル構造の解説とそれを利用したOSの改善について書きます。