2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

半年ほど前にはなりますが、SECCON Beginners CTF 2024に参加しました。
普段業務ではほとんど触らないような領域だったこともあって、ほとんど解くことができませんでした。
その中で特に興味を持ったreversingカテゴリのassembleという問題について、解き方と仕組みを解説します。

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

SECCON Beginners CTF 2024の問題は、こちらのリポジトリにあり、対象の問題は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を書き込むだけなので、以下のようになります。

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します。

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

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

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

ドキュメントを見ると、以下のように記載されています。

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のシステムコールのテーブルを参照します。

上記のテーブルを見ると、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します。

mov rax, 0x6f6c6c6548
push rax

この時点で、スタック領域にデータが積み上がったのでRSP(スタックポインタ)の値が「Helloという文字列の先頭アドレス」になります。
RBP(ベースポインタ),RSPとスタック領域の関係を図にすると以下のようになります。

スクリーンショット 2024-12-07 14.32.14.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

参考

2
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?