はじめに
この記事は 1日1CTF Advent Calendar 2024 の 5 日目の記事です。
問題
polyglot4b (問題出典: SECCON Beginners CTF 2023)
polyglotってなに?
たぶんpolyglotを作れるエディタを開発したよ!
リポジトリ: https://github.com/SECCON/SECCON_Beginners_CTF_2023/tree/main/misc/polyglot4b
問題概要
import os
import sys
import uuid
import shutil
import subprocess
print(
f"""\033[36m\
____ _ _ _ _____ _ _ _
| _ \ ___ | |_ _ __ _| | ___ | |_ | ____|__| (_) |_ ___ _ __
| |_) / _ \| | | | |/ _` | |/ _ \| __| | _| / _` | | __/ _ \| '__|
| __/ (_) | | |_| | (_| | | (_) | |_ | |__| (_| | | || (_) | |
|_| \___/|_|\__, |\__, |_|\___/ \__| |_____\__,_|_|\__\___/|_|
|___/ |___/
{"-" * 68}
>> """,
end="",
)
file = b""
for _ in range(10):
text = sys.stdin.buffer.readline()
if b"QUIT" in text:
break
file += text
print(f"{'-' * 68}\033[0m")
if len(file) >= 50000:
print("ERROR: File size too large. (len < 50000)")
sys.exit(0)
f_id = uuid.uuid4()
os.makedirs(f"tmp/{f_id}", exist_ok=True)
with open(f"tmp/{f_id}/{f_id}", mode="wb") as f:
f.write(file)
try:
f_type = subprocess.run(
["file", "-bkr", f"tmp/{f_id}/{f_id}"], capture_output=True
).stdout.decode()
except:
print("ERROR: Failed to execute command.")
finally:
shutil.rmtree(f"tmp/{f_id}")
types = {"JPG": False, "PNG": False, "GIF": False, "TXT": False}
if "JPEG" in f_type:
types["JPG"] = True
if "PNG" in f_type:
types["PNG"] = True
if "GIF" in f_type:
types["GIF"] = True
if "ASCII" in f_type:
types["TXT"] = True
for k, v in types.items():
v = "🟩" if v else "🟥"
print(f"| {k}: {v} ", end="")
print("|")
if all(types.values()):
print("FLAG: ctf4b{****REDACTED****}")
else:
print("FLAG: No! File mashimashi!!")
以下のコマンドを実行した結果に、JPEG
, PNG
, GIF
, ASCII
がすべて含まれているようなファイルを作れればクリア。
file -bkr ファイル名
考察
とりあえず file の man を読んでコマンドについているオプションを調べる。
-b, --brief
Do not prepend filenames to output lines (brief mode).
-k, --keep-going
Don't stop at the first match, keep going. Subsequent matches will be have the string '\012- '
prepended. (If you want a newline, see the -r option.)
-r, --raw
Don't translate unprintable characters to \ooo. Normally file translates unprintable characters to
their octal representation.
あまり本質ではなさそう。とりあえずもう少し man を眺める。
The magic tests are used to check for files with data in particular fixed formats. The canonical example of this is a binary executable (compiled program) a.out file, whose format is defined in , and possibly in the standard include directory. These files have a "magic number" stored in a particular place near the beginning of the file that tells the UNIX operating system that the file is a binary executable, and which of several types thereof. The concept of a “magic number” has been applied by extension to data files. Any file with some invariant identifier at a small fixed offset into the file can usually be described in this way. The information identifying these files is read from /etc/magic and the compiled magic file /usr/share/misc/magic.mgc, or the files in the directory /usr/share/misc/magic if the compiled file does not exist. In addition, if \$HOME/.magic.mgc or \$HOME/.magic exists, it will be used in preference to the system magic files.
とりあえず /usr/share/misc/magic.mgc
にどうやって file
コマンドがファイルタイプを判別しているのか書いてありそうなので読んでみた。が、コンパイルされているようで何が書いてあるか全くわからない。少し調べてみると、GitHub にコンパイル前のがあった。
今回関係しそうなのはファイル images
と jpeg
っぽい。長過ぎるので必要な部分だけ掲載する。
...
# Standard PNG image.
0 string \x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0DIHDR PNG image data
...
# GIF
# Strength set up to beat 0x55AA DOS/MBR signature word lookups (+65)
0 string GIF8 GIF image data
0 belong&0xffffff00 0xffd8ff00 JPEG image data
いくつか他にもマッチするパターンがあったが、ほとんど最初の数バイトで判別していて、複数の判定を踏むようにするのは難しそう。
ここで、PNG
の判定を観察していると %d
という表記がなされていることに気づいた。printf
みたいな記法が採用されているのだろうか。
# Standard PNG image.
0 string \x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0DIHDR PNG image data
!:mime image/png
!:ext png
!:strength +10
>16 use png-ihdr
>33 string \x00\x00\x00\x08acTL \b, animated
>>41 ubelong 1 (%d frame
>>41 ubelong >1 (%d frames
>>45 ubelong 0 \b, infinite repetitions)
>>45 ubelong 1 \b, %d repetition)
>>45 ubelong >1 \b, %d repetitions)
magic ファイルのフォーマットを調べてみるため、magic の man を読んでみる。
Each line of the file specifies a test to be performed. A test compares the data starting at a particular offset in the file with a byte value, a string or a numeric value. If the test succeeds, a message is printed. The line consists of the following fields:
(中略)
message The message to be printed if the comparison succeeds. If the string contains a printf(3) format specification, the value from the file (with any specified masking performed) is printed using the message as the format string. If the string begins with "\b", the message printed is the remainder of the string with no whitespace added before it: multiple matches are normally separated by a single space.
太字部分が該当箇所。じゃあ、%s
を含むようなやつを 1 個みつければいい。
sgi
ファイルにこんなのがあった。
# 32bit core file
0 belong 0xdeadadb0 IRIX core dump
>4 belong 1 of
>16 string >\0 '%s'
これを使ってみよう。
$ echo -e "\xde\xad\xad\xb0\x00\x00\x00\x01________hogehoge" > img.txt
$ file -bkr ./img.txt
IRIX core dump of 'hogehoge'
- data
ビンゴ。
$ echo -e "\xde\xad\xad\xb0\x00\x00\x00\x01________JPEG_PNG_GIF_ASCII\nQUIT" | nc localhost 31416
____ _ _ _ _____ _ _ _
| _ \ ___ | |_ _ __ _| | ___ | |_ | ____|__| (_) |_ ___ _ __
| |_) / _ \| | | | |/ _` | |/ _ \| __| | _| / _` | | __/ _ \| '__|
| __/ (_) | | |_| | (_| | | (_) | |_ | |__| (_| | | || (_) | |
|_| \___/|_|\__, |\__, |_|\___/ \__| |_____\__,_|_|\__\___/|_|
|___/ |___/
--------------------------------------------------------------------
>> --------------------------------------------------------------------
| JPG: 🟩 | PNG: 🟩 | GIF: 🟩 | TXT: 🟩 |
FLAG: ctf4b{y0u_h4v3_fully_und3r5700d_7h15_p0ly6l07}