🔒

SECCON BEGINNER CTF 2023 Writeup(YARO) dimeiza版

2023/06/06に公開

はじめに

先週(6/3〜6/4)、こんなイベントがあったので、自宅から参加してきました。

https://www.seccon.jp/2023/beginners/about-seccon-beginners.html

私はcpaw CTFとかpico CTFは時々やってたんですが、今までリアルタイムのCTFに参加したことはなく、どこかで参加してみたいなーと思っていた所、ちょうど初心者向けのCTFが開催されることを知ったので顔を出してみた次第です。

競技が終了してWrite upをちらほら見ていたんですが、私が解いたあのBeginner問題、意外にもメジャーな解き方が私のものと違っていて。

これはひょっとすると非想定解法なのでは…? 共有する価値あるかも? と思い立ったので、分不相応にもWrite upを書いてみることにしました。

問題(YARO)

image

ファイルを展開すると、yaraルールらしきものとpythonコードが入っていました。

rule shebang {
    strings:
        $shebang = /^#!(\/[^\/ ]*)+\/?/
    condition:
        $shebang
}
rule maybe_python_executable {
    strings:
        $ident = /python(2|3)\r*\n/
    condition:
        shebang and $ident
}

指定されたサーバに接続すると、

rule:

と、いかにもルールを入れてください的なプロンプトが出てくるので、ファイルに入っていたルールを入れてやると、

Found: ./redir.sh, matched: [shebang]
Found: ./server.py, matched: [shebang, maybe_python_executable]
Not found: ./flag.txt
Not found: ./requestments.txt

こんな出力が出てきます。

どうやらpythonコードはサーバで稼働しているコードのようで、カレントフォルダのファイルをYARAルールでチェックしているようです。

カレントフォルダにはflag.txtというファイルがあるので、このファイルにルールをマッチさせつつ、ファイルの中身を取り出せば、フラグを取得できそう、という状況でした。

皆様のWrite up

で、皆様のWrite upをちらほら見ていると、基本的には1文字づつ特定させながら当てていく、という方針らしくて。

https://hack.nikkei.com/blog/ctf4b202306/

作問者の方のWriteupもそんな感じでした。

https://feneshi.co/ctf4b2023writeup/

私のWrite up

実は私、この問題にぶち当たるまでYARAのことを全く知らなくてですね。慌ててドキュメントを読み始めたわけです。

https://yara.readthedocs.io/en/stable/

とりあえずstringに正規表現を書けば当たるのかな? と思ったので、フラグの表現を書いてみた所、当たるには当たったんですよね。

rule:
rule ctf_flag {
    strings:
        $ident = /ctf4b{(\w+)}/
    condition:
        $ident 
}

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

ところがマッチしても、マッチ内容を出力する方法がない、ということで、野生の勘とGoogle力をフル回転してドキュメントを探した所、こんなページを見つけたんですよ。

https://yara.readthedocs.io/en/stable/modules/console.html

Consoleモジュール。

New in version 4.2.0.

と書いてありましたが、そういえば、同梱されていたrequirements.txtにはこう書いてあったな、と。

yara-python == 4.2.3
pwntools == 4.10.0
timeout-decorator == 0.5.0

ひょっとして、このConsoleモジュール使えないか? と思って試し始めました。

本当はconsole(string)で中身をそのまま出したかったんですが、stringの変数を指定しただけではうまく動かなくて(私が使い方を知らないだけかもしれません)。

rule ctf_flag {
    strings:
        $ident = /ctf4b{(\w+)}/
    condition:
        $ident and console.log($ident)
}

こうしたかったんですが、出力は"Something wrong"。

そこで、苦し紛れにこんなことをしてみたのです。

import "console"
rule ctf_flag {
    strings:
        $ident = /ctf4b{(\w+)}/
    condition:
        $ident 
        and console.log("",uint8(@ident+0))
        and console.log("",uint8(@ident+1))
        and console.log("",uint8(@ident+2))
        and console.log("",uint8(@ident+3))
        and console.log("",uint8(@ident+4))
}

このときの出力を見た瞬間、これはイケるのでは、と思いました。

Not found: ./redir.sh
Not found: ./server.py
99
116
102
52
98
Found: ./flag.txt, matched: [ctf_flag]
Not found: ./requestments.txt

99→'c'、116→'t'、102→'f'、52→'4'、98→'b'じゃないですか。

フラグの一部が出せることが分かったので、後は力技で行きます。

import "console"
rule ctf_flag {
    strings:
        $ident = /ctf4b{(\w+)}/
    condition:
        $ident 
        and console.log("",uint8(@ident+0))
        and console.log("",uint8(@ident+1))
        and console.log("",uint8(@ident+2))
        and console.log("",uint8(@ident+3))
        and console.log("",uint8(@ident+4))
        and console.log("",uint8(@ident+5))
        and console.log("",uint8(@ident+6))
        and console.log("",uint8(@ident+7))
        and console.log("",uint8(@ident+8))
        and console.log("",uint8(@ident+9))
        and console.log("",uint8(@ident+10))
        and console.log("",uint8(@ident+11))
        and console.log("",uint8(@ident+12))
        and console.log("",uint8(@ident+13))
        and console.log("",uint8(@ident+14))
        and console.log("",uint8(@ident+15))
        and console.log("",uint8(@ident+16))
        and console.log("",uint8(@ident+17))
        and console.log("",uint8(@ident+18))
        and console.log("",uint8(@ident+19))
        and console.log("",uint8(@ident+20))
        and console.log("",uint8(@ident+21))
        and console.log("",uint8(@ident+22))
        and console.log("",uint8(@ident+23))
        and console.log("",uint8(@ident+24))
        and console.log("",uint8(@ident+25))
        and console.log("",uint8(@ident+26))
        and console.log("",uint8(@ident+27))
        and console.log("",uint8(@ident+28))
        and console.log("",uint8(@ident+29))
        and console.log("",uint8(@ident+30))
        and console.log("",uint8(@ident+31))
        and console.log("",uint8(@ident+32))
        and console.log("",uint8(@ident+33))
        and console.log("",uint8(@ident+34))
        and console.log("",uint8(@ident+35))
        and console.log("",uint8(@ident+36))
        and console.log("",uint8(@ident+37))
        and console.log("",uint8(@ident+38))
        and console.log("",uint8(@ident+39))
}

何文字あるか知らんが、全部ゲロしやがれ!

Not found: ./redir.sh
Not found: ./server.py
99
116
102
52
98
123
89
51
116
95
65
110
48
116
104
51
114
95
82
51
52
100
95
79
112
112
48
114
116
117
110
49
116
121
125
10
Not found: ./flag.txt
Not found: ./requestments.txt

後はこの数値列をcyberchefにかけるだけです。

というわけで、フラグ"ctf4b{Y3t_An0th3r_R34d_Opp0rtun1ty}"をゲットし、無事撃破しました。

さいごに

正直、YARAについては全く素人だったのですが、逆に素人故なのか、ちょっと変わった解法に到達したみたいだったので、調子に乗って共有してみました。

本問のおかげでYARAについて学ぶ機会を得たり、aiwafのような面白い問題があったりと、学びと面白さの共存した不思議なひとときでした。

当初は土曜午後の2時間ぐらいで切り上げるつもりだったのが、気づいたら4時間以上取り組んでいて、日曜の午前中も戦っていました。

私は本来、競争ごとが嫌いで、リアルタイムCTFや競技プログラミングのような活動には否定的だったのですが、

  • 競争を意識せず(他人を気にせず)
  • 自分のレベルに合った、楽しめる問題を
  • 1問でも良いので解けるレベルで解いていく[1]

これができるようであれば、リアルタイムでCTFに参加するのも存外悪くないな、という好印象でした。

リアルタイムCTFへの参加に背中を押された気もするので、常設CTFをちまちま解きつつ、初心者向けのリアルタイムCTFにもちょくちょく顔を出していきたい、と思っているところです。

脚注
  1. 解けなかったときに何も残らず一気にテンションがガタ落ちする、というのが、今のCTFのイケてないところだと思っています。スーパーマリオの中間地点のような、複数段の足がかりのある問題や、解決の方向性についてフィードバックを得られる(正しい方向に進んでいることを示唆する)ような問題があると、もっと初心者に優しいCTFができるのではないか、とも思いました。 ↩︎

Discussion