30日OS自作入門21日目(Win10)

はじめに

目次

OSを守ろう

文字列表示APIを今度こそ(harib18a)

前回hello2.hrbがうまく動作しなかった原因を調べるようです。その原因はセグメントにあるようですね。文字列の場合だとセグメントを指定をしていなかったために正しい場所を読みだしていなかったということのようです。よって、コードセグメントの設定を行ったcmd_appからhrb_appにメモリを利用してデータを渡す修正をしています。ほんとポインタって便利ですね。

実行すると
f:id:No000:20200207171121p:plain お…ちゃんとできた。

アプリケーションをC言語で作ってみたい(harib18b)

アプリケーションを書く時にアセンブラを使ってきたわけですがC言語の方が複雑なことができるし、実行できるようになりたいよねって話のようでした。でも、ライブラリとかはないのでa_nask.nasAPIをたたく処理はやるみたいです。

その際に重要となっている点が、アプリケーションの最初の処理として以下のようなものが必要なことのようです。

[BITS  32]
           CALL      0x1b
           RETF

0x1bはHariMainの番地ですので、実行開始関数を呼び出して、終わったらコンソールの処理に戻ってこい!ってコードのようですね。ここら辺は本来はコンパイラがやるのだろうか…?OSがやるのだろうか…?気になるけど詳しく載っていないので、知っている方がいれば教えてください。

実行すると

OSを守ろう(1)(harib18c)

とりあえず悪さをするアプリケーションを書いてどんなことをやるのかを、試してみる回って感じです。

まず、悪さをするアプリケーションのコードが

void HariMain(void)
{
  *((char *) 0x00102600) = 0;
  return;
}

勝手にメモリの値をいじっている時点でやばさ満点ですね☆

実行してみると

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

これらの処理は複雑ですが、スタックとセグメントに注目して、セグメント同士をいかにして干渉させないかを考えてやればわかりやすくなるかも…と感じました。

実行すると

例外をサポートしよう(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ⅾがあらかじめアプリの異常終了の際に自動で割り込まれるように決まっているからのようです。

実行すると

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

実行すると

OSを守ろう(4)(harib18g)

harib18gのような攻撃から守るための設定をするようです。この際に要点となっていると思うのは
・アクセス権に0x60を足せば、アプリ用のセグメント扱いになる
・OSからアプリ用のセグメントにfar-CALL・far-JMPができないので、RETFを使う(スタックのデータでだます)
・この方法だとセグメントごとのスタックの切り替えは、CPUがやってくれる。
・アプリとセグメントを明確に分けているので、APIもしっかりとアプリ用と設定する必要がある。

あとは、アプリの始まり方は
1.汎用レジスタを使ってセグメントレジスタに指定のデータを格納
2.RETF命令を出せるようにスタックの調整
でアプリのセグメントに飛べるようです。

ということでコードを修正して
よし実行だ!

f:id:No000:20200209143047p:plain

ナレーター「crack1.hrbからの防衛に失敗しました。」
猫   「は?」

確実にタイプミスですね。Winmergeで探すと

_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さんがぁ…

よし!直したので実行してみると

お仕置き完了

アセンブリ言語

ローカルラベル

c言語のブロック内変数のようなイメージみたいですね。
参考

page0015 - essen-wiki

メモ

・HariMainをCALLしている理由
・セグメントの3~1002はTSS用
・far-JMPやfar-CALLができないからRETFで代用するのって、裏技的なことでやってるのか?それともそうするって決まってたか?

最後に

例外が来るとイラッっとすることもありましたが、これを体験すると非常にありがたみをかんじますね。