30日OS自作入門21日目(Win10)
はじめに
目次
OSを守ろう
文字列表示APIを今度こそ(harib18a)
前回hello2.hrbがうまく動作しなかった原因を調べるようです。その原因はセグメントにあるようですね。文字列の場合だとセグメントを指定をしていなかったために正しい場所を読みだしていなかったということのようです。よって、コードセグメントの設定を行ったcmd_appからhrb_appにメモリを利用してデータを渡す修正をしています。ほんとポインタって便利ですね。
実行すると
お…ちゃんとできた。
アプリケーションをC言語で作ってみたい(harib18b)
アプリケーションを書く時にアセンブラを使ってきたわけですがC言語の方が複雑なことができるし、実行できるようになりたいよねって話のようでした。でも、ライブラリとかはないのでa_nask.nasでAPIをたたく処理はやるみたいです。
その際に重要となっている点が、アプリケーションの最初の処理として以下のようなものが必要なことのようです。
[BITS 32] CALL 0x1b RETF
0x1bはHariMainの番地ですので、実行開始関数を呼び出して、終わったらコンソールの処理に戻ってこい!ってコードのようですね。ここら辺は本来はコンパイラがやるのだろうか…?OSがやるのだろうか…?気になるけど詳しく載っていないので、知っている方がいれば教えてください。
実行すると
30日自作OS本(harib18b)
— 猫(1010) (@Wagahaiha_toto) 2020年2月7日
OSとは独立した実行開始関数で実行できるようになった。 pic.twitter.com/kehoncMsCg
OSを守ろう(1)(harib18c)
とりあえず悪さをするアプリケーションを書いてどんなことをやるのかを、試してみる回って感じです。
まず、悪さをするアプリケーションのコードが
void HariMain(void) { *((char *) 0x00102600) = 0; return; }
勝手にメモリの値をいじっている時点でやばさ満点ですね☆
実行してみると
30日自作OS本(harib18d)
— 猫(1010) (@Wagahaiha_toto) 2020年2月7日
OSのふもとのメモリを書き換えるとは…悪い子😡 pic.twitter.com/h53q62t6A8
OSを守ろう(harib18d)
要はOSのメモリにアクセスすることができてしまうのが問題なので、それを制御するためにアプリ用の領域を作っていくようです。
まず、アプリはconsoleの方で制御しているので、console.cにメモリ確保/解放のコードとセグメント登録のコードを記載しています。そして、セグメントの管理に関してをアセンブリのnaskfunc.asmで変更を加えていくようです。
OSからアプリに移動する際には
_start_app: ; void start_app(int eip, int cs, int esp, int ds); PUSHAD ; 32ビットレジスタを全部保存しておく MOV EAX,[ESP+36] ; アプリ用のEIP MOV ECX,[ESP+40] ; アプリ用のCS MOV EDX,[ESP+44] ; アプリ用のESP MOV EBX,[ESP+48] ; アプリ用のDS/SS MOV [0xfe4],ESP ; OS用のESP(アドレス) CLI ; 切り替え中に割り込みが起きてほしくないので禁止 MOV ES,BX ; 念のため MOV SS,BX MOV DS,BX MOV FS,BX ; 念のため MOV GS,BX ; 念のため MOV ESP,EDX ; アプリ用のESPを格納 STI ; 切り替え完了なので割り込み可能に戻す PUSH ECX ; far-CALLのためにPUSH(cs) PUSH EAX ; far-CALLのためにPUSH(eip) CALL FAR [ESP] ; アプリを呼び出す ; アプリが終了するトここに帰ってくる(メインルーチンに戻る) MOV EAX,1*8 ; OS用のDS CLI ; また切り替えるので割り込み禁止 MOV ES,AX MOV SS,AX MOV DS,AX MOV FS,AX MOV GS,AX MOV ESP,[0xfe4] STI ; 切り替え完了なので割り込み可能に戻す POPAD ; 保存しておいたレジスタを回復 RET
このような処理を行っていて
アプリケーション側の操作も以下のような変更が加えられています。
_asm_hrb_api: ; 都合の良いことに最初空割り込み禁止になっている PUSH DS PUSH ES PUSHAD ; 保存のためのPUSH MOV EAX,1*8 MOV DS,AX ; とりあえずDSだけOS用にする MOV ECX,[0xfe4] ; OS用のESP ADD ECX,-40 MOV [ECX+32],ESP ; アプリのESPを保存 MOV [ECX+36],SS ; アプリのSSを保存 ; PUSHADした値をシステムのスタックにコピーする MOV EDX,[ESP ] MOV EBX,[ESP+ 4] MOV [ECX ],EDX ; hrb_apiに渡すためコピー MOV [ECX+ 4],EBX ; hrb_apiに渡すためコピー MOV EDX,[ESP+ 8] MOV EBX,[ESP+12] MOV [ECX+ 8],EDX ; hrb_apiに渡すためコピー MOV [ECX+12],EBX ; hrb_apiに渡すためコピー MOV EDX,[ESP+16] MOV EBX,[ESP+20] MOV [ECX+16],EDX ; hrb_apiに渡すためコピー MOV [ECX+20],EBX ; hrb_apiに渡すためコピー MOV EDX,[ESP+24] MOV EBX,[ESP+28] MOV [ECX+24],EDX ; hrb_apiに渡すためコピー MOV [ECX+28],EBX ; hrb_apiに渡すためコピー MOV ES,AX MOV SS,AX MOV ESP,ECX STI ; やっと割り込み許可 CALL _hrb_api MOV ECX,[ESP+32] ; アプリのESPを思い出す MOV EAX,[ESP+36] ; アプリのSSを思い出す CLI MOV SS,AX MOV ESP,ECX POPAD POP ES POP DS IRETD ; この命令が自動でSTIしてくれる
そして、アプリケーション実行中に割り込みが発生した際の処理を追加しないとまずいので追加するようです。
_asm_inthandler20: ;timer PUSH ES PUSH DS PUSHAD MOV AX,SS CMP AX,1*8 JNE .from_app ; OSが動いているときに割り込まれたのでほぼ今までどおり MOV EAX,ESP PUSH SS PUSH EAX MOV AX,SS MOV DS,AX MOV ES,AX CALL _inthandler20 ADD ESP,8 POPAD POP DS POP ES IRETD .from_app: ; アプリが動いているときに割り込まれた MOV EAX,1*8 MOV DS,AX ; とりあえずDSだけOS用にする MOV ECX,[0xfe4] ; OS用のESP ADD ECX,-8 MOV [ECX+4],SS ; 割り込まれたときのSSを保存 MOV [ECX ],ESP ; 割り込まれたときのESPを保存 MOV SS,AX MOV ES,AX MOV ESP,ECX CALL _inthandler20 POP ECX POP EAX MOV SS,AX ; SSをアプリ用に戻す MOV ESP,ECX ; ESPもアプリ用に戻す POPAD POP DS POP ES IRETD
これらの処理は複雑ですが、スタックとセグメントに注目して、セグメント同士をいかにして干渉させないかを考えてやればわかりやすくなるかも…と感じました。
実行すると
30日自作OS本(harib18d)
— 猫(1010) (@Wagahaiha_toto) 2020年2月8日
OSちゃんのデータに悪いことをする子は別の部屋へ… pic.twitter.com/13oosdhb6V
例外をサポートしよう(harib18e)
例外が発生した際に、IDTに登録してある処理を実行するようにするようです。
割り込みの処理内容は以下のようであり
int inthandler0d(int *esp) { struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec); cons_putstr0(cons, "\nINT 0D :\n General Protected Exception.\n"); return 1; /* 異常終了させる */ }
OSの処理に戻したのちに、エラーメッセージを出力する処理を行うようです。また、発生するための処理を書かなくていいのですが、それはIDTの0x0ⅾがあらかじめアプリの異常終了の際に自動で割り込まれるように決まっているからのようです。
実行すると
30日自作OS本(harib18e)
— 猫(1010) (@Wagahaiha_toto) 2020年2月9日
Qemuのバグで表示がでないので、Virtualboxでやったら座禅されましたね… pic.twitter.com/g3UUyjiYXl
OSを守ろう(3)(harib18f)
今度は、OS用のセグメントを指定して攻撃を行うようです。OSのセグメントをDSに代入しOSの管理するアドレスに0を代入しています。ここで問題となるのは、OSのセグメントをDSに格納できてしまっているということみたいですね。
やらかしたアプリケーション君
[INSTRSET "i486p"] [BITS 32] MOV EAX,1*8 ; OS用のセグメント番号 MOV DS,AX ; これをDSに入れちゃう MOV BYTE [0x102600],0 RETF
実行すると
30日自作OS本(harib18f)
— 猫(1010) (@Wagahaiha_toto) 2020年2月9日
DSの方からやられたOS君 pic.twitter.com/lELgyeVsTM
OSを守ろう(4)(harib18g)
harib18gのような攻撃から守るための設定をするようです。この際に要点となっていると思うのは
・アクセス権に0x60を足せば、アプリ用のセグメント扱いになる
・OSからアプリ用のセグメントにfar-CALL・far-JMPができないので、RETFを使う(スタックのデータでだます)
・この方法だとセグメントごとのスタックの切り替えは、CPUがやってくれる。
・アプリとセグメントを明確に分けているので、APIもしっかりとアプリ用と設定する必要がある。
あとは、アプリの始まり方は
1.汎用レジスタを使ってセグメントレジスタに指定のデータを格納
2.RETF命令を出せるようにスタックの調整
でアプリのセグメントに飛べるようです。
ということでコードを修正して
よし実行だ!
ナレーター「crack1.hrbからの防衛に失敗しました。」
猫 「は?」
_start_app: ; void start_app(int eip, int cs, int esp, int ds, int tss_esp0); PUSHAD ; 32ビットレジスタを全部保存しておく MOV EAX,[ESP+36] ; アプリ用のEIP MOV ECX,[ESP+40] ; アプリ用のCS MOV EDX,[ESP+44] ; アプリ用のESP MOV EBX,[ESP+48] ; アプリ用のDS/SS MOV EBP,[ESP+52] ; tss.esp0の番地 MOV [EBP ],ESP ; OS用のESPを保存 MOV [EBP+4],SS ; OS用のSSを保存 MOV ES,BX MOV DS,BX <-ここを忘れてた MOV FS,BX MOV GS,BX
DSさんがぁ…
よし!直したので実行してみると
30日自作OS本(harib18g)
— 猫(1010) (@Wagahaiha_toto) 2020年2月9日
例外処理ができるようになったので、悪い子にはお仕置きです! pic.twitter.com/AdS5TX5dfV
お仕置き完了
アセンブリ言語
ローカルラベル
c言語のブロック内変数のようなイメージみたいですね。
参考
メモ
・HariMainをCALLしている理由
・セグメントの3~1002はTSS用
・far-JMPやfar-CALLができないからRETFで代用するのって、裏技的なことでやってるのか?それともそうするって決まってたか?
最後に
例外が来るとイラッっとすることもありましたが、これを体験すると非常にありがたみをかんじますね。