ごちゃごちゃしたIT勉強記録

自分用メモ。セキュリティ関連の内容を書いてます。

動的バイナリ計装(Dynamic Binary Instrumentation)を調べたりしてみた

CODE BLUEのトレーニングにあったDBIというものがちょっと面白そうだったので、調べたりしてみました。
レーニング自体参加したかったのですが、仕事の関係で参加できず。
codeblue.jp

ただ、調べたり論文みたりしてみたらかなり便利そう(特にマルウェア解析するときには効率良くなりそう)と思ったので、わからないことは多いけど少しまとめてみます。

動的バイナリ計装(DBI : Dynamic Binary Instrumentation)とは

論文とかをみてみると、下のようにまとめられるかな、と。

  • プログラムの動的解析の手法
  • 実行中のプログラムにコードを挿入(コードは解析者があらかじめ準備)
  • プログラム内の情報(APIの関数名、設定された引数など)を明らかにできる

日本語で書かれた記事については下の参考URLをみてほしいのですが、英語の記事でまとまってそうなのはこちら。
Dynamic Binary Instrumentation Primer

やってみる!

今回は、上の中でもDynamoRIOを使ってみたいと思います。とりあえずの取っ掛かりとしてはつかいやすそうだったのが理由です。
ダウンロードしたzipを展開して、コマンドを実行すれば色々情報が取れそうな感じです。
今回試してみる環境はこちら

環境

  • ホストOS : Windows7 64bit
  • 仮想環境 : VMware Workstation 12 Pro
  • ゲストOS : Windows10 64bit
  • DynamoRIO version : 7.0.0-RC1(DynamoRIO-Windows-7.0.0-RC1.zipをダウンロード)

ゲスト環境にDynamoRIOを展開して、色々やってみます。

System Call Tracer

Windowsにおけるシステムコールをトレースすることができます。呼び出されたシステムコール、設定された引数がログとしてファイルに出力されます。
以下のコマンドで電卓(calc.exe)をトレースすると、以下のような結果が得られます。

drrun.exe -t drstrace -- calc
NtQueryVirtualMemory
	arg 0: 0xffffffff (type=HANDLE, size=0x4)
	arg 1: 0x008c2980 (type=void *, size=0x4)
	arg 2: 0x2 (type=int, size=0x4)
	arg 3: 0x02d1fca4 (type=<struct>*, size=0x4)
	arg 4: 0x212 (type=unsigned int, size=0x4)
	arg 5: 0x00000000 (type=unsigned int*, size=0x4)
    succeeded =>
	arg 3: <NYI> (type=<struct>*, size=0x4)
	arg 5: 0x00000000 (type=unsigned int*, size=0x4)
	retval: 0x0 (type=NTSTATUS, size=0x4)
NtQueryInformationProcess
	arg 0: 0xffffffff (type=HANDLE, size=0x4)
	arg 1: 0x24 (type=int, size=0x4)
	arg 2: 0x02d1fc78 (type=<struct>*, size=0x4)
	arg 3: 0x4 (type=unsigned int, size=0x4)
	arg 4: 0x00000000 (type=unsigned int*, size=0x4)
    succeeded =>
	arg 2: <NYI> (type=<struct>*, size=0x4)
	arg 4: 0x00000000 (type=unsigned int*, size=0x4)
	retval: 0x0 (type=NTSTATUS, size=0x4)
.......

Library Tracing Tool

呼び出されたライブラリコールをトレースして表示してくれます。
以下のコマンドでメモ帳(notepad.exe)をトレースすると、以下のような結果が得られます

drrun -t drltrace -only_from_app -- notepad
~~~~ KERNEL32.dll!GetStartupInfoA(0x007dfbfc, 0x595d088d)
~~~~ KERNELBASE.dll!GetModuleHandleA(0x00000000, 0x007dfbdc)
~~~~ msvcrt.dll!__set_app_type(0x00000002, 0x00000002)
~~~~ msvcrt.dll!__p__fmode(0x00e2a63a, 0x007e0000)
~~~~ msvcrt.dll!__p__commode(0x00e2a63a, 0x007e0000)
~~~~ msvcrt.dll!_controlfp(0x00010000, 0x00030000)
~~~~ KERNELBASE.dll!SetUnhandledExceptionFilter(0x00e2a880, 0x00e2a63a)
~~~~ msvcrt.dll!_initterm(0x00e11328, 0x00e11340)
~~~~ msvcrt.dll!__getmainargs(0x00e2c7e8, 0x00e2c7ec)
~~~~ msvcrt.dll!_onexit(0x00e2af40, 0x595d0f49)
~~~~ msvcrt.dll!_onexit(0x00e2af60, 0x595d0f49)
~~~~ msvcrt.dll!_ismbblead(0x0000006e, 0x595d088d)

かっこ内で表示されているのは、ライブラリコールの際に設定された引数の一部っぽい?

まとめ

ぶっちゃけまだ勝手がよくわからんと言う感じ。
ただ、「どんなライブラリコールがされたのか」「どういった順番でよばれたか」がわかる感じなので、ちゃんとツールの特徴とかを把握できればマルウェア解析とが少し楽になりそうと言う感想。
他にも色々ツールがあるので試してみたいのと、ちゃんと「DBIってなに」というのを勉強しようと思う。
特に、セキュリティ系の論文は結構読んでて面白いなぁと思うことがよくあるので、これからも色々読んで試せるものがあれば試してみたいと思います。

【参考URL】
PinからPEMUへ | 一生あとで読んでろ
Intel Pinを使ってみる - ももいろテクノロジー
PEMUを動かしてみる - 拾い物のコンパス
Windows 10でDynamoRIOを使ってみた - SENTO NO OBOEGAKI

【あとで追加予定】radare2メモ書き

CTF界隈では非常に使われているradare2ですが、コマンドとかよくわからないし、どう処理を進めていけばいいのかが全然わからんちんなので、まとめておく。

radare2とは

Reverse Engineering Framework。バイナリの解析とかに便利なツール、コマンドが色々入っている。
いろんなアーキテクチャ、ファイルフォーマットに対応している。フリーでさまざまなアーキテクチャ・ファイルフォーマットに対応している点は非常に強いと思う(ただし、x86には対応しているがx86-64には対応していない様子)。詳しい情報は以下を参照。
github.com

解析で基本的にやりたいこと

コードの逆アセンブル

アセンブルで個人的にやりたいのは以下のような作業。

  • 特定のアドレスから何行分かのアセンブリコードを表示するなどの操作
  • import, exportの一覧、しょっぱなから見れるstring情報を表示
  • まぁ、ざっくりアセンブリコードを眺めるとかそんな感じ

デバッグ

デバッグでやりたい基本的なことは以下のようなこと。

  • セキュリティの機構の有無(ASLRなど)
  • メモリマップの表示
  • ブレークポイントの設置
  • ステップ実行(ステップイン、ステップアウト)
  • レジスタの状態
  • 特定のメモリアドレスに格納されている値(hex, strings)

一番最初のコマンド

アセンブルするだけの場合とデバッグをする場合ではすこし違う。
ただ、dスイッチをつけるだけなのだが。

【逆アセンブル】最初のコマンド

r2 <elf binary file>

デバッグ】最初のコマンド

r2 -d <elf binary file>

dスイッチをつけることで、デバッグモードで起動することが可能。起動すると以下のような表示があった後、プロンプトが表示される。

$ r2 -d hello
Process with PID 9276 started...
= attach 9276 9276
bin.baddr 0x00400000
Using 0x400000
asm.bits 64
 -- Press 'C' in visual mode to toggle colors
[0x7f532faecc30]> 

ここで表示される画面がradare shellというものらしい。
まぁ、radareにおけるホーム画面みたいなもんとして今の所は理解しておけばいいかと。
で、最初のコマンドが違うだけで、後の操作は基本的に同じかと思われる。

radare shell起動後の操作

セキュリティ機構の有無

iI

実行するとこんな感じ。

[0x7f532faecc30]> iI
arch     x86
baddr    0x400000
binsz    6666
bintype  elf
bits     64
canary   false
class    ELF64
crypto   false
endian   little
havecode true
intrp    /lib64/ld-linux-x86-64.so.2
lang     c
linenum  true
lsyms    true
machine  AMD x86-64 architecture
maxopsz  16
minopsz  1
nx       true
os       linux
pcalign  0
pic      false
relocs   true
relro    partial
rpath    NONE
static   false
stripped false
subsys   linux
va       true

バイナリに関連する情報と、セキュリティ機構の有無に関する情報が出てくる。
checksec.shとかと似たようなもん。

初期段階の解析

aaa

上記のコマンド(analyzeコマンド)を行うと以下のような表示が出る。

[0x7f532faecc30]> aaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[TOFIX: afta can't run in debugger mode.ions (afta)
[x] Type matching analysis for all functions (afta)
[x] Use -AA or aaaa to perform additional experimental analysis.
= attach 9276 9276
9276

複数の解析をいっぺんにやっているという感じ。
複数の解析というのは、各行のカッコ内に記述されているコマンド。
なので、aa, aac, aar, aan, aftaをaaaというコマンドでいっぺんにやっているという感じでしょうか。
各コマンドの意味は、上の出力から判断できるかと。

関数名の調査

afl

バイナリに含まれているシンボルの一覧を出す(analyze function list?)ようなものでしょうか。
表示されるものは以下のようなもの

[0x7f532faecc30]> afl
0x00400400    3 26           sym._init
0x00400430    1 6            sym.imp.puts
0x00400440    1 6            sym.imp.__libc_start_main
0x00400450    1 6            sym.imp.exit
0x00400460    1 6            sub.__gmon_start_460
0x00400470    1 41           entry0
0x004004a0    4 50   -> 41   sym.deregister_tm_clones
0x004004e0    4 58   -> 55   sym.register_tm_clones
0x00400520    3 28           sym.__do_global_dtors_aux
0x00400540    4 38   -> 35   entry1.init
0x00400566    1 24           sym.main
0x00400580    4 101          sym.__libc_csu_init
0x004005f0    1 2            sym.__libc_csu_fini
0x004005f4    1 9            sym._fini

特定の関数またはアドレスへの移動

s <symbol or address>

sはseekコマンド。特定のシンボルがさす関数の先頭または任意のアドレスへの移動を行う。
例えば、aflで

[0x7f532faecc30]> afl
0x00400400    3 26           sym._init
0x00400430    1 6            sym.imp.puts
0x00400440    1 6            sym.imp.__libc_start_main
0x00400450    1 6            sym.imp.exit
0x00400460    1 6            sub.__gmon_start_460
0x00400470    1 41           entry0
0x004004a0    4 50   -> 41   sym.deregister_tm_clones
0x004004e0    4 58   -> 55   sym.register_tm_clones
0x00400520    3 28           sym.__do_global_dtors_aux
0x00400540    4 38   -> 35   entry1.init
0x00400566    1 24           sym.main
0x00400580    4 101          sym.__libc_csu_init
0x004005f0    1 2            sym.__libc_csu_fini
0x004005f4    1 9            sym._fini

sym.mainに移動したいのであれば、

s sym.main

とする。そうすると、プロンプトのアドレスがseekした後のアドレスに変化する。

[0x7f532faecc30]> s sym.main
[0x00400566]>

ビジュアルモードへの移行

Vコマンドをベースとして、次にくる文字によって表示方法を変更する。
表示方法としてはおもにfncgraphとpanelがある。

その1:fncgraph(function graph?)

[0040055a]> VV

これを実行すると、現在のアドレスを開始地点としてIDAのGraph Viewっぽい画面が出る。

[0x00400566]> VV @ sym.main (nodes 1 edges 0 zoom 100%) BB-NORM mouse:canvas-y mov-speed:5

                 .---------------------------------------------------.                                                                                       
                 | [0x400566]                                        |                                                                                       
                 | ;-- main:                                         |                                                                                       
                 | (fcn) sym.main 24                                 |                                                                                       
                 |   sym.main (int argc, char **argv, char **envp);  |                                                                                       
                 | ; DATA XREF from entry0 (0x40048d)                |                                                                                       
                 | push rbp                                          |                                                                                       
                 | mov rbp, rsp                                      |                                                                                       
                 | ; 0x400604                                        |                                                                                       
                 | ; "Hello world!"                                  |                                                                                       
                 | mov edi, str.Hello_world                          |                                                                                       
                 | call sym.imp.puts;[ga]                            |                                                                                       
                 | mov edi, 0                                        |                                                                                       
                 | call sym.imp.exit;[gb]                            |                                                                                       
                 `---------------------------------------------------'  

画面の移動はvimと同じようにhjklキーを使って行う。
で、この画面上でできることは

入力キー 操作
shift+/キー 使用可能なコマンド一覧を表示(ヘルプ機能)
xキー 今いるサブルーチンの呼び出し元(xref)のアセンブリコードを表示する
スペース hexダンプの表示
shift+dキー アセンブリコードとアドレスを同時に表示するモード

あとfncgraphの画面で
xキー -> vキー
という順番でやると、左側に関数のシンボル名、右側に左側で選択した関数のアドレスから何行分かのアセンブリコードが表示される画面が出てくる。
f:id:motojiroxx:20180912154800p:plain


その2:Panel表示

[0040055a]> V!

上記のコマンドを入力すると、以下のような画面が表示される。
f:id:motojiroxx:20180912150339p:plain
それぞれのパネルへ、tabキーを使うことで移動することが可能。
また、上にあるCUI版メニューバーへもtabキーで移動可能。
メニューバーには結構色々な機能があって、非常に使える。
(ただ、string検索はなぜか引っかかるはずの文字列が引っかからなかったのだが....)

いつものデバッグ操作

基本的なデバッグ操作はだいたい

  1. ブレークポイントの設置
  2. continue操作(現在位置から直近のブレークポイントまでpcを進める)
  3. ブレークポイントからステップ実行

という感じが多い。なので、それをradareでやる場合のコマンドなどを下にまとめる。基本的にはradare shell上でdコマンド(debug command)を使っていくことになる。

ブレークポイントの設置

radare shellでdbコマンド(debug breakpoint)を使う。

db <address>

これでブレークポイントを仕掛けることができる。

直近のブレークポイントまで移動(continue)

dc

このコマンドを実行すると、radare shellのプロンプトが表示するアドレスが設定したブレークポイントのところまでいっているはずである。

ステップ実行

ds

デバッグ操作の基本はだいたいこんな感じかと。


ここまでで把握できたもの

大まかなコマンド体系としては、いくつかの1文字で表現されるメインコマンドがあり、1文字で表現されるサブコマンド・オプションを合体させて1つの実行コマンドとしている。(メインコマンドの一覧は、radare shell上で'?'コマンドを実行するとみることが可能)
メインコマンドのうち主要なものを以下に列挙。

a : analyze command
aの次にくる文字で「何を解析(analyze)の対象とするか」を指定する
(例として、関数名を調べたい場合には' af 'とする)
で、その次にくる文字で「解析した結果をどのように処理したいか」というオプションがくる。
(例としては、リストとして画面出力したい場合であれば' afl 'とする)

p : print command
pの次にくる文字で「どういった形式で出力(print)を行うか」を指定する
(例として、逆アセンブルしたコードを出力する場合には' pd 'とする)
で、その次にくる文字で「何を対象とするか:を指定する
(例として、関数を出力する場合には' pdf@function_name 'とする)

i : information command
iの次にくる文字で「どういった情報を出力するか」を指定する
(例として、importされた関数の情報が欲しい場合には' ii 'とする)

V : Visual mode command
vの次にくる文字で「どういったモードで表示を行うか」を指定する
(例として、上記のようにpanelで表示する場合には' V! 'とする)

d : debug command
dの次にくる文字で「デバッグモードでどのような操作を行うか」を指定

gdbでarmバイナリをデバッグできるようにする

普段はMacVMware Fusionをいれて、仮想マシンLinux環境を動かしているわけですが、CTFの問題を解いているとELFだったとしてもアーキテクチャがARMのものにぶち当たったりします。

GDBでARMのELFをデバッグするためのパッケージとして、gdb-multiarchというものがあります。
これを入れることで、普段だとx86-64x86アーキテクチャのELFしか解析できない(はず)ですが、ARMのアーキテクチャもちゃんと解析できるようになります。

環境

$ uname -a
Linux forensic-virtual-machine 4.13.0-36-generic #40~16.04.1-Ubuntu SMP Fri Feb 16 23:25:58 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="16.04.4 LTS (Xenial Xerus)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 16.04.4 LTS"
VERSION_ID="16.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
VERSION_CODENAME=xenial
UBUNTU_CODENAME=xenial

解析対象

とあるCTFの問題で出てきたバイナリを対象とします(手元にちょうどあったので)

$ file bin_arm 
bin_arm: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.3, for GNU/Linux 3.2.0, not stripped

gdb-multiarchが入っていない場合

まずは、gdb-multiarchが入っていない状態でバイナリを解析してみます。
いつも通りデバッグ対象に上記のファイルを指定します。

gdb bin_arm

試しにmain関数を逆アセンブルした結果を見てみようと思います。

gdb-peda$ disas main
Dump of assembler code for function main:
   0x0001055c <+0>:	add    BYTE PTR [eax+0x2d],cl
   0x0001055f <+3>:	jmp    0xe28eb568
   0x00010564 <+8>:	or     al,dl
   0x00010566 <+10>:	dec    ebp
   0x00010567 <+11>:	loop   0x10571 <main+21>
   0x00010569 <+13>:	add    BYTE PTR [ebx],cl
   0x0001056b <+15>:	in     eax,0xc
   0x0001056d <+17>:	adc    BYTE PTR [ebx],cl
   0x0001056f <+19>:	in     eax,0x8
   0x00010571 <+21>:	xor    BYTE PTR [ebx],bl
   0x00010573 <+23>:	in     eax,0x1
   0x00010575 <+25>:	add    BYTE PTR [ebx-0x1d],dl
   0x00010578 <+28>:	or     BYTE PTR [eax],al
   0x0001057a <+30>:	add    dl,cl
   0x0001057c <+32>:	pusha  
   0x0001057d <+33>:	xor    BYTE PTR [edi-0x6cffff1b],bl
   0x00010583 <+39>:	in     eax,0xc
   0x00010585 <+41>:	xor    BYTE PTR [ebx],bl
   0x00010587 <+43>:	in     eax,0x0
   0x00010589 <+45>:	xor    BYTE PTR [ebx-0x5fdffc1b],dl
   0x0001058f <+51>:	loope  0x105e1 <main+133>
   0x00010591 <+53>:	adc    BYTE PTR [edi-0x891b],bl
   0x00010597 <+59>:	jmp    0x10599 <main+61>
   0x00010599 <+61>:	xor    al,ah
   0x0001059b <+63>:	jecxz  0x105aa <main+78>
   0x0001059d <+65>:	add    BYTE PTR [eax],al
   0x0001059f <+67>:	jmp    0x3004:0xe51b300c
   0x000105a6 <+74>:	and    edx,0x0
   0x000105a9 <+77>:	xor    BYTE PTR [ebx-0x5ffffc1b],dl
   0x000105af <+83>:	loope  0x10573 <main+23>
   0x000105b1 <+85>:	(bad)  
   0x000105b2 <+86>:	(bad)  
   0x000105b3 <+87>:	jmp    0x105b5 <main+89>
   0x000105b5 <+89>:	xor    BYTE PTR [eax+0x530000e1],ah
   0x000105bb <+95>:	jecxz  0x105bf <main+99>
   0x000105bd <+97>:	add    BYTE PTR [eax],al
   0x000105bf <+99>:	sbb    ah,BYTE PTR [eax+eax*1]
   0x000105c2 <+102>:	lahf   
   0x000105c3 <+103>:	in     eax,0x61
   0x000105c5 <+105>:	(bad)  
   0x000105c6 <+106>:	(bad)  
   0x000105c7 <+107>:	jmp    0x105ca <main+110>

ところどころ(bad)となっており、マシンコードを適切にアセンブリコードに変換できていないことが分かるかと思います。
あと、みるからにアセンブリコードが変(いつも見ている感じとはなんか違和感がある)です。

ということで、なにも手を加えずgdbでarmバイナリに突撃すると爆死します...

gdb-multiarchを入れてやってみる

では早速入れてやってやってみましょう。いつも通りaptで入れます。

sudo apt install gdb-multiarch

準備はこれで終了です。では早速起動。

$ gdb-multiarch bin_arm 
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from bin_arm...(no debugging symbols found)...done.
gdb-peda$ 

普通にgdbと同じように起動してしまったので、「これこのまんまでいけるんか?」と不安になりますがそのまま行きます。
また、見て分かる通り、gdb-pedaを入れている方であればgdb-multiarchを起動しても同じようにいけます(おそらく同じ.gdbinitを見にいっているかと)

というわけで、逆アセンブルしたmain関数のアセンブリコードを見てみます。

gdb-peda$ disas main
Dump of assembler code for function main:
   0x0001055c <+0>:	push	{r11, lr}
   0x00010560 <+4>:	add	r11, sp, #4
   0x00010564 <+8>:	sub	sp, sp, #8
   0x00010568 <+12>:	str	r0, [r11, #-8]
   0x0001056c <+16>:	str	r1, [r11, #-12]
   0x00010570 <+20>:	ldr	r3, [r11, #-8]
   0x00010574 <+24>:	cmp	r3, #1
   0x00010578 <+28>:	bgt	0x105a0 <main+68>
   0x0001057c <+32>:	ldr	r3, [pc, #96]	; 0x105e4 <main+136>
   0x00010580 <+36>:	ldr	r0, [r3]
   0x00010584 <+40>:	ldr	r3, [r11, #-12]
   0x00010588 <+44>:	ldr	r3, [r3]
   0x0001058c <+48>:	mov	r2, r3
   0x00010590 <+52>:	ldr	r1, [pc, #80]	; 0x105e8 <main+140>
   0x00010594 <+56>:	bl	0x10374 <fprintf@plt>
   0x00010598 <+60>:	mvn	r3, #0
   0x0001059c <+64>:	b	0x105d8 <main+124>
   0x000105a0 <+68>:	ldr	r3, [r11, #-12]
   0x000105a4 <+72>:	add	r3, r3, #4
   0x000105a8 <+76>:	ldr	r3, [r3]
   0x000105ac <+80>:	mov	r0, r3
   0x000105b0 <+84>:	bl	0x104c0 <check>
   0x000105b4 <+88>:	mov	r3, r0
   0x000105b8 <+92>:	cmp	r3, #0
   0x000105bc <+96>:	bne	0x105cc <main+112>
   0x000105c0 <+100>:	ldr	r0, [pc, #36]	; 0x105ec <main+144>
   0x000105c4 <+104>:	bl	0x10350 <puts@plt>
   0x000105c8 <+108>:	b	0x105d4 <main+120>
   0x000105cc <+112>:	ldr	r0, [pc, #28]	; 0x105f0 <main+148>
   0x000105d0 <+116>:	bl	0x10350 <puts@plt>
   0x000105d4 <+120>:	mov	r3, #0
   0x000105d8 <+124>:	mov	r0, r3
   0x000105dc <+128>:	sub	sp, r11, #4
   0x000105e0 <+132>:	pop	{r11, pc}
   0x000105e4 <+136>:	andeq	r1, r2, r0, asr r0
   0x000105e8 <+140>:	andeq	r0, r1, r4, ror #12
   0x000105ec <+144>:	andeq	r0, r1, r8, ror r6
   0x000105f0 <+148>:	andeq	r0, r1, r4, lsl #13
End of assembler dump.

ちゃんと表示されているっぽい!
ぶっちゃけARMのアセンブリの解析はほとんどやっていないので、どの命令がどの処理をするのかわかってません。
(噂だとオペランドを3つとるとかなんとか....)

なので、ひとまずはこれを使って勉強していこうと思います。

linux x64での関数呼び出し

今回、久しぶりにCTFに参加しました。1問も解けず(泣
ただ、単なる勘違いで逃したようなものなので、その勘違いを供養するためにメモを書きます。
x64の関数呼び出しをx86と同じものだと勘違いしていて、いきなりrdiとかでてきて「ナンジャコリャ」となりました。
なので、そこらへんのちょっとしたメモをば。

呼び出し規約(Call Convention)

サブルーチンが呼び出される際に従わなければいけないルールのこと。ABI(Application Binary Interface)*1の一部。
今回は、サブルーチン(関数)が呼ばれる前にセットされる引数の扱いについてまとめます。

今回の環境は以下の通り

$ uname -a
Linux forensic-virtual-machine 4.13.0-36-generic 
#40~16.04.1-Ubuntu SMP Fri Feb 16 23:25:58 UTC 2018 
x86_64 x86_64 x86_64 GNU/Linux
$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="16.04.4 LTS (Xenial Xerus)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 16.04.4 LTS"
VERSION_ID="16.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
VERSION_CODENAME=xenial
UBUNTU_CODENAME=xenial


この環境において、例として以下のようなプログラムを考えます。

// sample.c

#include<stdio.h>

int add(int a, int b, int c);

int main(){
        int x,y,z;
        int sum;
        x = 2;
        y = 3;
        z = 5;
        printf("x = %d, y = %d, z = %d\n", x, y, z);

        sum = add(x,y,z);
        printf("x + y + z = %d\n",sum);

        return 0;
}

int add(int a, int b, int c){
        return a + b + c;
}

着目するのは、定義したadd関数です。
add関数は、3つの引数を足した値を戻り値として返すものになります。

そして、このソースコードを以下のようにコンパイルし、x86バイナリとx64バイナリを生成します

# x86 binary
gcc -m32 sample.c -o sample_x86
# x64 binary
gcc sample.c -o sample_x64

では、ここからx86とx64でadd関数の引数がどのように扱われるのか見ていきます。

x86における引数の扱い

まずは、sample_x86のバイナリをデバッガで見た場合。
add関数の引数は以下のように設定されます。

   0x0804844a <+63>:	push   DWORD PTR [ebp-0x10]         ; 5
   0x0804844d <+66>:	push   DWORD PTR [ebp-0x14]         ; 3
   0x08048450 <+69>:	push   DWORD PTR [ebp-0x18]         ; 2
   0x08048453 <+72>:	call   0x804847e <add>

add関数の引数は3つともスタックにpushされ、その後にadd関数がコールされている形になります。
つまり、x86の場合、関数の引数はスタックに積まれることになります。
(実際、サブルーチンに入った後はebpのオフセット指定で引数の値を取り出して演算を行います)

x64における引数の扱い

では、ここからが本題。x64においてはどうなるのか。
まずは、sample_x64バイナリをデバッガで見た場合を確認。
add関数の引数が以下のように設定されます

   0x000000000040055d <+55>:	mov    edx,DWORD PTR [rbp-0x8]       ; 5
   0x0000000000400560 <+58>:	mov    ecx,DWORD PTR [rbp-0xc]        ; 3
   0x0000000000400563 <+61>:	mov    eax,DWORD PTR [rbp-0x10]      ; 2
   0x0000000000400566 <+64>:	mov    esi,ecx
   0x0000000000400568 <+66>:	mov    edi,eax
   0x000000000040056a <+68>:	call   0x40058d <add>

add関数の引数はレジスタのedi, esi, edxに格納された後、add関数のコールがかかっている状態です。
つまり、x64の場合、関数の引数がレジスタに格納されるという形になります。

また、どの引数がどのレジスタに格納されるかについては、linux64-abiの資料*2を確認してみました。

引数 第1引数 第2引数 第3引数 第4引数 第5引数 第6引数
レジスタ rdi rsi rdx rcx r8 r9

第6引数まではレジスタを使用し、第7引数以上はスタックが使用されるということらしい。

なので、試しにadd関数の引数を8個に引き伸ばした関数を作り、どうなるか試して見ます。
ソースはこちら。

// ext_sample.c

#include<stdio.h>

int add(int a, int b, int c, int d, int e, int f, int g, int h);

int main(){
        int s,t,u,v,w,x,y,z;
        int sum;
        s = 1;
        t = 2;
        u = 3;
        v = 4;
        w = 5;
        x = 6;
        y = 7;
        z = 8;
        sum = add(s,t,u,v,w,x,y,z);
        printf("%d\n",sum);

        return 0;
}

int add(int a, int b, int c, int d, int e, int f, int g, int h){
        return a + b + c + d + e + f + g + h;
}

このソースでは、add関数で8つの引数を指定します。なので、後ろの2つの引数についてはスタックを使っているはずですので、その部分にも注目していきたいと思います。
では、コンパイルした後のx64バイナリをデバッガにかけて、add関数の引数設定を見ていきます。

   0x000000000040052e <+8>:	mov    DWORD PTR [rbp-0x24],0x1     
   0x0000000000400535 <+15>:	mov    DWORD PTR [rbp-0x20],0x2
   0x000000000040053c <+22>:	mov    DWORD PTR [rbp-0x1c],0x3
   0x0000000000400543 <+29>:	mov    DWORD PTR [rbp-0x18],0x4
   0x000000000040054a <+36>:	mov    DWORD PTR [rbp-0x14],0x5
   0x0000000000400551 <+43>:	mov    DWORD PTR [rbp-0x10],0x6
   0x0000000000400558 <+50>:	mov    DWORD PTR [rbp-0xc],0x7
   0x000000000040055f <+57>:	mov    DWORD PTR [rbp-0x8],0x8

   0x0000000000400566 <+64>:	mov    r9d,DWORD PTR [rbp-0x10]
   0x000000000040056a <+68>:	mov    r8d,DWORD PTR [rbp-0x14]
   0x000000000040056e <+72>:	mov    ecx,DWORD PTR [rbp-0x18]
   0x0000000000400571 <+75>:	mov    edx,DWORD PTR [rbp-0x1c]
   0x0000000000400574 <+78>:	mov    esi,DWORD PTR [rbp-0x20]
   0x0000000000400577 <+81>:	mov    eax,DWORD PTR [rbp-0x24]

   0x000000000040057a <+84>:	mov    edi,DWORD PTR [rbp-0x8]      ; 8th argument
   0x000000000040057d <+87>:	push   rdi
   0x000000000040057e <+88>:	mov    edi,DWORD PTR [rbp-0xc]      ; 7th argument
   0x0000000000400581 <+91>:	push   rdi

   0x0000000000400582 <+92>:	mov    edi,eax                                       ; 1st argument
   0x0000000000400584 <+94>:	call   0x4005ab <add>

意図的に改行を入れて少し見やすくしています。

上の段では、1から8までの値をメモリに格納している部分になります。

次の段では、引数として1から8の値を設定するため、レジスタへそれぞれ値を格納しています。
ただし、<+81>の部分では、第1引数の値をeaxに入れています。通常であれば第1引数の値はrdiに入れるはずです。
なぜそんなことをするかというと、その下でrdi(edi)を使っているからです。


その下の段にいくと、値をediにいれてスタックにpushしています。この部分が第7引数と第8引数の設定部分になります。
引数をスタックにpushする際は、rdiレジスタを使っています。スタックに値を入れる順番は後ろに引数からです(ここはx86と変わらない)。
ここでrdiを使ってしまっているので、その上の段で第1引数をrdiに入れるとまずいですね。
(だけど、なんでわざわざrdiレジスタを使うんだろう?他のレジスタじゃだめなのだろうか....)

で、第7引数と第8引数を入れ終わった後はrdiが使えるので、eaxに入っている第1引数の値をrdiに入れ、add関数をコールするという流れになります。

今後の課題

今まで32bitのプログラムの解析ばっかりしていたので、x64の知識が全くついていない。
今回みたいな勘違いが起きないように、x64についてもちゃんと勉強しておこうと思う。

というわけで、以下の本を買ったので読み進めていこう(時間ばあればまとめていきたい)。
www.shoeisha.co.jp

*1:CPUの命令セットや呼出規約といった、ユーザーのプログラムとOS・ライブラリ間のバイナリレベルのインターフェース

*2:https://software.intel.com/sites/default/files/article/402129/mpx-linux64-abi.pdf

30日OS自作入門-5日目(GDTとIDTの前まで)-

だいたい投稿の間隔は1週間という感じでしょうか。
もしかしたらもうちょっとかかるかもしれませんけれども。
ということで5日目のラスト以外の内容について。

今回のまとめ

主には、4日目に表示させた画面上に文字だったり、マウスカーソルを表示させていきます。
どん詰まりすると思われるのはやはりフォントを増やす部分(harib02e)と変数の値を画面に表示させる(harib02g)部分かと思います。そこについては参考となるサイトをガン見してやっていけば問題ないかと思います。

harib02a

nasmhead.nasで記録されている画面モードを取得して、それを使って処理をおこなっていきます。
まずは、bootpack.cを変更していきます。
上記の処理と、4日目の最後harib01hで表示したboxfill8が大量に並んでいるところを関数としてまとめて定義(init_screen関数)します。

/* proto type declaration */ 
extern void io_hlt(void); 
extern void io_cli(void);
extern void io_out8(int port, int data);
extern int io_load_eflags(void);
extern void io_store_eflags(int eflags);

void init_palette(void);
void set_palette(int start, int end, unsigned char *rgb);
void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1);
void init_screen(char *vram, int x, int y);

#define COL8_000000	0
#define COL8_FF0000	1
#define COL8_00FF00	2
#define COL8_FFFF00	3
#define COL8_0000FF	4
#define COL8_FF00FF	5
#define COL8_00FFFF	6
#define COL8_FFFFFF	7
#define COL8_C6C6C6	8
#define COL8_840000	9
#define COL8_008400	10
#define COL8_848400	11
#define COL8_000084	12
#define COL8_840084	13
#define COL8_008484	14
#define COL8_848484	15

void HariMain(void)
{
	char *vram;
	int xsize, ysize;	
	short *binfo_scrnx, *binfo_scrny;
	int *binfo_vram;

	init_palette();		/* setting palette */
	binfo_scrnx = (short *) 0x0ff4;
	binfo_scrny = (short *) 0x0ff6;
	binfo_vram = (int *) 0x0ff8;
	xsize = *binfo_scrnx;
	ysize = *binfo_scrny;
	vram = (char *) *binfo_vram;

	init_screen(vram, xsize, ysize);

	for(;;){
		io_hlt();
	}
}

void init_palette(void)
{
	static unsigned char table_rgb[16*3] = {
		0x00, 0x00, 0x00,	/* 0:black */
		0xff, 0x00, 0x00,	/* 1:light red */
		0x00, 0xff, 0x00,	/* 2:light green */
		0xff, 0xff, 0x00,	/* 3:light yellow */
		0x00, 0x00, 0xff,	/* 4:light blue */
		0xff, 0x00, 0xff,	/* 5:light purple */
		0x00, 0xff, 0xff,	/* 6:light water blue */
		0xff, 0xff, 0xff,	/* 7:white */
		0xc6, 0xc6, 0xc6,	/* 8:light gray */
		0x84, 0x00, 0x00,	/* 9:dark red */
		0x00, 0x84, 0x00,	/*10:dark green */
		0x84, 0x84, 0x00,	/*11:dark yellow */
		0x00, 0x00, 0x84,	/*12:dark blue */
		0x84, 0x00, 0x84,	/*13:dark purple */
		0x00, 0x84, 0x84,	/*14:dark water blue */
		0x84, 0x84, 0x84	/*15:dark gray */
	};
	set_palette(0, 15, table_rgb);
	return;

	/* static char operation is equals to DB operation */
}

void set_palette(int start, int end, unsigned char *rgb)
{
	int i, eflags;
	eflags = io_load_eflags();		/* record the allowing interrupt flags value */
	io_cli();				/* set allowing flag 0 for prohibitting interrupt */
	io_out8(0x03c8, start);

	for(i = start; i <= end; i++){
		io_out8(0x03c9, rgb[0]/4);
		io_out8(0x03c9, rgb[1]/4);
		io_out8(0x03c9, rgb[2]/4);
		rgb += 3;			/* gain rgb pointer index 3 */
	}

	io_store_eflags(eflags);		/* restore interrupt allowing flags */
	return;
}

void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1)
{
	int x,y;
	for(y = y0; y <=y1; y++){
		for(x = x0; x <= x1; x++){
			vram[y * xsize + x] = c;
		}
	}
	return;
}

void init_screen(char *vram, int x, int y)
{
	boxfill8(vram, x, COL8_008484,  0,	0,	x - 1, 	y - 29);
	boxfill8(vram, x, COL8_C6C6C6,  0,	y - 28,	x - 1, 	y - 28);
	boxfill8(vram, x, COL8_FFFFFF,  0,	y - 27,	x - 1, 	y - 27);
	boxfill8(vram, x, COL8_C6C6C6,  0,	y - 26,	x - 1, 	y - 1);

	boxfill8(vram, x, COL8_FFFFFF,  3,	y - 24,	59,	y - 24);
	boxfill8(vram, x, COL8_FFFFFF,  2,	y - 24,	2, 	y - 4);
	boxfill8(vram, x, COL8_848484,  3,	y - 4,	59, 	y - 4);
	boxfill8(vram, x, COL8_848484,  59,	y - 4,	59, 	y - 5);
	boxfill8(vram, x, COL8_000000,  2,	y - 3,	59, 	y - 3);
	boxfill8(vram, x, COL8_000000,  60,	y - 24,	60, 	y - 3);

	boxfill8(vram, x, COL8_848484,  x - 47,	y - 24,	x - 4, 	y - 24);
	boxfill8(vram, x, COL8_848484,  x - 47,	y - 23,	x - 47, y - 4);
	boxfill8(vram, x, COL8_FFFFFF,  x - 47,	y - 3,	x - 4, 	y - 3);
	boxfill8(vram, x, COL8_FFFFFF,  x - 3,	y - 24,	x - 3, 	y - 3);
	
	return;
}

今回は、コードをまとめたりしただけなので、実際にmakeして実行させてもharib01hのときのものと結果は変わりません。
HariMainのところで追加で変数宣言したり、値を代入を以下のようにしています。

void HariMain(void)
{
	char *vram;
	int xsize, ysize;	
	short *binfo_scrnx, *binfo_scrny;
	int *binfo_vram;

	init_palette();		/* setting palette */
	binfo_scrnx = (short *) 0x0ff4;
	binfo_scrny = (short *) 0x0ff6;
	binfo_vram = (int *) 0x0ff8;
	xsize = *binfo_scrnx;
	ysize = *binfo_scrny;
	vram = (char *) *binfo_vram;

で、binfo_scrnxやbinfo_scrny, binfo_vramにアドレスを格納していますが、これらはnasmhead.nasで画面解像度の値が格納されているアドレスになります。
nasmhead.nasで該当する部分がこちら。

; related to BOOT_INFO
CYLS	EQU	0x0ff0		; setting boot sector
LEDS	EQU	0x0ff1
VMODE	EQU	0x0ff2		; how many bit color ?
SCRNX	EQU	0x0ff4		; X display resolution
SCRNY	EQU	0x0ff6		; Y display resolution
VRAM	EQU	0x0ff8		; start address fo graphic buffer

	ORG	0xc200		; start address this program is loaded

; display mode settings
	
	MOV	AL,0x13		; VGA graphics, 320x200x8bit color
	MOV	AH,0x00		; fixed value
	INT	0x10
	MOV	BYTE [VMODE],8	; store display mode in the following
	MOV	WORD [SCRNX],320
	MOV	WORD [SCRNY],200
	MOV	DWORD [VRAM],0x000a0000

SCRNX, SCRNY, VRAMのアドレスに値をそれぞれ320, 200, 0xa0000と入れています。この値をbinfo_ふんちゃかでは参照しています。

また、harib01hの時にHariMain関数にあった大量のboxfill8関数をまとめてinit_screen関数に置いたことでHariMainの中身がすっきりしました。

この後のharib02bとharib02cは構造体とアロー演算子の話なのですっとばします。
アロー演算子は、構造体のポインタ変数を宣言した際に、そのメンバのポインタを指定する際に使用するものです。
通常の構造体変数を宣言した場合はメンバへのアクセスはドットを使い、構造体ポインタを宣言した場合にはメンバへのアクセスはアロー演算子を使う、といった具合。

harib02d

今まで何もなかった画面上にAという文字を表示させます。
変更するのはbootpack.cになります。コードは以下の通り。

/* proto type declaration */ 
extern void io_hlt(void); 
extern void io_cli(void);
extern void io_out8(int port, int data);
extern int io_load_eflags(void);
extern void io_store_eflags(int eflags);

void init_palette(void);
void set_palette(int start, int end, unsigned char *rgb);
void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1);
void init_screen(char *vram, int x, int y);
void putfont8(char *vram, int xsize, int x, int y, char c, char *font);
 
#define COL8_000000	0
#define COL8_FF0000	1
#define COL8_00FF00	2
#define COL8_FFFF00	3
#define COL8_0000FF	4
#define COL8_FF00FF	5
#define COL8_00FFFF	6
#define COL8_FFFFFF	7
#define COL8_C6C6C6	8
#define COL8_840000	9
#define COL8_008400	10
#define COL8_848400	11
#define COL8_000084	12
#define COL8_840084	13
#define COL8_008484	14
#define COL8_848484	15

struct BOOTINFO{
	char cyls, leds, vmode, reserve;
	short scrnx, scrny;
	char *vram;
};

void HariMain(void)
{
	struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0;
	static char font_A[16] = {
		0x00, 0x18, 0x18, 0x18, 0x18, 0x24, 0x24, 0x24,
		0x24, 0x7e, 0x42, 0x42, 0x42, 0xe7, 0x00, 0x00
	};

	init_palette();		/* setting palette */
	init_screen(binfo->vram, binfo->scrnx, binfo->scrny);
	putfont8(binfo->vram, binfo->scrnx, 10, 10, COL8_FFFFFF, font_A);

	for(;;){
		io_hlt();
	}
}

void init_palette(void)
{
	static unsigned char table_rgb[16*3] = {
		0x00, 0x00, 0x00,	/* 0:black */
		0xff, 0x00, 0x00,	/* 1:light red */
		0x00, 0xff, 0x00,	/* 2:light green */
		0xff, 0xff, 0x00,	/* 3:light yellow */
		0x00, 0x00, 0xff,	/* 4:light blue */
		0xff, 0x00, 0xff,	/* 5:light purple */
		0x00, 0xff, 0xff,	/* 6:light water blue */
		0xff, 0xff, 0xff,	/* 7:white */
		0xc6, 0xc6, 0xc6,	/* 8:light gray */
		0x84, 0x00, 0x00,	/* 9:dark red */
		0x00, 0x84, 0x00,	/*10:dark green */
		0x84, 0x84, 0x00,	/*11:dark yellow */
		0x00, 0x00, 0x84,	/*12:dark blue */
		0x84, 0x00, 0x84,	/*13:dark purple */
		0x00, 0x84, 0x84,	/*14:dark water blue */
		0x84, 0x84, 0x84	/*15:dark gray */
	};
	set_palette(0, 15, table_rgb);
	return;

	/* static char operation is equals to DB operation */
}

void set_palette(int start, int end, unsigned char *rgb)
{
	int i, eflags;
	eflags = io_load_eflags();		/* record the allowing interrupt flags value */
	io_cli();				/* set allowing flag 0 for prohibitting interrupt */
	io_out8(0x03c8, start);

	for(i = start; i <= end; i++){
		io_out8(0x03c9, rgb[0]/4);
		io_out8(0x03c9, rgb[1]/4);
		io_out8(0x03c9, rgb[2]/4);
		rgb += 3;			/* gain rgb pointer index 3 */
	}

	io_store_eflags(eflags);		/* restore interrupt allowing flags */
	return;
}

void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1)
{
	int x,y;
	for(y = y0; y <=y1; y++){
		for(x = x0; x <= x1; x++){
			vram[y * xsize + x] = c;
		}
	}
	return;
}

void init_screen(char *vram, int x, int y)
{
	boxfill8(vram, x, COL8_008484,  0,	0,	x - 1, 	y - 29);
	boxfill8(vram, x, COL8_C6C6C6,  0,	y - 28,	x - 1, 	y - 28);
	boxfill8(vram, x, COL8_FFFFFF,  0,	y - 27,	x - 1, 	y - 27);
	boxfill8(vram, x, COL8_C6C6C6,  0,	y - 26,	x - 1, 	y - 1);

	boxfill8(vram, x, COL8_FFFFFF,  3,	y - 24,	59,	y - 24);
	boxfill8(vram, x, COL8_FFFFFF,  2,	y - 24,	2, 	y - 4);
	boxfill8(vram, x, COL8_848484,  3,	y - 4,	59, 	y - 4);
	boxfill8(vram, x, COL8_848484,  59,	y - 4,	59, 	y - 5);
	boxfill8(vram, x, COL8_000000,  2,	y - 3,	59, 	y - 3);
	boxfill8(vram, x, COL8_000000,  60,	y - 24,	60, 	y - 3);

	boxfill8(vram, x, COL8_848484,  x - 47,	y - 24,	x - 4, 	y - 24);
	boxfill8(vram, x, COL8_848484,  x - 47,	y - 23,	x - 47, y - 4);
	boxfill8(vram, x, COL8_FFFFFF,  x - 47,	y - 3,	x - 4, 	y - 3);
	boxfill8(vram, x, COL8_FFFFFF,  x - 3,	y - 24,	x - 3, 	y - 3);
	
	return;
}

void putfont8(char *vram, int xsize, int x, int y, char c, char *font)
{
	int i;
	char *p, d;	// d == data
	for(i = 0; i < 16; i++){
		p = vram + (y + i) * xsize + x;
		d = font[i];
		if((d & 0x80) != 0){ p[0] = c; }
		if((d & 0x40) != 0){ p[1] = c; }
		if((d & 0x20) != 0){ p[2] = c; }
		if((d & 0x10) != 0){ p[3] = c; }
		if((d & 0x08) != 0){ p[4] = c; }
		if((d & 0x04) != 0){ p[5] = c; }
		if((d & 0x02) != 0){ p[6] = c; }
		if((d & 0x01) != 0){ p[7] = c; }
	}
	return;
}

まず、構造体ポインタbinfoはアドレス0x0ff00をさしています。構造体自体は配列と同じように、メモリの連続したアドレス上にデータを格納します。配列と異なる点は、構造体の場合メンバ変数ごとに型が異なることです(メンバ変数がすべて同じ型であれば配列と同じようになると思います)。
それを踏まえると、構造体のメンバのアドレスはそれぞれ以下の通りになります。

0x0ff0 : cyls, 0x0ff1 : leds, 0x0ff2 : vmode, 0x0ff3 : reserve, 
0x0ff4 : scrnx, 0x0ff6 : scrny,
0x0ff8 : vram
char型は1バイトを使用、short型は2バイトを使用

それぞれのアドレスがnasmhead.nasで定義したBOOT_INFOの部分に一致するのが確認できるかと思います(上に記載したnasmhead.nasの該当部分を参照してください)。

次にfont_A配列について。
これは本に書いてある通り、「A」という文字を8(横)×16(縦)のドットで表現した場合に「塗りつぶしたドット部分」をhexで表した値を配列として定義したものになります。
下に表したものだとわかりやすいかな。

0 0 0 0 0 0 0 0    = 0x00
0 0 0 1 1 0 0 0    = 0x18
0 0 0 1 1 0 0 0    = 0x18
0 0 0 1 1 0 0 0    = 0x18
0 0 0 1 1 0 0 0    = 0x18
0 0 1 0 0 1 0 0    = 0x24
0 0 1 0 0 1 0 0    = 0x24
0 0 1 0 0 1 0 0    = 0x24
0 0 1 0 0 1 0 0    = 0x24
0 1 1 1 1 1 1 0    = 0x7e
0 1 0 0 0 0 1 0    = 0x42
0 1 0 0 0 0 1 0    = 0x42
0 1 0 0 0 0 1 0    = 0x42
1 1 1 0 0 1 1 1    = 0xe7
0 0 0 0 0 0 0 0    = 0x00
0 0 0 0 0 0 0 0    = 0x00

2進数表現でドットを塗りつぶした部分を1とした場合、各行の2進数をhexで表したものがfont_A配列に格納されています。
あとは、これをvramに書き込んであげることで「A」という文字を表示します。その処理を行うのがputfont8関数です。

putfont8関数では、上で定義したfont_A配列を使ってAを表示します。
for文内にあるif文は各ループにおいて全て実施されます。記述されているif文は、font_Aの各配列要素を2進数で考えた際に「1になっている部分を識別して、その部分に色をセットする」ことをやっています。どの色をセットするかはputfont8のarg4で指定したもの(今回の場合はCOL8_FFFFFF)で決定しています。

ということで、実行した結果は以下のようになります。
f:id:motojiroxx:20180619010703p:plain
図形だけでなく文字もちゃんと表示することができました。

harib02e

フォントを増やして色々な文字を表示する、という話なのですがここでも独自ツール。。。はぁ。
フォントの元となるhankaku.txtファイルはあるので、それを読み取って先ほどのAと同じようなものを作るコードが必要になります。
処理の方針としては、hankaku.txtに書かれているドットとアスタリスクで描かれたものを読み取り、ドットだったら0、アスタリスクだったら1に変換して、それをfont_X[16]といった形でhexの値を入れていくという形です。
で、自分にはコードを自力で書く能力はないので、いつも通り参考となるサイトに書いてあるものを見よう見まねでやってみます。
参考とするのは、以下のサイトです。いつも参考にさせていただいている「サラリーマンがハッカーを真剣に目指す」のOS自作5日目です
GDT(グローバルディスクリプタテーブル) | OS自作入門 5日目-1 【Linux】 | サラリーマンがハッカーを真剣に目指す
ということで、上記に書いてあるコードを参考に(丸パクリ)します。
converter.cとして保存し、まずはこいつをコンパイルして実行します
(このコードについては、下で自分なりの解説などを入れたいと思います)

gcc converter.c -o converter.o
./converter.o

実行がうまくいくと、ディレクトリにhankaku.cというコードが出てきますので、これをつかってOS上でA以外のフォントを表示させます。
bootpack.cでの変更点は、まずfont_Aの配列をなくしてhankaku.cで定義している配列を取り込むコードをかきます。次に取り込んだ配列を使って複数の文字を画面上に描画する処理をかきます。
今回は変更点だけ以下に記します(他の部分は変更ありません)。

void HariMain(void)
{
	struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0;
	extern char hankaku[4096];

	init_palette();		/* setting palette */
	init_screen(binfo->vram, binfo->scrnx, binfo->scrny);
	putfont8(binfo->vram, binfo->scrnx,  8, 8, COL8_FFFFFF, hankaku + 'A' * 16);
	putfont8(binfo->vram, binfo->scrnx, 16, 8, COL8_FFFFFF, hankaku + 'B' * 16);
	putfont8(binfo->vram, binfo->scrnx, 24, 8, COL8_FFFFFF, hankaku + 'C' * 16);
	putfont8(binfo->vram, binfo->scrnx, 40, 8, COL8_FFFFFF, hankaku + '1' * 16);
	putfont8(binfo->vram, binfo->scrnx, 48, 8, COL8_FFFFFF, hankaku + '2' * 16);
	putfont8(binfo->vram, binfo->scrnx, 56, 8, COL8_FFFFFF, hankaku + '3' * 16);

また、hariboteos.img作成時にもhankaku.cをリンクさせないといけないので、Makefileを少し変更します。
とはいってもbootpack.hrbの部分にhankaku.cを追加するだけです。

ootpack.hrb: hankaku.c bootpack.c nasmfunc.o os.lds
	gcc -march=i486 -m32 -nostdlib -T os.lds nasmfunc.o hankaku.c bootpack.c -o bootpack.hrb

実行した結果は以下の通りです。
f:id:motojiroxx:20180619032809p:plain

いいかんじです!

harib02f

文字列をかきますが、やることは先ほどのコードのうち、文字を設定する関数をまとめるだけです。で、文字列の終端はヌルバイトになるため、ヌルバイトに到達したら設定を終了するという処理をかけばいいです。bootpack.cに関数を追加しますが、その関数がこちら。

void putfonts8_asc(char *vram, int xsize, int x, int y, char c, unsigned char *s)
{
        extern char hankaku[4096];
        for (; *s != 0x00; s++) {
                putfont8(vram, xsize, x, y, c, hankaku + *s * 16);
                x += 8;
        }
        return;
}

実行した結果は以下の通りです。
f:id:motojiroxx:20180622001637p:plain

harib02g

変数の値を表示させます。
が、他のサイトでもあるとおり、sprintf関数をそのまま使おうとするとエラーが出て先に進めなくなります。
私の場合、以下のようなエラーが出ました。

forensic@forensic-virtual-machine:~/tegaki_os/day5/harib02g$ make hariboteos.img
nasm ipl.nas -o ipl.bin
nasm nasmhead.nas -o nasmhead.bin -l nasmhead.lst
nasm -f elf nasmfunc.nas -o nasmfunc.o -l nasmfunc.lst
gcc -march=i486 -m32 -nostdlib -T os.lds nasmfunc.o hankaku.c bootpack.c -o bootpack.hrb
/tmp/ccfzvo0m.o: In function `HariMain':
bootpack.c:(.text+0xc3): undefined reference to `sprintf'
collect2: error: ld returned 1 exit status
Makefile:2: recipe for target 'bootpack.hrb' failed
make: *** [bootpack.hrb] Error 1

リンクで問題がおこっているっぽいので、その辺どうにか対処すればいきそうなのですがちょっと今回はさくっといきたいのでharib02eの時と同様に参考サイトのsprintf関数のコードをお借りします。
sprintfを実装する | OS自作入門 5日目-2 【Linux】 | サラリーマンがハッカーを真剣に目指す
上記のリンクにあるコードをmysprintf.cとして保存します。
あとは、bootpack.cでmysprintf.cで定義されている関数を使うので、Makefileを編集してちゃんとリンクされるように設定します。
変更したのはbootpack.hrbを生成する部分で、加えてmysprintf.oを生成する項目を新たに記述いたしました。

bootpack.hrb: hankaku.c mysprintf.o bootpack.c nasmfunc.o os.lds
        gcc -march=i486 -m32 -nostdlib -T os.lds nasmfunc.o hankaku.c mysprintf.o bootpack.c -o bootpack.hrb
・・・
mysprintf.o: mysprintf.c
        gcc -c -m32 -march=i486 -nostdlib mysprintf.c -o mysprintf.o

bootpack.cのコードについては本の内容で十分かと思いますので、改めてここで説明する必要はないかと。
ということで、実行した結果は以下の通りです。
f:id:motojiroxx:20180624182706p:plain

harib02h

マウスカーソルを描いてみます。ようやくOSっぽさが出てきた感じです。
4日目の最後にやったタスクバーの表示などと似たような感じで、マウスカーソルを出していきます。
今回もbootpack.cにコードを追加していきます。これも本を見ておけば問題ないかなと。
ということで、実行した結果は以下の通り。
f:id:motojiroxx:20180625010956p:plain

まだカーソルは動きませんが、だんだんOSっぽくなってきました!


ということで、今回はこの辺で。
ソースコードについては、ここで載せるよりかはgithubにあげた方がいいかと思いますので、後日その辺は載せようかと。

30日OS自作入門-4日目-

長くかかった3日目が終了し、これでやっとC言語を使った作業が行えます。
ということで早速はじめていきます。

今日のまとめ

4日目は画面表示がメインの項目になります。
画面表示をさせたい場合にはVRAMの領域に表示させたいものを記述させることで、OS起動時にVRAMを読み取って画面出力を行う、と言う流れです。
なので、今回やる内容としては

  • OS起動時に画面表示を行う関数を作成
  • 関数内で、「どういったものを画面表示させるか」を設定

ということになります。特にここではメモリのアドレスを意識してやっていく必要があります。

harib01a

3日目の最後に作成したharib00jのファイルに追記していく形になります。
まずは、nasmfunc.nasに画面表示用の関数と処理を定義します。nasmfunc.nasの中身は以下の通り

; File name : nasmfunc.nas
; TAB=4

[BITS 32]			; 32 bit mode machine code

; information for object file

	GLOBAL	io_hlt		; function name that contains this program
	GLOBAL	write_mem8


[SECTION .text]			; write this description at first
io_hlt:				; void io_hlt(void)
	HLT
	RET

write_mem8:			; void write_mem8(int addr, int data)
	MOV	ECX,[ESP+4]
	MOV	AL,[ESP+8]
	MOV	[ECX],AL
	RET			

write_mem8という関数を追加しています。
これ自体は、呼び出し元で設定された変数を読み出して各レジスタに格納している処理になります。

続いて、nasmfunc.nasで定義した関数を呼び出す側の方をみていきます。write_mem8を呼び出しているのはbookpack.cになります。

/* File name : bootpack.c*/
/* proto type declaration */
extern void io_hlt(void);
extern void write_mem8(int addr, int data);

void HariMain(void)
{
	int i;			/* 32bit integer */
	for(i = 0xa0000; i <= 0xaffff; i++){
		write_mem8(i,15);	/* MOV BYTE [i],15*/
	}

	for(;;){
		io_hlt();
	}
}

write_mem8で設定されている引数はarg1=i, arg2=15となります。
arg1で設定されているiは、繰り返しによって値が0xa0000から0xaffffまで変化します。
arg2では固定値として15が指定されています。

結論から言うと、動作させた結果は以下の通り真っ白な画面が出てきます。
f:id:motojiroxx:20180611023807p:plain
arg1の0xa0000から0xaffffの範囲は「画面表示させるものが記録されているメモリアドレス」でarg2の15が「実際に表示させるもの」になります。
で、15が画面の色で白を意味するので画面全体が真っ白になる、ということです。

ベースはわかったので、あとはちょいちょい値を変更していって画面の変化を観察していきます。

harib01b

今度はしましま模様を出します。変更する部分はbootpack.cのwrite_mem8に渡す引数部分です。
arg2で今度はiと0x0fのANDを取っています。

/* File name : bootpack.c
/* proto type declaration */
extern void io_hlt(void);
extern void write_mem8(int addr, int data);

void HariMain(void)
{
	int i;			/* 32bit integer */
	for(i = 0xa0000; i <= 0xaffff; i++){
		write_mem8(i,i & 0x0f);		/* MOV BYTE [i],15*/
	}

	for(;;){
		io_hlt();
	}
}

実行結果はこちら。
f:id:motojiroxx:20180611025504p:plain

んで、このあとのharib01cからharib01eまではポインタの話がつらつら書いているだけなので飛ばします。

harib01f

harib01bとはちょっと色違いのしましまを出します。その際、harib01bとは異なり色番号を自分で設定します。

harib01bでコードを追加したbootpack.cに、さらに結構コードを追加します。
追加するコードは、自分で設定する色番号とそれに関連する関数になります。

/* File name : bootpack.c */
/* proto type declaration */

extern void io_hlt(void);
extern void io_cli(void);
extern void io_out8(int port, int data);
extern int io_load_eflags(void);
extern void io_store_eflags(int eflags);

void init_palette(void);
void set_palette(int start, int end, unsigned char *rgb);

void HariMain(void)
{
	int i;			/* 32bit integer */
	char *p;		/* pointer */

	init_palette();		/* setting palette */

	p = (char *)0xa0000;	/* set address of VRAM area */

	for(i = 0; i <= 0xffff; i++){
		p[i] = i & 0x0f;	/* MOV BYTE [i],15*/
	}

	for(;;){
		io_hlt();
	}
}

void init_palette(void)
{
	static unsigned char table_rgb[16*3] = {
		0x00, 0x00, 0x00,	/* 0:black */
		0xff, 0x00, 0x00,	/* 1:light red */
		0x00, 0xff, 0x00,	/* 2:light green */
		0xff, 0xff, 0x00,	/* 3:light yellow */
		0x00, 0x00, 0xff,	/* 4:light blue */
		0xff, 0x00, 0xff,	/* 5:light purple */
		0x00, 0xff, 0xff,	/* 6:light water blue */
		0xff, 0xff, 0xff,	/* 7:white */
		0xc6, 0xc6, 0xc6,	/* 8:light gray */
		0x84, 0x00, 0x00,	/* 9:dark red */
		0x00, 0x84, 0x00,	/*10:dark green */
		0x84, 0x84, 0x00,	/*11:dark yellow */
		0x00, 0x00, 0x84,	/*12:dark blue */
		0x84, 0x00, 0x84,	/*13:dark purple */
		0x00, 0x84, 0x84,	/*14:dark water blue */
		0x84, 0x84, 0x84	/*15:dark gray */
	};
	set_palette(0, 15, table_rgb);
	return;

	/* static char operation is equals to DB operation */
}

void set_palette(int start, int end, unsigned char *rgb)               /*パレットのアクセスの手順にしたがって処理を行う*/
{
	int i, eflags;
	eflags = io_load_eflags();		/* record the allowing interrupt flags value */
	io_cli();				/* set allowing flag 0 for prohibitting interrupt */
	io_out8(0x03c8, start);

	for(i = start; i <= end; i++){
		io_out8(0x03c9, rgb[0]/4);
		io_out8(0x03c9, rgb[1]/4);
		io_out8(0x03c9, rgb[2]/4);
		rgb += 3;			/* gain rgb pointer index 3 */
	}

	io_store_eflags(eflags);		/* restore interrupt allowing flags */
	return;
}

色番号の設定は、以下の2つの関数で行なっています。

init_palette
set_palette

2つの関数の処理の流れとしては、

  1. init_palette関数を呼び出し、関数内部で色番号の配列を定義しset_palette関数を呼び出す
  2. set_palette関数内で、eflagの値を退避させたり割込み禁止にしたりしたあと、for文のなかで自分が設定しようとしている色番号を登録し、割込み禁止解除とeflagsの値を復元

というものになります。
特に、「set_palette関数って何やってるの?」というところについては、以下のサイトに詳しく書いてあります。
http://oswiki.osask.jp/?VGA
上記のリンクの「ビデオDAコンバータ」の項目の「パレットのアクセスの手順」を確認しておくといいかもです。
あとは、HariMainのルーチンに戻ったあと、登録した色番号を使用してVRAMのアドレスに色を書き込んでいきます。
これが今回書いたbootpack.cの処理フローです。

続いて。上記で追加した関数の処理についてはnasmfunc.nasにて定義します。なのでnasmfunc.nasもコードを追加します。

; nasmfunc
; TAB=4

; [FORMAT "WCOFF"]		; the mode creating object file
[BITS 32]			; 32 bit mode machine code

; information for object file

; [FILE "nasmfunc.nas"]		; source file information

	GLOBAL	io_hlt		; function name that contains this program
	GLOBAL	io_cli
	GLOBAL	io_sti
	GLOBAL	io_stihlt
	GLOBAL	io_in8
	GLOBAL	io_in16
	GLOBAL	io_in32
	GLOBAL	io_out8
	GLOBAL	io_out16
	GLOBAL	io_out32
	GLOBAL	io_load_eflags
	GLOBAL	io_store_eflags

section .text			; write this description at first

io_hlt:				; void io_hlt(void)
	HLT
	RET

io_cli:				; void io_cli(void)
	CLI
	RET

io_sti:				; void io_sti(void)
	STI
	RET

io_stihlt:			; void io_stihlt(void)
	STI
	HLT
	RET

io_in8:				; int io_in8(int port)
	MOV	EDX,[ESP+4]	; [ESP+4] is arg1
	MOV	EAX,0
	IN	AL,DX
	RET


io_in16:			; int io_in16(int port)
	MOV	EDX,[ESP+4]
	MOV	EAX,0
	IN	AX,DX
	RET

io_in32:			; int io_in32(int port)
	MOV	EDX,[ESP+4]
	IN	EAX,DX
	RET

io_out8:			; void io_out8(int port, int data)
	MOV	EDX,[ESP+4]
	MOV	AL,[ESP+8]
	OUT	DX,AL
	RET

io_out16:			; void io_out16(int port, int data)
	MOV	EDX,[ESP+4]
	MOV	EAX,[ESP+8]
	OUT	DX,AX
	RET

io_out32:			; void io_out32(int port, int data)
	MOV	EDX,[ESP+4]
	MOV	EAX,[ESP+8]
	OUT	DX,EAX
	RET

io_load_eflags:			; int io_load_eflags(void);
	PUSHFD			; it is equals to PUSH EFLAGS
	POP	EAX
	RET

io_store_eflags:		; void io_store_eflags(int eflags);
	MOV	EAX,[ESP+4]
	PUSH	EAX
	POPFD			; it is equals to POP EFLAGS
	RET

一応、いくつか出てくるアセンブリ命令についてまとめておきます。

IN	EAX,DX                     ; 装置から電気信号を受け取る命令
OUT	DX,EAX                     ; 装置へ電気信号を送る命令
※扱うサイズによってEAXの部分はALAXになる

それぞれ、DXには「装置番号」、EAXには「受け取るor送る値」が設定されます
で、2つの命令では指定するレジスタはDXとEAXで、INとOUTでsourceとdestinationが入れ替わります。
わりと覚えやすいかも。

CLI                    ;  割り込みフラグを0にする命令(CLear Interrupt flag)-> つまり割り込み禁止にする
STI                    ;  割り込みフラグを1にする命令(SeT Interrupt flag)-> つまり割り込みを有効にする

何か重要な処理をしたいので「割り込みは勘弁!」といった際にはCLIを使い、その処理が終了後にSTIで元に戻せばいいんでしょうか。
割り込みに関しては後々やるとのことなので、こんな推測に留めておきます。

PUSHFD                  ; eflags(フラグレジスタの値)をDWORDサイズでスタックに積む
POPFD                    ; eflags(フラグレジスタの値)をDWORDサイズでスタックから取り出す

フラグレジスタの操作に関連したpushとpop命令です。


んで、最終的に実行結果はこちら。harib01bとはちょっと色が違うことが確認できるかと思います。
f:id:motojiroxx:20180613020616p:plain

harib01g

ここでは3つほど色の違う四角形を表示させます。
先ほど作成したbootpack.cの内容を色々変更しますが、変更点については以下の通り

  • 先ほど配列で定義していた色番号をdefineで定義しなおす
  • 四角形を描画する関数boxfill8を定義して、指定した色で塗りつぶして表示する処理を記述

というわけで変更後のbootpack.cのコードは以下の通りです。

/* proto type declaration */ 
extern void io_hlt(void); 
extern void io_cli(void);
extern void io_out8(int port, int data);
extern int io_load_eflags(void);
extern void io_store_eflags(int eflags);

void init_palette(void);
void set_palette(int start, int end, unsigned char *rgb);
void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1);

#define COL8_000000	0
#define COL8_FF0000	1
#define COL8_00FF00	2
#define COL8_FFFF00	3
#define COL8_0000FF	4
#define COL8_FF00FF	5
#define COL8_00FFFF	6
#define COL8_FFFFFF	7
#define COL8_C6C6C6	8
#define COL8_840000	9
#define COL8_008400	10
#define COL8_848400	11
#define COL8_000084	12
#define COL8_840084	13
#define COL8_008484	14
#define COL8_848484	15

void HariMain(void)
{
	int i;			/* 32bit integer */
	char *p;		/* pointer */

	init_palette();		/* setting palette */

	p = (char *)0xa0000;	/* set address of VRAM area */

/* 3つの四角形を、描画位置と色を指定して描画 */
	boxfill8(p, 320, COL8_FF0000, 20, 20, 120, 120);
	boxfill8(p, 320, COL8_00FF00, 70, 50, 170, 150);
	boxfill8(p, 320, COL8_0000FF, 120, 80, 220, 180);

	for(;;){
		io_hlt();
	}
}

void init_palette(void)
{
	static unsigned char table_rgb[16*3] = {
		0x00, 0x00, 0x00,	/* 0:black */
		0xff, 0x00, 0x00,	/* 1:light red */
		0x00, 0xff, 0x00,	/* 2:light green */
		0xff, 0xff, 0x00,	/* 3:light yellow */
		0x00, 0x00, 0xff,	/* 4:light blue */
		0xff, 0x00, 0xff,	/* 5:light purple */
		0x00, 0xff, 0xff,	/* 6:light water blue */
		0xff, 0xff, 0xff,	/* 7:white */
		0xc6, 0xc6, 0xc6,	/* 8:light gray */
		0x84, 0x00, 0x00,	/* 9:dark red */
		0x00, 0x84, 0x00,	/*10:dark green */
		0x84, 0x84, 0x00,	/*11:dark yellow */
		0x00, 0x00, 0x84,	/*12:dark blue */
		0x84, 0x00, 0x84,	/*13:dark purple */
		0x00, 0x84, 0x84,	/*14:dark water blue */
		0x84, 0x84, 0x84	/*15:dark gray */
	};
	set_palette(0, 15, table_rgb);
	return;

	/* static char operation is equals to DB operation */
}

void set_palette(int start, int end, unsigned char *rgb)
{
	int i, eflags;
	eflags = io_load_eflags();		/* record the allowing interrupt flags value */
	io_cli();				/* set allowing flag 0 for prohibitting interrupt */
	io_out8(0x03c8, start);

	for(i = start; i <= end; i++){
		io_out8(0x03c9, rgb[0]/4);
		io_out8(0x03c9, rgb[1]/4);
		io_out8(0x03c9, rgb[2]/4);
		rgb += 3;			/* gain rgb pointer index 3 */
	}

	io_store_eflags(eflags);		/* restore interrupt allowing flags */
	return;
}

void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1)
{
	int x,y;
	for(y = y0; y <=y1; y++){
		for(x = x0; x <= x1; x++){
			vram[y * xsize + x] = c;
		}
	}
	return;
}

表示される画面はこんな感じです。
f:id:motojiroxx:20180614025851p:plain

boxfill8で特定の座標に四角形を描画していますが、座標軸は通常数学でやるときの中心を原点とするものではないです。
PCの場合、画面左上を原点として座標の計算を行います(一般的な話かどうかは知りませんが)。
今回四角形の描画で使用しているboxfill8の引数と関連させてみると、以下の図のようになるかと。
f:id:motojiroxx:20180616142652p:plain

あとは、実際に描画させる際にはVRAMのアドレスを対応させればいいです。
なので、「画面上の」原点の位置は、「メモリ上では」0xa0000に対応するので、特定の座標の色に対応するメモリの位置は以下の計算式で求められます。

0xa0000 + x + y * 320

で、「なぜyに320をかけるのか?」と言う疑問がありますが、これについては以下のサイトにあるsksatさんの解説が非常にわかりやすいです(ページ上で「320」と検索をかければ見つかります」
http://hrb.osask.jp/wiki/?q_and_a

加えて、こちらのサイトの内容も確認しておくとこの疑問については解消されるのではないかと思います。
OS自作入門 4日目 【Linux】| VRAMとカラーパレット | サラリーマンがハッカーを真剣に目指す
具体的に、0xa0000から開始されるvramにおいて、(X座標, Y座標)がどのアドレスに該当するのかをわかりやすく示してくれています。


ここまでくると「おぉ〜」とちょっと感動しますね。

harib01h

4日目の最後です。ここでは、昔のウィンドウズっぽい画面を描画します。
ただ、とくに新しいことはなく、boxfill8関数を使ってそれっぽいものをじゃんじゃん描画していくのみになります。
なので、変更するのはbootpack.cのみ。

/* proto type declaration */ 
extern void io_hlt(void); 
extern void io_cli(void);
extern void io_out8(int port, int data);
extern int io_load_eflags(void);
extern void io_store_eflags(int eflags);

void init_palette(void);
void set_palette(int start, int end, unsigned char *rgb);
void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1);

#define COL8_000000	0
#define COL8_FF0000	1
#define COL8_00FF00	2
#define COL8_FFFF00	3
#define COL8_0000FF	4
#define COL8_FF00FF	5
#define COL8_00FFFF	6
#define COL8_FFFFFF	7
#define COL8_C6C6C6	8
#define COL8_840000	9
#define COL8_008400	10
#define COL8_848400	11
#define COL8_000084	12
#define COL8_840084	13
#define COL8_008484	14
#define COL8_848484	15

void HariMain(void)
{
	char *vram;
	int xsize, ysize;	

	init_palette();		/* setting palette */
	vram = (char *)0xa0000;	/* set address of VRAM area */
	xsize = 320;
	ysize = 200;

	boxfill8(vram, xsize, COL8_008484,  0,		0,		xsize - 1, 	ysize - 29);
	boxfill8(vram, xsize, COL8_C6C6C6,  0,		ysize - 28,	xsize - 1, 	ysize - 28);
	boxfill8(vram, xsize, COL8_FFFFFF,  0,		ysize - 27,	xsize - 1, 	ysize - 27);
	boxfill8(vram, xsize, COL8_C6C6C6,  0,		ysize - 26,	xsize - 1, 	ysize - 1);

	boxfill8(vram, xsize, COL8_FFFFFF,  3,		ysize - 24,	59,		ysize - 24);
	boxfill8(vram, xsize, COL8_FFFFFF,  2,		ysize - 24,	2, 		ysize - 4);
	boxfill8(vram, xsize, COL8_848484,  3,		ysize - 4,	59, 		ysize - 4);
	boxfill8(vram, xsize, COL8_848484,  59,		ysize - 4,	59, 		ysize - 5);
	boxfill8(vram, xsize, COL8_000000,  2,		ysize - 3,	59, 		ysize - 3);
	boxfill8(vram, xsize, COL8_000000,  60,		ysize - 24,	60, 		ysize - 3);

	boxfill8(vram, xsize, COL8_848484,  xsize - 47,	ysize - 24,	xsize - 4, 	ysize - 24);
	boxfill8(vram, xsize, COL8_848484,  xsize - 47,	ysize - 23,	xsize - 47, 	ysize - 4);
	boxfill8(vram, xsize, COL8_FFFFFF,  xsize - 47,	ysize - 3,	xsize - 4, 	ysize - 3);
	boxfill8(vram, xsize, COL8_FFFFFF,  xsize - 3,	ysize - 24,	xsize - 3, 	ysize - 3);

	for(;;){
		io_hlt();
	}
}

void init_palette(void)
{
	static unsigned char table_rgb[16*3] = {
		0x00, 0x00, 0x00,	/* 0:black */
		0xff, 0x00, 0x00,	/* 1:light red */
		0x00, 0xff, 0x00,	/* 2:light green */
		0xff, 0xff, 0x00,	/* 3:light yellow */
		0x00, 0x00, 0xff,	/* 4:light blue */
		0xff, 0x00, 0xff,	/* 5:light purple */
		0x00, 0xff, 0xff,	/* 6:light water blue */
		0xff, 0xff, 0xff,	/* 7:white */
		0xc6, 0xc6, 0xc6,	/* 8:light gray */
		0x84, 0x00, 0x00,	/* 9:dark red */
		0x00, 0x84, 0x00,	/*10:dark green */
		0x84, 0x84, 0x00,	/*11:dark yellow */
		0x00, 0x00, 0x84,	/*12:dark blue */
		0x84, 0x00, 0x84,	/*13:dark purple */
		0x00, 0x84, 0x84,	/*14:dark water blue */
		0x84, 0x84, 0x84	/*15:dark gray */
	};
	set_palette(0, 15, table_rgb);
	return;

	/* static char operation is equals to DB operation */
}

void set_palette(int start, int end, unsigned char *rgb)
{
	int i, eflags;
	eflags = io_load_eflags();		/* record the allowing interrupt flags value */
	io_cli();				/* set allowing flag 0 for prohibitting interrupt */
	io_out8(0x03c8, start);

	for(i = start; i <= end; i++){
		io_out8(0x03c9, rgb[0]/4);
		io_out8(0x03c9, rgb[1]/4);
		io_out8(0x03c9, rgb[2]/4);
		rgb += 3;			/* gain rgb pointer index 3 */
	}

	io_store_eflags(eflags);		/* restore interrupt allowing flags */
	return;
}

void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1)
{
	int x,y;
	for(y = y0; y <=y1; y++){
		for(x = x0; x <= x1; x++){
			vram[y * xsize + x] = c;
		}
	}
	return;
}

表示される画面はこんな感じ。
f:id:motojiroxx:20180616154402p:plain

なんかOSぽくなってきました!ちょっと今後が楽しみ

という感じで、今回は特に大きな失敗はありませんでした(逆に不安

30日OS自作入門-番外編その1 FTK Imagerを使ったイメージファイルの読み込み-

本編とは関係ないのですが、ちょっと自分が使っているツールの紹介をば。

 

まだ3日目ですが、作成したイメージファイルの中身の構造がどうなっているのかを知りたい際には、FTK Imagerというツールがおすすめです。

Windows上でのみ動くツールですが、イメージファイルの簡易的な解析には非常に使えます。まぁ、このツール自体がフォレンジック用途なので、ファイルシステムの解析などに強いと言うのもあるのですが。

 

 

私自身、フォレンジックをやっている身なので、よくイメージファイルの中身を簡易的に見るという作業をやっているのですが、その際に使用しています。

 

FTK Imger自体は、ダウンロードフォームに記入後、メールにてダウンロードリンクが飛んできますので、そこに記載されているダウンロードリンクから落とせます。

accessdata.com

 

このツールを使って作成したイメージファイルをマウントさせると、こんな感じで中身が見れます。

f:id:motojiroxx:20180609235025p:plain

 

ちょっとだけ、使い方を紹介いたします。

 

FTK Imagerの使い方

使うときのフローとしては以下の通り。

  1. 作成したイメージファイルor接続されているHDDを選択
  2. ツールに読み込まれたイメージファイルorHDDの中身をみる

です。非常に簡単です。

1. 作成したイメージファイルの選択

まずは、作成したイメージファイルの選択をします。以下の図のようにやっていただければいいです。

流れとしては

  • Evidenceの追加
  • Evidenceの種類を選択(今回はイメージファイルなので、Image Fileを選択)
  • イメージファイルを指定

f:id:motojiroxx:20180610001946p:plain

 

最後のFinishをクリックすると、下のような画面に切り替わります。

 

f:id:motojiroxx:20180610002253p:plain

それぞれの画面で、イメージファイルないのディレクトリツリー、ファイルのリスト、プロパティ情報、hexを確認することができます。

で、30日自作OSの3日目のharib00jで作成したイメージファイルであれば、イメージファイルの先頭から0x4200にbootpackのコードがあるはずです。

 

なので、Evidence Treeの部分でimgファイルを選択し、hexの画面上でCtrl+Gをすると任意のアドレス(オフセット?)に飛ぶことができます。実際に0x4200を指定してそのデータを見て見ると確かにそのコード部分が存在するのが確認できるかと思います。

f:id:motojiroxx:20180610003031p:plain

 

f:id:motojiroxx:20180610003047p:plain

 

hexを見るだけであればxxdコマンドなどを使えばいいのですが、こちらのツールを使うとファイルシステムを読み取って中の構成をちゃんと確認できるので、「作成したharibote.sysがファイルとして格納されているはずなんだけど、本当にあるのか?」など確認できます。

 

その他にも、イメージファイル内に存在するファイルを取ってきたりメモリダンプを取りことも可能です。フリーツールとは思えない完成度だとおもいます。。

 

興味があればぜひ使ってみてください。