目的
会社の同好会で久しぶりにCTF,SECCON Beginners CTF 2024に参加して,なんとかreversingのassembleが解けた*1ので,フラグを得るまでの流れを書き留めておく.
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 2. Please write 0x123 to RAX and push it on stack!
Challenge 1.での結果を利用して1行追加する,というだけ.
mov rax, 0x123 push rax
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 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
感想
いやいや,アセンブラなんて高専時代のZ80くらいしかというレベルからスタート.
6/15夕方
とにかく,参考サイトとして末尾に記載したシステムコールとは何なのかがとてもわかりやすい.
システムコール実行は,前準備をしてからsyscall
の実行でOK.前準備のレジスター設定は順番が決まっており
をするだけ.そして,その実行したシステムコールの戻り値はRAXレジスターに入る.
困ったのは,RAXレジスターに設定するシステムコール番号が,検索したサイトごとに違うことがあって,何が正しいかわからなかったこと.今,冷静に見たら,参考サイトとして記載したサイトでは統一されている.
6/16 夜
一番わからなくなったのは,openシステムコールに引数として与える文字列.結果としては,flag.txt
*2の8文字を"そのまま"渡すだけだった.
検索していろいろ出てきた
'flag.txt'
'./flag.txt'
'flag.txt',0
'./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
今,チームのDiscordを見直したら,flag.txt
の末尾にNULLを埋めてなかっただけで実はもっと早く……
6/16 深夜
メンバーから,openは
- システムコール番号0じゃなくて2で
- 引数はそのまま文字列を渡すだけ
ぽくて,raxレジスターにファイルディスクリプター返るように見えるとのことで,一気に進む.できていなかったのはopenのシステムコールだけだったので,stdoutにテキトーに標準出力したら少し文字足りず:
つづいて,エイヤーで60文字mov rdx, 60
(readもwriteも)としたら,今度はctf4bすら出ず:
結局この文字列は,実際には読み込んでいない領域を表示してナゾだっただけで,結局mov rdx, 60
で指定していても,実際に読み込まれた文字数は52バイトであることがRAXレジスター表示で判明して納得:
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を出力する
参考サイト
- システムコールとは何なのか(2024/06/16現在)
- Linux System Call Table for x86 64(2024/06/16現在)
- Assembly - System Calls(2024/06/16現在)
- Syscall Number for x86-64 linux (A)(2024/06/16現在)