はじめに
この記事は 1日1CTF Advent Calendar 2024 の 7 日目の記事です。
なお、この記事は 先日の polyglot4b の WriteUp の内容を前提としております。まだお読みになっていない方は先にそちらをどうぞ。
問題
polyglot4b2 (問題出典: SECCON Beginners CTF 2023)
より美しいpolyglotを作れるエディタを開発したよ!
やっぱりpolyglotは簡単に作れる?
リポジトリ: https://github.com/SECCON/SECCON_Beginners_CTF_2023/tree/main/misc/polyglot4b2
問題概要
import os
import re
import sys
import uuid
import shutil
import subprocess
print(
f"""\033[31m\
____ _ _ _ _____ _ _ _ ____
| _ \ ___ | |_ _ __ _| | ___ | |_ | ____|__| (_) |_ ___ _ __ |___ \\
| |_) / _ \| | | | |/ _` | |/ _ \| __| | _| / _` | | __/ _ \| '__| __) |
| __/ (_) | | |_| | (_| | | (_) | |_ | |__| (_| | | || (_) | | / __/
|_| \___/|_|\__, |\__, |_|\___/ \__| |_____\__,_|_|\__\___/|_| |_____|
|___/ |___/
{"-" * 76}
>> """,
end="",
)
file = ""
for _ in range(10):
text = sys.stdin.buffer.readline().decode()
if not re.fullmatch("[A-Z]+", text.replace("\n", "")):
print("ERROR: Hi, Hacker.")
sys.exit(0)
if "QUIT" in text:
break
file += text
print(f"{'-' * 76}\033[0m")
if len(file) >= 5000:
print("ERROR: File size too large. (len < 5000)")
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="w") 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}")
# You are a beginner!!!!
_4bflag = True
if "4b" not in f_type:
print("ERROR: You are not a beginner.")
_4bflag = False
types = {
"JPG": False,
"PNG": False,
"GIF": False,
"PDF": False,
"ELF": False,
"TXT": False,
}
f_type = f_type.split("\n")
try:
if "JPEG" in f_type[0]:
types["JPG"] = True
if "PNG" in f_type[1]:
types["PNG"] = True
if "GIF" in f_type[2]:
types["GIF"] = True
if "PDF" in f_type[3]:
types["PDF"] = True
if "ELF" in f_type[4]:
types["ELF"] = True
if "ASCII" in f_type[5]:
types["TXT"] = True
except:
pass
for k, v in types.items():
v = "🟩" if v else "🟥"
print(f"| {k}: {v} ", end="")
print("|")
if _4bflag and all(types.values()):
print("FLAG: ctf4b{****REDACTED****}")
else:
print("FLAG: No! File koime!!")
ということで、コマンド
file -bkr ファイル名
を実行した結果について、
- 1 行目に
JPEG
が含まれている - 2 行目に
PNG
が含まれている - 3 行目に
GIF
が含まれている - 4 行目に
PDF
が含まれている - 5 行目に
ELF
が含まれている - 6 行目に
ASCII
が含まれている -
4b
がどこかに含まれている
をすべて満たすようなファイルを作れればクリア。ただし、ファイルはすべて英大文字と改行のみで構成されている必要がある。
考察
polyglot4b と同じ考え方でいけそう。
ソースコードのリポジトリの magic ファイルがまとめてあるディレクトリ から、全英大文字からなるマジックバイトを grep する。
$ grep -E "(0x)?[0-9A-F]+\s*string\s*[A-Z]+(|\s+.*)$" ./*
./aes:0 string AES
./amanda:>>23 string X
./amigaos:0 string SMOD Future Composer 1.3 Module sound file
./amigaos:#26 string BPSM Brian Postma's Soundmon Module sound file v3
./amigaos:0 string RDSK Rigid Disk Block
./amigaos:0 string DOS
./amigaos:0 string KICK Kickstart disk
./amigaos:0 string LZX LZX compressed archive (Amiga)
./android:>1024 string LOKI \b, LOKI'd
./animation:0 string MOVI Silicon Graphics movie file
./animation:>8 string XAVC \b, MPEG v4 system, Sony XAVC Codec
...
結構いっぱい出てきた。この中から都合がいいものを頑張って探す。
一番都合が良さそうなのはこれ。
#------------------------------------------------------------------------------
# $File: pmem,v 1.4 2021/04/26 15:56:00 christos Exp $
# pmem: file(1) magic for Persistent Memory Development Kit pool files
#
0 string PMEM
>4 string POOLSET Persistent Memory Poolset file
>>11 search REPLICA with replica
>4 regex LOG|BLK|OBJ Persistent Memory Pool file, type: %s,
>>8 lelong >0 version: %#x,
>>12 lelong x compat: %#x,
>>16 lelong x incompat: %#x,
>>20 lelong x ro_compat: %#x,
>>120 leqldate x crtime: %s,
>>128 lequad x alignment_desc: %#016llx,
>>136 clear x
>>136 byte 2 machine_class: 64-bit,
>>136 default x machine_class: unknown
>>>136 byte x (%#d),
>>137 clear x
>>137 byte 1 data: little-endian,
>>137 byte 2 data: big-endian,
>>137 default x data: unknown
>>>137 byte x (%#d),
>>138 byte !0 reserved[0]: %d,
>>139 byte !0 reserved[1]: %d,
>>140 byte !0 reserved[2]: %d,
>>141 byte !0 reserved[3]: %d,
>>142 clear x
>>142 leshort 62 machine: x86_64
>>142 leshort 183 machine: aarch64
>>142 default x machine: unknown
>>>142 leshort x (%#d)
>4 string BLK
>>4096 lelong x \b, blk.bsize: %d
>4 string OBJ
>>4096 string >0 \b, obj.layout: '%s'
>>4096 string <0 \b, obj.layout: NULL
必要な部分だけ抜き出すと、こんな感じ。
0 string PMEM
>4 regex LOG|BLK|OBJ Persistent Memory Pool file, type: %s,
>>8 lelong >0 version: %#x,
(中略)
>4 string OBJ
>>4096 string >0 \b, obj.layout: '%s'
>>4096 string <0 \b, obj.layout: NULL
ということで、PMEMOBJ
から始めれば 4096 文字目 (0-indexed) からの内容が出力される。同時に、8 文字目 (0-indexed) から 4 バイト読み込んで整数として解釈した結果が 16 進数として出力される。
よって、文字コード 0x4b
にあたる文字は K
なので、
PMEMOBJ + (任意の1文字) + K + (任意の 4087 文字) + JPEG + 改行 + ...
みたいなファイルを作ってあげればいい。適当に python で実装した。
solver
$ python3 -c "print('PMEMOBJ' + 'A' + 'K' + 'A' * 4087 + 'JPEG\nPNG\nGIF\nPDF\nELF\nASCII\nQUIT')" | nc localhost 31417
____ _ _ _ _____ _ _ _ ____
| _ \ ___ | |_ _ __ _| | ___ | |_ | ____|__| (_) |_ ___ _ __ |___ \
| |_) / _ \| | | | |/ _` | |/ _ \| __| | _| / _` | | __/ _ \| '__| __) |
| __/ (_) | | |_| | (_| | | (_) | |_ | |__| (_| | | || (_) | | / __/
|_| \___/|_|\__, |\__, |_|\___/ \__| |_____\__,_|_|\__\___/|_| |_____|
|___/ |___/
----------------------------------------------------------------------------
>> ----------------------------------------------------------------------------
| JPG: 🟩 | PNG: 🟩 | GIF: 🟩 | PDF: 🟩 | ELF: 🟩 | TXT: 🟩 |
FLAG: ctf4b{y0u_54y_p0ly6l07 h45_n07h1n6_70_d0_w17h_17?}
file -bkr
コマンドを打ったときの結果も載せておく。
Persistent Memory Pool file, type: OBJ, version: 0x4141414b, compat: 0x41414141, incompat: 0x41414141, ro_compat: 0x41414141, crtime: *Invalid time*, alignment_desc: 0x4141414141414141, machine_class: unknown (65), data: unknown (65), reserved[0]: 65, reserved[1]: 65, reserved[2]: 65, reserved[3]: 65, machine: unknown (16705), obj.layout: 'JPEG
PNG
GIF
PDF
ELF
ASCII
'
- , ASCII text, with very long lines (4100)