アセンブリで文字列を出力する

## はじめに
半年ほど前にはなりますが、[SECCON Beginners CTF 2024](https://www.seccon.jp/13/seccon_beginners/content.html)に参加しました。
普段業務ではほとんど触らないような領域だったこともあって、ほとんど解くことができませんでした。
その中で特に興味を持った`reversing`カテゴリの`assemble`という問題について、解き方と仕組みを解説します。

この記事では「アセンブリで文字列を出力する」ところまでやりたいので、Step4は対象外でStep3まで挑戦します。(Step4に関しては別の記事に書く予定です。)

`SECCON Beginners CTF 2024`の問題は、[こちらのリポジトリ](https://github.com/SECCON/SECCON_Beginners_CTF_2024)にあり、対象の問題は[assemble](https://github.com/SECCON/SECCON_Beginners_CTF_2024/tree/main/reversing/assemble)になります。

## 問題概要
Intel記法でアセンブリ言語のプログラムを書いて、flag(特定のfileに存在する文字列)を出力できるようにする問題です。
問題はStep1 ~ Step4まで段階的に進めていき、Step4で`flag.txt`の内容を出力できたら達成です。

今回はStep3までで、アセンブリ言語で特定の文字列を標準出力に出力できるようにするところまでです。

## Step1
> Challenge 1. Please write 0x123 to RAX!
> 1. Only mov, push, syscall instructions can be used.
> 2. The number of instructions should be less than 25.

Step1は愚直にRAXレジスタに対して`0x123`を書き込むだけなので、以下のようになります。

```asm
mov rax, 0x123
```

## Step2
> Challenge 2. Please write 0x123 to RAX and push it on stack!
> 1. Only mov, push, syscall instructions can be used.
> 2. The number of instructions should be less than 25.

Step2も愚直問題で、`0x123`を書き込んだRAXをメモリのスタック領域に対してpushします。

```asm
mov rax, 0x123
push rax
```

## Step3
> Challenge 3. Please use syscall to print Hello on stdout!
> 1. Only mov, push, syscall instructions can be used.
> 2. The number of instructions should be less than 25.

Step3ではsystem callを呼び出して`Hello`という文字列を標準出力に出力します。

### write(2)
標準出力に文字列を出力する際には、STDOUTに対する書き込みを行なっているわけですがこのときに呼び出されているのが、`write`システムコールです。

echoなどの標準出力に出力するようなコマンドを実行し、システムコールをトレースするとわかりやすいです。

```sh
$ strace -e write echo "hello world"
write(1, "hello world\n", 12hello world
)           = 12
+++ exited with 0 +++
```

↑ を見ると`write`を呼び出す際に引数が3つ渡されていますが、これらは何でしょうか？
この`write`のインターフェースについて見ていきます。

https://man7.org/linux/man-pages/man2/write.2.html

ドキュメントを見ると、以下のように記載されています。
```
ssize_t write(int fd, const void buf[.count], size_t count);
```
> write() writes up to count bytes from the buffer starting at buf to the file referred to by the file descriptor fd.

第一引数が出力先のfile descriptor、第二引数がbuffer(の先頭アドレス)、第三引数が対象のbufferからfile descriptorに対して書き込むサイズを表していることがわかります。

今回の場合を考えると、以下のような引数になればいいはずです。

- `fd`: 1(STDOUTのfile descriptorの番号)
- `buf`: Helloという文字列の先頭アドレス
- `count`: 6(Hello + null終端)

### syscallの呼び出し
`write`の際に必要な引数がわかったので、次はアセンブリ言語でどのように書いたら`write`を特定の引数で呼び出せるかです。
今回の問題では、`x86_64`を想定しているので`x86_64`のシステムコールのテーブルを参照します。

https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md#x86_64-64_bit

上記のテーブルを見ると、`write`を呼び出すためにレジスタの状態が以下のようになっている必要があります。

- `rax`: 0x01
- `rdi`: unsigned int fd
- `rsi`: const char *buf
- `rdx`: size_t count

これも同様に今回の場合を考えると以下のようになります。

- `rax`: 0x01
- `rdi`: 0x01
- `rsi`: Helloという文字列の先頭アドレス
- `rdx`: 0x06

### 文字列の取り扱い
`mov`, `push`, `syscall`命令だけしか使えず、dataセクションなどに`Hello`という文字列をそのまま書くことはできないので、HelloをASCIIコードに変換して扱う必要があります。

Helloの文字をそれぞれASCIIコードに変換すると、`0x48`, `0x65`, `0x6c`, `0x6c`, `0x6f`になります。

これに加えて、`x86_64`アーキテクチャにおいてはリトルエンディアンで扱う必要があるので、Helloという文字列を表現しようとすると`0x6f6c6c6548`になります。

### Step3の回答
必要な情報が揃ったので問題を解いていきます。
まずは、Helloという文字列をスタック領域にpushします。
```asm
mov rax, 0x6f6c6c6548
push rax
```
この時点で、スタック領域にデータが積み上がったのでRSP(スタックポインタ)の値が「Helloという文字列の先頭アドレス」になります。
RBP(ベースポインタ),RSPとスタック領域の関係を図にすると以下のようになります。

![スクリーンショット 2024-12-07 14.32.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849988/8b9b7a1d-e6f9-1aac-fcf0-d49ca65324ce.png)

残りは、各レジスタに所定の値を入れていけば良いので以下のようになります。
```
mov rax, 0x6f6c6c6548
push rax

mov rax, 0x01
mov rdi, 0x01
mov rsi, rsp
mov rdx, 0x06
```
これで条件を満たすようなシステムコールを呼び出す準備ができたので、残りはsyscall命令のみです。
```
mov rax, 0x6f6c6c6548
push rax
mov rax, 0x01
mov rdi, 0x01
mov rsi, rsp
mov rdx, 0x06
syscall
```

![スクリーンショット 2024-12-07 14.44.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849988/a8018636-fd04-b14d-0945-fdb1aa0ea9e4.png)

## 参考

https://www.youtube.com/watch?v=HFzk0fKDm_w

https://nextpublishing.jp/book/11353.html

https://www.shoeisha.co.jp/book/detail/9784798170299

https://www.amazon.co.jp/%E3%83%8F%E3%83%AD%E3%83%BC%E2%80%9CHello-World%E2%80%9D-OS%E3%81%A8%E6%A8%99%E6%BA%96%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%81%AE%E3%82%B7%E3%82%B4%E3%83%88%E3%81%A8%E3%81%97%E3%81%8F%E3%81%BF-%E7%AC%AC2%E7%89%88-%E5%9D%82%E4%BA%95%E5%BC%98%E4%BA%AE/dp/4798074144/ref=pd_sbs_d_sccl_1_1/356-0647010-7253863?pd_rd_w=8MxdC&content-id=amzn1.sym.cc4f782f-18e8-4959-acfc-ffdf7905539a&pf_rd_p=cc4f782f-18e8-4959-acfc-ffdf7905539a&pf_rd_r=S2GK9R11ZHJX0YKJHJN5&pd_rd_wg=xML0n&pd_rd_r=eb4e53fa-a514-4288-b3bd-ca43d8cd3e4c&pd_rd_i=4798074144&psc=1