Satoooonの物置

CTFなどをしていない

Pythonの識別子におけるUnicode正規化(NFKC)とpyjail

Unicode正規化とは?

Unicode正規化は「コードポイントや字体が異なる同じ意味の文字は等価とみなすように比較したい!」というときに使うものです。例えば、正規化形式の一種であるNFKCを使うと「ア」と「ア」が等価になります。

詳しくはMDNを読んでください。

developer.mozilla.org

Pythonでは

Pythonでは識別子にASCII外の文字を使用することができます。

入力1 = input()
print(入力1)

Pythonソースコードを解析するとき、全ての識別子はNFKCに変換されます。

docs.python.org

そのため、このようなコードも動きます。

ア = input()  # 全角
print(ア)  # 半角

このように字体が異なっていてもNFKC的に等しいなら同じ変数として使用できるんですね。そのため、次のようなコードも動いてしまいます。

# Normal
print("Hello world")
# 全角
print("Hello world")
# bold italic
𝙥𝙧𝙞𝙣𝙩("Hello world")
# 上付き文字 下付き文字
ᵖʳⁱⁿᵗ("Hello world")
ₚᵣᵢₙₜ("hello world")
# いろいろ
𝖕𝖗𝖎𝖓𝖙("Hello world")
𝓹𝓻𝓲𝓷𝓽("Hello world")
𝕡𝕣𝕚𝕟𝕥("Hello world")
𝚙𝚛𝚒𝚗𝚝("Hello world")

Pyjailでは

このような面白い挙動がCTFではどう出題されているのでしょうか?

Filter bypass

code = input()

for c in code:
    if c in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890":
        print("deny")
        exit()

exec(code)

アルファベットと数字が全て禁止されているpyjailです。ASCII内で考えると記号しか使えないように思ってしまいますが、先ほどのテクニックを使うと簡単にbypassできますね。

exec(input())

このテクニックは日本のCTFでは見たことないですが、ところどころで使われています。

code golf

調べていて見つけたWriteup(polygl0ts)で面白い悪用方法があったので紹介します。

b01ler CTF 2021 の pyjailgolf3 という問題です。

from collections.abc import __builtins__

bi = __builtins__
del bi["help"]

line = input(">>> ")

flag = "THIS_IS_FLAG"

if len(line) > 10:
    raise Exception()

try:
    eval(line, {"__builtins__": bi}, locals())
except:
    pass

b01lers-ctf-2021/pyjailgolf3.py at main · b01lers/b01lers-ctf-2021 · GitHub

10文字以内、help関数なしという制限のpyjailです。変数にフラグが格納されているのでprint(flag)をしたいところですが、残念ながらこれは11文字です。

想定解では、NFKCの挙動を使います。ligature(合字)をNFKCで正規化すると複数の文字に分解されます。(例:「㍍」→「メートル」) そして、Unicodeにはというfとlの合字が存在します。これを使えばflagflを一文字に短縮することができます。

つまり、print(flag)が正解です。シンプルですが、競技中にこの解法を閃くのはかなり難しいと思います。

余談ですが、合字テクが使える名前の関数があるなら一般的なコードゴルフでも短縮が狙えるかもしれませんね。あるかは調べてませんが……