Wiki

Clone wiki

ikebin / 8086 / hello

8086 > 1, 2, 2-1, 2-2, 2-3, 2-4, 2-5, 2-6, 2-7, 3, 4

CC0 この資料とサンプルコードはCC0(いわゆるパブリックドメイン)で提供します。


小さなバイナリを作って分析

  • バイナリの対象は8086版UNIX V6
  • まだOS本体は移植されていないため、インタプリタ上で仮想的に実行

ソース

write.s

! write(1, hello, 6);
mov ax, #1
int 7
.data1 4
.data2 hello, 6

! exit(0);
mov ax, #0
int 7
.data1 1

.sect .data
hello: .ascii "hello\n"
  • ACKの文法
    • アセンブリ言語の文法はアセンブラによって方言がある
    • ! はコメント
  • ax はレジスタと呼ばれます。変数のようなものです。
  • 8086のアセンブリ言語
    • mov は代入: mov ax, #0ax = 0
    • int は割り込み命令、7 は割り込み番号(システムコールは7固定)

システムコール

割り込みでカーネルを呼び出す。

  • .data1 でシステムコール番号を指定
  • ax は第一引数
    • write に渡す 1 は標準出力
  • .data2 は追加の引数

UNIX V6のシステムコール定義: /usr/sys/ken/sysent.c

int sysent[]
{
    追加の引数の数, &処理関数, /* システムコール番号 = システムコール名 */
    ...
};
  • exit: システムコール番号 1, 追加の引数の数 0
  • write: システムコール番号 4, 追加の引数の数 2
  • sysent[] { の間にイコールがないのはC言語が古いため(pre K&R)

実行

$ 8086v6-cc -.o write.s
$ v6strip a.out
$ 7run a.out
hello
  • -.o オプションはcrtやlibcをリンクしないオプション
    • バイナリを小さくするためC言語に必要なものを外した
    • gcc -nostdlib に相当
    • 出力ファイルを指定する -o とは別なのに注意

逆アセンブル

バイナリを分析

$ 7run -d a.out
0000: b80100        mov ax, 0001
0003: cd07          int 7
0005: 04            ; sys write
0006: 1000          ; arg
0008: 0600          ; arg
000a: b80000        mov ax, 0000
000d: cd07          int 7
000f: 01            ; sys exit
  • write.s →(アセンブル)→ a.out →(逆アセンブル)→ 上記結果
    • アセンブル: アセンブリ言語 → バイナリ
    • 逆アセンブル: バイナリ → アセンブリ言語
  • 逆とは言っても完全に元に戻るわけではない
    • 逆アセンブルでは対応するバイナリが確認できる
  • 逆アセンブラの出力はACKの文法とは異なる
    • 最終的にはこの文法を受け付けるアセンブラを提供予定

バイナリダンプ

バイナリを16進数で出力

$ hd a.out
000000 eb 0e 10 00 06 00 00 00 00 00 00 00 00 00 01 00  >................<
000010 b8 01 00 cd 07 04 10 00 06 00 b8 00 00 cd 07 01  >................<
000020 68 65 6c 6c 6f 0a                                >hello.<
000026

hdが存在しない環境ではhexdump -Cを使用

メモリに読み込み

ヘッダはメモリに配置しないため、ファイルのオフセットとメモリのアドレスは16バイトずれる。

0000 b8 01 00 cd 07 04 10 00 06 00 b8 00 00 cd 07 01  ................
0010 68 65 6c 6c 6f 0a                                hello.
  • text(命令)+data

仕様

コード例はF#で示します。

  • ヘッダ+text(命令)+data
  • ヘッダは最初の16バイト
aout = System.IO.File.ReadAllBytes "a.out"
h = aout.[0..15]
  • ヘッダを2バイトずつ区切って、2番目がtextのサイズ、3番目がデータのサイズ。リトルエンディアン。
  • 今回のサンプル: tsize = 0x0010, dsize = 0x0006
tsize = (int h.[2]) ||| ((int h.[3]) <<< 8)
dsize = (int h.[4]) ||| ((int h.[5]) <<< 8)
  • ヘッダの後にtextがあり、その後にdataがある。連続した領域なので一気にメモリに読み込む。
  • 「メモリに読み込み」はmemをダンプしたもの
mem = aout.[16..16+tsize+dsize]

課題 1

※ 制限時間内に作成できない場合、解答例に進んでください。

【30分】バイナリダンプするプログラムを作ってください。(hd a.out 相当)

  1. ファイルそのまま
  2. メモリに読み込んだもの

解答例

1-1.fsx

let aout = System.IO.File.ReadAllBytes "../../a.out"
for i in 0 .. 0x10 .. aout.Length - 1 do
    printf "%04x " i
    for j in 0 .. 0xf do
        if i + j < aout.Length then
            printf "%02x " aout.[i + j]
        else
            printf "   "
    for j in 0 .. 0xf do
        if i + j < aout.Length then
            let ch = int aout.[i + j]
            if 0x20 <= ch && ch <= 0x7e then
                printf "%c" (char ch)
            else
                printf "."
    printfn ""

1-2.fsx

let aout = (System.IO.File.ReadAllBytes "../../a.out").[16..]
// 以下 1-1.fsx と同じ

課題 2

※ 制限時間内に作成できない場合、解答例に進んでください。

  1. 【1時間】逆アセンブラを作ってください。(7run -d a.out 相当)
  2. 【1時間】バイナリを実行してください。(7run a.out 相当)
    • 以後、ここで作ったものをインタプリタと呼びます。

解答例

2-1.fsx

let aout = System.IO.File.ReadAllBytes "../../a.out"
let read16 (a:byte[]) b =
    (int a.[b]) ||| ((int a.[b + 1]) <<< 8)
let tsize = read16 aout 2
let dsize = read16 aout 4
let mem = aout.[16 .. 16 + tsize + dsize - 1]
let mutable ip = 0
let show len dis =
    printf "%04x: " ip
    for j in 0 .. 5 do
        if j < len then
            printf "%02x" mem.[ip + j]
        else
            printf "  "
    printfn "%s" dis
    ip <- ip + len
while ip < tsize do
    match int mem.[ip] with
    | 0xb8 ->
        let n = read16 mem (ip + 1)
        show 3 (sprintf "mov ax, %04x" n)
    | 0xcd ->
        let n = int mem.[ip + 1]
        show 2 (sprintf "int %x" n)
        if n = 7 then
            match int mem.[ip] with
            | 1 ->
                show 1 "; sys exit"
            | 4 ->
                show 1 "; sys write"
                show 2 "; arg"
                show 2 "; arg"
            | _ ->
                show 1 "; ???"
    | _ ->
        show 1 "???"

2-2.fsx

let aout = System.IO.File.ReadAllBytes "../../a.out"
let read16 (a:byte[]) b =
    (int a.[b]) ||| ((int a.[b + 1]) <<< 8)
let tsize = read16 aout 2
let dsize = read16 aout 4
let mem = aout.[16 .. 16 + tsize + dsize - 1]
let mutable ax, ip = 0, 0
while ip < tsize do
    match int mem.[ip] with
    | 0xb8 ->
        ax <- read16 mem (ip + 1)
        ip <- ip + 3
    | 0xcd ->
        let n = int mem.[ip + 1]
        if n <> 7 then
            printfn "%04x: ??? int %x" ip n
            exit 1
        match int mem.[ip + 2] with
        | 1 ->
            exit ax
        | 4 ->
            let arg1 = read16 mem (ip + 3)
            let arg2 = read16 mem (ip + 5)
            let bytes = mem.[arg1 .. arg1 + arg2 - 1]
            for b in bytes do
                printf "%c" (char b)
            ip <- ip + 7
        | sc ->
            printfn "%04x: ??? syscall %d" ip sc
            exit 1
    | b ->
        printfn "%04x: %02x ???" ip b
        exit 1

8086 > 1, 2, 2-1, 2-2, 2-3, 2-4, 2-5, 2-6, 2-7, 3, 4

Updated