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

はじめに

目次

C言語でアプリケーションを作ろう

OSを守ろう(5)(harib19a)

内容的にOS側に脆弱性をわざと作るって、OS側に欠陥があるとまずいですよって内容っぽいですね。
まずは例外処理が、どのくらい働いてくれるかについてを試すために、悪いアプリを書いて試すようです。

timerに誤作動を起こすcrack3.nas

[INSTRSET "i486p"]
[BITS 32]
    MOV   AL,0x34
    OUT   0x43,AL
    MOV   AL,0xff
    OUT   0x40,AL
    MOV   AL,0xff
    OUT   0x40,AL

;   ↑これは以下のプログラムに相当(タイマへの攻撃)
; io_out8(PIT_CTRL, 0x34);
; io_out8(PIT_CNT0, 0xff);
; io_out8(PIT_CNT0, 0xff);

    MOV   EDX,4
    INT   0x40

HLT命令をかますcrack4.nas

[INSTRSET "i486p"]
[BITS 32]
    CLI   ; フリーズ攻撃
fin:
    HLT
    JMP   fin

far-CALLをかますcrack5.nas

[INSTRSET "i486p"]
[BITS 32]
    CALL  2*8:0xac1
    MOV   EDX,4
    INT   0x40

crack1~5を実行すると

console.cのAPIに裏口を作って、crack6.nasでそこを突いたら

oh...変なAPIは危険!

バグ発見を手伝おう(harib19b)

配列のバグというか、配列の意図しない部分まで指定されても実行されてしまうのがまずいので、例外を出すようにするようです。

まず、以下のコードを実行してみました。(いかにもやばそうなコード)

void api_putchar(int c);
void api_end(void);

void HariMain(void)
{
  char a[100];
  a[10] = 'A';    /* これはもちろんいい */
  api_putchar(a[10]);
  a[102] = 'B';   /* これはまずいよね */
  api_putchar(a[102]);
  a[123] = 'C';   /* これもまずいよね */
  api_putchar(a[123]);
  api_end();
}

すると
f:id:No000:20200209224029p:plain

あーあ…配列で確保している範囲を超えているどころか、セグメントの壁超えてますよこれ…

あと、これってQemuのバグで実行されているだけで、実機だとエラー吐いてリセットかかるみたいですね。
そのバグが、例外の処理を書いたあとでも足を引っ張ります。(やはり実機でしてみたい…)

その後、バグを表示させるなら、例外を起こした命令の番地を知ることができるようにすることも可能のようです。しかも簡単に(そんなこともできるのか…)

実機なんてないので、試しにVMwareでやったら

表示できた!

ちなみに、bug1.mapの内容は

text size :     74(0x0004A)
data size :      0(0x00000)
bss  size :      0(0x00000)

0x00000024 : (.text)
0x00000024 : _HariMain
0x00000052 : _api_putchar
0x00000052 : (.text)
0x0000005E : _api_end
0x00000065 : (.text)
0x00000065 : _HariStartup
0x00000000 : (.data)
0x00000000 : (.data)
0x00000000 : (.data)
0x00000000 : (.bss)
0x00000000 : (.bss)
0x00000000 : (.bss)

ちゃんとHariMainの中を指示してます。

アプリの強制終了(harib19c)

EIPのデータをいじって、_asm_end_appに接続しています。

_asm_end_app:
; EAXはtss.esp0の番地
    MOV     ESP,[EAX]
    MOV     DWORD [EAX+4],0 ; タスクを0に強制的にしてる
    POPAD
    RET             ; cmd_appへ帰る

ここで、end_appを_asm_end_appに変更した場合、JNEで指定しているところも変わるので検索機能等で変更する必要あり

実行してみると

クッション君のありがたみを感じますね。

C言語で文字列表示(1)(harib19d)

今回は文字列表示APIを利用した関数がないので、作ろうって流れのようです。あれですかね?システムコールとライブラリのような関係になるのでしょうか。

そこでライブラリ的なファイルである。a_nask.nasAPIを利用したライブラリを書き、hello4.cというアプリを作っています。
a_nask.nasの文字列表示関数

_api_putstr0:   ; void _api_putstr0(char *s);
    PUSH    EBX
    MOV     EDX,2
    MOV     EBX,[ESP+8]     ; s
    INT     0x40
    POP     EBX
    RET

hello4.cは

void api_putstr0(char *s);
void api_end(void);

void HariMain(void)
{
  api_putstr0("hello, world\n");
  api_end();
}

そして、RETFを使う必要もないので、6バイトの書き換えをなくすようです。(その際にstart_appのアドレスを変えている)

実行してみると
f:id:No000:20200210134550p:plain

hello4.cは書籍通り動いてないですね。

C言語で文字列表示(2)(harib19e)

今回は、helo4.cがなぜ動かなかったかをやるようですね。ななめ読みした感じ、かなり面白そうな内容...

まず、書籍通りにEBXを表示してみると
f:id:No000:20200210160859p:plain

00000400になってますね。これは確保したファイルサイズを優に超えているので、表示できなようです。なんでこうなるんだろ…

ということでhello4.cのアドレスmapを確認してみると、以下のようになっています。

text size :     62(0x0003E)
data size :     14(0x0000E)
bss  size :      0(0x00000)

0x00000024 : (.text)
0x00000024 : _HariMain
0x00000038 : (.text)
0x00000038 : _api_putchar
0x00000044 : _api_putstr0
0x00000052 : _api_end
0x00000059 : (.text)
0x00000059 : _HariStartup
0x00000000 : (.data)
0x00000000 : (.data)
0x00000400 : (.data)
0x00000000 : (.bss)
0x00000000 : (.bss)
0x00000000 : (.bss)

おろ?0x00000400?.data...?
ここが肝のようですね。基本的にアプリケーションのプログラム等はテキストセクションやデータセクション等があります。このハリボテOSのアプリケーションも例外ではなく、.hrbファイルの先頭36バイトのメタデータを利用してデータセグメント等の管理をしています。よって、いままでのはりぼてアプリケーションはコード部分のみを考えて実行されてました。が、今回はこのメタデータの存在を利用して、いろいろな処理に対応させるようです。

まず、各種メタデータは書籍に解説(P460)されていますが、要約すると

0x0000->OSで確保してもらうデータセグメントの容量
0x0004->拡張子以外での確認のための"Hari"(ここらへんELFみたい…)
0x0008->データセグメントの予備領域(説明なし)
0x000c->ESPの初期値、スタックのあるアドレス
0x0010->データセグメントへ送るデータの容量
0x0014->.hrbファイルのデータ部分の開始アドレス
0x0018->メモリの仕様を利用したJMP命令の機械語
0c001c->JMP命令を考慮したアプリの実行開始アドレス
0x0020->malloc領域の開始アドレス

こんな感じのようです。
ここらへんほんと、メタデータの重要性をしみじみ感じる

でこれを考慮し、cnsole.cに修正を加えそれに対応したアセンブラhello5.nasを書くようです。

[FORMAT "WCOFF"]
[INSTRSET "i486p"]
[BITS 32]
[FILE "hello5.nas"]

    GLOBAL _HariMain

[SECTION .text]

_HariMain:
    MOV   EDX,2
    MOV   EBX,msg
    INT   0x40
    MOV   EDX,4
    INT   0x40

[SECTION .data]

msg:
    DB  "hello,world", 0x0a, 0

セクションって概念だ!これってよくアセンブラで出ていたので気になっていたんですよね。すっきりポイント!

これで実行すると

ウィンドウを出そう(harib19f)

ウィンドウのAPIを作ってアプリケーションとして実行させるようです。

winhelo.cのマップファイルは

text size :    112(0x00070)
data size :   7516(0x01D5C)
bss  size :      0(0x00000)

0x00000024 : (.text)
0x00000024 : _HariMain
0x00000048 : (.text)
0x00000048 : _api_putchar
0x00000054 : _api_putstr0
0x00000062 : _api_end
0x00000069 : _api_openwin
0x0000008B : (.text)
0x0000008B : _HariStartup
0x00000000 : (.data)
0x00000000 : (.data)
0x00000400 : (.data)
0x00000410 : _buf
0x00000000 : (.bss)
0x00000000 : (.bss)
0x00000000 : (.bss)

バッファ…

実行すると

ウィンドウに文字や四角を描こう(harib19g)

ウィンドウもできたの文字の中に四角や文字を表示させていこうって感じみたい~

APIに関しては以下のようなコードです

_api_putstrwin: ; void api_putstrwin(int win, int x, int y, int col, int len, char *str);
    PUSH    EDI
    PUSH    ESI
    PUSH    EBP
    PUSH    EBX
    MOV     EDX,6
    MOV     EBX,[ESP+20]    ; win
    MOV     ESI,[ESP+24]    ; x
    MOV     EDI,[ESP+28]    ; y
    MOV     EAX,[ESP+32]    ; col
    MOV     ECX,[ESP+36]    ; len
    MOV     EBP,[ESP+40]    ; str
    INT     0x40
    POP     EBX
    POP     EBP
    POP     ESI
    POP     EDI
    RET

_api_boxfilwin: ; void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);
    PUSH    EDI
    PUSH    ESI
    PUSH    EBP
    PUSH    EBX
    MOV     EDX,7
    MOV     EBX,[ESP+20] ; win
    MOV     EAX,[ESP+24] ; x0
    MOV     ECX,[ESP+28] ; y0
    MOV     ESI,[ESP+32] ; x1
    MOV     EDI,[ESP+36] ; y1
    MOV     EBP,[ESP+40] ; col
    INT     0x40
    POP     EBX
    POP     EBP
    POP     ESI
    POP     EDI
    RET

書籍の該当するページはP467で

実行すると

やっぱりスタックをうまく使って渡していますね。

メモ

・スタック例外といっても、見てくれるのはデータセグメントの範囲からはみ出してないかだけ
Qemuのバグには注意
・アプリの強制終了は、タスク管理のメタデータをいじって終了させていた。
・Hariのマークを先頭に入れなかったのは、テキストファイルとの間違いをなくすため。

最後に

今回は、例外処理でCPUのありがたさと例外処理のありがたさを痛感する日でした。明日からは、グラフィックなので楽しみですね。目に見えて変化がわかるので…