ひとこと
べんりなつーるがよのなかにはあるものですな すばらしい
問題
情報
ジャンル: Rev
難易度: Medium 4.5
問題リンク: https://alpacahack.com/daily/challenges/typhoon-pythoon
問題文
台風がプログラムを遠くへ飛ばしてしまいました。 どうやらフラグは台風の目の中にあるようです。
このファイルは Python 3.10 でコンパイルされています。import や実行には Python 3.10 を使用してください。
初心者向けヒント
-
.pycファイルにはコンパイル済みのPythonバイトコードが含まれます - モジュールとしてimportし、
dirやdis.dis()で調べてみましょう。
唐突ですが初心者向けヒントを無視します
実は世の中にはpycdcというコンパイル済みPythonはデコンパイルしてくれる素晴らしいソフトがあります。これを用いてデコードすることにすると
# Source Generated with Decompyle++
# File: server.pyc (Python 3.10)
from pathlib import Path
EYE_DATA = bytes([
207,
197,
236,
194,
221,
208,
195,
183,
175,
190,
183,
178,
145,
162,
132,
159,
119,
90,
105,
94,
127,
126,
83,
73,
105,
37,
44,
46,
57,
45,
25,
11,
6,
26,
255,
237,
247])
def is_inside_the_typhoon_eye():
'''Check whether the observation server is in the only calm place.'''
current_place = Path(__file__).resolve().parent.name
required_place = bytes([
101,
121,
101]).decode()
return current_place == required_place
def reverse_the_wind(observations = None):
'''Return observations to their state before the wind moved them.'''
location = Path(__file__).resolve().parent.name.encode()
restored = []
for position, value in enumerate(observations):
place = location[position % len(location)]
wind = position * 7 + 41 + place & 255
restored.append(chr(value ^ wind))
return ''.join(restored)
def main():
if not is_inside_the_typhoon_eye():
print('Hint: Move this file into the eye of the typhoon.')
return None
answer = None('What was inside the eye? > ')
if answer == reverse_the_wind(EYE_DATA):
print('The typhoon is gone. Correct!')
return None
None('The wind is still too strong...')
if __name__ == '__main__':
main()
return None
こういうコードが出てきます。printfがNoneになってたりと色々おかしいですが結構いろいろ見れます。
第一段階
if not is_inside_the_typhoon_eye():
print('Hint: Move this file into the eye of the typhoon.')
return None
def is_inside_the_typhoon_eye():
'''Check whether the observation server is in the only calm place.'''
current_place = Path(__file__).resolve().parent.name
required_place = bytes([
101,
121,
101]).decode()
return current_place == required_place
とのことです。どうやら実行ファイルの親ディレクトリが
bytes([
101,
121,
101]).decode()
にいればいい、と...。これは解読するとeyeになるのでこのディレクトリに入れてあげます。
第二段階
if answer == reverse_the_wind(EYE_DATA):
print('The typhoon is gone. Correct!')
return None
def reverse_the_wind(observations = None):
'''Return observations to their state before the wind moved them.'''
location = Path(__file__).resolve().parent.name.encode()
restored = []
for position, value in enumerate(observations):
place = location[position % len(location)]
wind = position * 7 + 41 + place & 255
restored.append(chr(value ^ wind))
return ''.join(restored)
locationは第一段階で判明した通りb'eye'であり、復号化された文字の$i$文字目を$A_i$とし、observations=EYE_DATAをB_iとすると
- $\text{place} = \text{location}_{i\bmod |\text{location}|} = \text{"eye"} _{i\bmod3}$
- $\text{wind} = 7i+41+(\text{place} \& 255) = 7i+41+(\text{"eye"} _{i\bmod3} \& 255)$
より
$$A_i=B_i\oplus (7i+41+(\text{"eye"} _{i\bmod3} \& 255))$$
よって
>>> EYE_DATA = bytes([ ... ])
>>> for i, value in enumerate(EYE_DATA):
... place = location[i % len(location)]
... wind = (i * 7 + 41 + place) & 255
... restored.append(chr(value ^ wind))
こんな風に入れてあげれば勝手にflagが出てきます
出てきたフラグ
Flag: Alpaca{dis_is_the_eye_of_the_typhoon}