reversing assembleのwriteup@SECCON Beginners CTF 2024

目的

会社の同好会で久しぶりにCTF,SECCON Beginners CTF 2024に参加して,なんとかreversingのassembleが解けた*1ので,フラグを得るまでの流れを書き留めておく.

assemble:Intel記法のアセンブリ言語を書いて、flag.txtファイルの中身を取得してみよう!

なお,この問題は,SECCON Beginners CTF2024のサーバー上で動作するため,PCのOSは何でもよくブラウザーでアクセスできればOKだった.

ご意見・ご感想および記載ミスがありましたら,お手数ですがコメントを下さい.

結論

Beginner問題なので,ステップごとに進み,全部で4ステップある.結局のところ,下表を知っていればスムーズ:

%rax System call %rdi %rsi %rdx
0 sys_read unsigned int fd char *buf size_t count
1 sys_write unsigned int fd const char *buf size_t count
2 sys_open const char *filename int flags int mode

それぞれのステップ(画面表示上はChallenge X,Xは1~4)ごとの結果は以下のとおり:

Challenge 1. Please write 0x123 to RAX!

これはシンプルに,0x123をレジスターRAXに入れてね,というだけ.

mov rax, 0x123

Challenge 1の結果

Challenge 2. Please write 0x123 to RAX and push it on stack!

Challenge 1.での結果を利用して1行追加する,というだけ.

mov rax, 0x123
push rax

Challenge 2の結果

Challenge 3. Please use syscall to print Hello on stdout!

ここから調べながら実施した.

mov rax, 0x6f6c6c6548 # olleHの順に詰める
push rax

mov rax, 1       # writeシステムコールの番号
mov rdi, 1       # stdoutの番号
mov rsi, rsp       # 先ほど詰めたHelloのHが格納されている番地
mov rdx, 5       # stdoutへの書込バイト数(5バイト).writeの戻り値(書込バイト数)はRAXレジスターに格納される
syscall

Challenge 3の結果

Challenge 4. Please read flag.txt file and print it to stdout!

ここで数時間消費した.Openシステムコールの番号と引数の正しい与え方がわからなかったことが原因.

mov rax, 0            # NULL文字終端
push rax
mov rax, 0x7478742e67616c66  # txt.galfの順に詰める
push rax

mov rax, 2           # openシステムコールの番号
mov rdi, rsp           # 先ほど詰めたtxt.galfのfが格納されている番地
mov rsi, 0           # READONLY指定
mov rdx, 0           # 
syscall
mov rbx, rax           # openシステムコールの戻り値(flag.txtのファイルディスクリプター)を待避

mov rax, 0           # readシステムコールの番号
mov rdi, rbx           # flag.txtのファイルディスクリプター
mov rsi, rsp           # flag.txtの読み取り結果を格納する先頭アドレス
mov rdx, 52          # flag.txtの読み取りバイト数
syscall

mov rax, 1           # writeシステムコールの番号
mov rdi, 1           # stdoutの番号
mov rsi, rsp           # flag.txtの読み取り結果が格納された先頭アドレス
mov rdx, 52          # stdoutへの書込バイト数
syscall

Challenge 4の結果

感想

いやいや,アセンブラなんて高専時代のZ80くらいしかというレベルからスタート.

6/15夕方

とにかく,参考サイトとして末尾に記載したシステムコールとは何なのかがとてもわかりやすい.

システムコール実行は,前準備をしてからsyscallの実行でOK.前準備のレジスター設定は順番が決まっており

  1. RAXレジスターにシステムコール番号
  2. RDLレジスターに第1引数
  3. RSLレジスターに第2引数
  4. RDXレジスターに第3引数(R10,R8,R9レジスターに第4,5,6引数とのこと)

をするだけ.そして,その実行したシステムコールの戻り値はRAXレジスターに入る.

困ったのは,RAXレジスターに設定するシステムコール番号が,検索したサイトごとに違うことがあって,何が正しいかわからなかったこと.今,冷静に見たら,参考サイトとして記載したサイトでは統一されている.

6/16 夜

一番わからなくなったのは,openシステムコールに引数として与える文字列.結果としては,flag.txt*2の8文字を"そのまま"渡すだけだった.
検索していろいろ出てきた

  1. 'flag.txt'
  2. './flag.txt'
  3. 'flag.txt',0
  4. './flag.txt',0

を以下の書式ならrspに格納できるようだったので試したが,RAXレジスターの中身が0xffffffffffffffff(いわゆる-1)にしかならず,渋々諦めていた:

mov rax, 0x0000000000002774 # flag.txt with single quote
push rax
mov rax, 0x78742e67616c6627 # flag.txt with single quote
push rax

RAXレジスターの結果(openシステムコールの戻り値)

今,チームのDiscordを見直したら,flag.txtの末尾にNULLを埋めてなかっただけで実はもっと早く……

6/15 20:30のワタクシ

6/16 深夜

メンバーから,openは

  1. システムコール番号0じゃなくて2で
  2. 引数はそのまま文字列を渡すだけ

ぽくて,raxレジスターにファイルディスクリプター返るように見えるとのことで,一気に進む.できていなかったのはopenのシステムコールだけだったので,stdoutにテキトーに標準出力したら少し文字足りず:

stdoutへのテキトー文字数出力

つづいて,エイヤーで60文字mov rdx, 60(readもwriteも)としたら,今度はctf4bすら出ず:

stdoutにナゾの文字列

結局この文字列は,実際には読み込んでいない領域を表示してナゾだっただけで,結局mov rdx, 60で指定していても,実際に読み込まれた文字数は52バイトであることがRAXレジスター表示で判明して納得:

readシステムコールで読み込まれた文字数

great job you have mastered assembly language*3

おまけ

pushするときにレジスターを介さないと4バイトしか実行できなくて,なぜかChallenge 3では,当初以下のように実行して満足していた.ちゃんとステップを踏もうね:

push 0x6c6c6548  # lleHを詰める
mov rax, 1
mov rdi, 1 
mov rsi, rsp
mov rdx, 4
syscall          # Hellまで出力する

push 0x6f       # oを詰める
mov rax, 1
mov rdi, 1 
mov rsi, rsp
mov rdx, 1
syscall          # 最後のoを出力する

参考サイト

  1. システムコールとは何なのか(2024/06/16現在)
  2. Linux System Call Table for x86 64(2024/06/16現在)
  3. Assembly - System Calls(2024/06/16現在)
  4. Syscall Number for x86-64 linux (A)(2024/06/16現在)

*1:一緒に参戦しているメンバーに1つヒントもらってなんとかなった

*2:まぁ後から考えてみればちょうど8文字だった

*3:but, you use 3 instructions onlyと続けたくなった