簡易catコマンドをアセンブリで自作する

# まえがき
先日[SECCON CTF for Beginners](https://www.seccon.jp/13/seccon_beginners/content.html)が開催された。
その中で、revジャンルの一問目としてアセンブリで`file.txt`を標準出力に読み出すというチャレンジがあった。
面白いチャレンジだったので、どうせならと思って簡易的な`cat`コマンドを自作してみることした。

# 機能
とりあえず今回はオプション等は考えないこととする。
実装する機能は第一引数として与えたパスのファイルの中身を標準出力に書き出すというものにする。
本来なら`cat`は引数を渡さなければ標準入力を読むがいったん考えないことにする。

# アセンブリ言語について
一応基本的な話を解説しておくことにする
書いたアセンブリを読むための必要最低限の情報のみを記載する
そのため厳密さはあまり考えない。例えばシステムコールに渡す値というのは厳密には引数と呼ばないのでは..?といったこと等についてである

## ニーモニック
アセンブリので用いられる命令のこと
以下例と雑な説明
- `mov`: 値をコピー
- `call`: あるラベルへ実行位置を移動
- `cmp`: 2つの値を比較, 結果に応じてフラグを操作
これらの命令を並べてアセンブリを書いていく

## 構文
アセンブリの構文にはIntel記法とAT&T記法の2つがある
筆者はAT&T記法が苦手なので本記事ではIntel記法を採用する
基本的な構文は以下のようになっている
```
<Mnemonic> <Destination>, <Source>
```
Destinationが前Sourceが後であることに注意する
例えば`rax`レジスタの値を`rbx`レジスタにコピーしたい際は以下のように書く(レジスタについては後述)
```
mov rbx, rax
```

また、`;`の後はコメントとなる
```
mov rbx, rax ; This is a comment
```

## レジスタ 
レジスタとは、簡単にはCPUの持つ、高速かつ小さなメモリのこと
CPUのアーキテクチャによって名前や構造は異なる  
本記事ではx64アーキテクチャを採用していることに留意されたし
コードで使用しているレジスタには以下のようなものがある
- `rax`
- `rbx`
- `rdi`
- `rsi`
- `rdx` 
`r...`といったレジスタは64bitのメモリを持っている
それに対し`e...`といったレジスタはrから始まるレジスタの中の後ろ32bitの部分を指す。つまり`rax`と`eax`は同じレジスタを指す。ただし範囲は異なる。

アーキテクチャによって、ある程度度のレジスタを何の用途で使用すべきかということが決まっている
特に、関数やシステムコールを呼び出す際のレジスタの使い方は厳密に定まっている
本記事を読む上では以下の用途を理解しておけばよい
- `syscall`を呼び出したいときは呼び出す前に`rax`レジスタにシステムコール番号を入れておく
- `syscall`を呼び出すとき、第一引数は`rdi`レジスタに入れておく
- `syscall`を呼び出すとき、第二引数は`rsi`レジスタに入れておく
- `syscall`を呼び出すとき、第三引数は`rdx`レジスタに入れておく
- `syscall`の返り値は`rax`レジスタに入る

## システムコール番号 
システムコールを呼び出すには、前述のとおり`syscall`命令を呼ぶ前に`rax`にシステムコール番号を入れておく必要がある。システムコール番号を調べる際は[この辺のサイト](https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/)が便利
もしくは`/usr/include/asm/unistd_(32|64).h`に記載されているので参照するとよい
# 実装 
```asm
section .data
    err_msg db 'Error opening file', 0xA

section .bss
    buffer resb 4096

section .text
    global _start

_start:
    mov rdi, [rsp + 16]
    call _open
    cmp rax, 0
    js _open_error
    mov rbx, rax ; Save file descriptor in rbx
    jmp read_loop

_open:
    mov eax, 2 ; sys_open
    mov rsi, 0 ; O_RDONLY
    syscall
    ret

_read:
    mov eax, 0 ; sys_read
    mov rdi, rbx ; Use saved file descriptor from rbx
    mov rsi, buffer
    mov rdx, 4096
    syscall
    ret

_write:
    mov eax, 1 ; sys_write
    mov rdi, 1 ; stdout
    mov rsi, buffer
    syscall
    ret

_close:
    mov eax, 3 ; sys_close
    mov rdi, rbx ; 
    syscall
    jmp _exit

_exit:
    mov eax, 60 ; sys_exit
    xor rdi, rdi
    syscall

read_loop:
    call _read
    test rax, rax
    jz end_read
    mov rdx, rax ; byte length
    call _write
    jmp read_loop

end_read:
    call _close
    jmp _exit

_open_error:
  mov rsi, err_msg
  mov rdi, 1
  mov rdx, 19
  mov eax, 1 ; sys_write
  syscall
  jmp _exit_error

_exit_error:
  mov eax, 60 ; sys_exit
  mov rdi, 1
  syscall
```

## dataセクション 
`open`に失敗した際に使用するエラーメッセージをハードコードしている
`0xA`は`\n`を表す 
## bssセクション 
ファイルを読む際のバッファを用意している
とりあえず`0x1000`である`4096`にしておいた

## textセクション 
ここまでの説明にない要素について簡単な説明を添える
### start 
プログラムの開始位置となる
以下の命令でc言語でいうところの`argv[1]`の先頭アドレスを`rdi`に乗せている
```
mov rdi, [rsp + 16]
```

`open`の後に返り値が`rax`に入る。これが負の値だった場合ファイルディスクリプタの取得に失敗しているため、エラーを発生させる
```
cmp rax, 0
js _open_error
```

`open`で得たファイルディスクリプタは今後の使用のため`rbx`レジスタに保管しておく
```
mov rbx, rax ; Save file descriptor in rbx
```

### open
`open`システムコールを使用して、読みたいファイルのファイルディスクリプタを取得している。取得したファイルディスクリプタは返り値として`rax`レジスタに格納される

`rdi`レジスタに入るべき第一引数は`start`内で用意しているため、ラベルの中では操作していない

## read 
```
mov rdi, rbx
```
保存しておいたファイルディスクリプタを`rdi`レジスタに設定する
## read_loop 
`read_loop`はファイルからデータを読み出して、バッファに書き込み、その内容を標準出力に出力するループである
```
test rax, rax 
jz end_read
```
この部分でファイルを最後まで読んだかどうかを確認している
ファイルを最後まで読んでいればループを抜ける

# コンパイル
以下のコマンドでコンパイルした
```bash
nasm -f elf64 -o cat.o cat.s
ld cat.o -o cat
```

実行すると実際にファイルの中身を読むことができた
実際のソースコードは以下から参照できる
[github](https://github.com/KikyoNanakusa/asm_commands/blob/develop/cat/cat.s)

#### 小話
コマンドライン引数や環境変数はスタック上に載っていることはある程度実験的に確かめた。
以下の[youtube上の動画](https://youtu.be/xtFs1yBVinc?si=u8bIFni7fMn7Gxf2)にもそんな感じの図が乗っているが~~ソース情報を見つけることができなかった~~。
2024/06/20追記
http://6.s081.scripts.mit.edu/sp18/x86-64-architecture-guide.html


# 参考文献
https://www.mztn.org/lxasm/asm06.html
https://youtu.be/xtFs1yBVinc?si=u8bIFni7fMn7Gxf2