polyglot4b2 (SECCON Beginners CTF 2023) WriteUp

# はじめに
この記事は [1日1CTF Advent Calendar 2024](https://qiita.com/advent-calendar/2024/1-day-1-ctf) の 7 日目の記事です。

なお、この記事は [先日の polyglot4b の WriteUp](https://qiita.com/hiikunZ/items/3376a1195fbc1b3cb7d3) の内容を前提としております。まだお読みになっていない方は先にそちらをどうぞ。

# 問題
polyglot4b2 (問題出典: SECCON Beginners CTF 2023)

> より美しいpolyglotを作れるエディタを開発したよ！
> やっぱりpolyglotは簡単に作れる？

リポジトリ: https://github.com/SECCON/SECCON_Beginners_CTF_2023/tree/main/misc/polyglot4b2

## 問題概要

```py
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!!")
```
ということで、コマンド

```sh
file -bkr ファイル名
```
を実行した結果について、

- 1 行目に `JPEG`が含まれている
- 2 行目に `PNG`が含まれている
- 3 行目に `GIF`が含まれている
- 4 行目に `PDF`が含まれている
- 5 行目に `ELF`が含まれている
- 6 行目に `ASCII`が含まれている
- `4b` がどこかに含まれている

をすべて満たすようなファイルを作れればクリア。ただし、**ファイルはすべて英大文字と改行のみで構成されている必要がある。**

## 考察

polyglot4b と同じ考え方でいけそう。

[ソースコードのリポジトリの magic ファイルがまとめてあるディレクトリ](https://github.com/file/file/blob/master/magic/Magdir/pmem) から、全英大文字からなるマジックバイトを grep する。

```console
$ 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
...
```
結構いっぱい出てきた。この中から都合がいいものを頑張って探す。

一番都合が良さそうなのはこれ。

```pmem

#------------------------------------------------------------------------------
# $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

```console
$ 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)
```