Jumbled snake (150)

ファイル print_flag.py.enc および jumble.py が与えられた。

jumble.py には、ランダムな対応関係を生成し、入力の文字をそれぞれ対応する文字に置換して出力する処理が書かれていた。
print_flag.py.enc には、1行目に平文の一部、2行目以降に暗号文が書かれているようだった。

まず、平文の _quick_brown_fox_jumps_ の部分に対応する暗号文を探すため、サクラエディタで正規表現 (.).....\1.....\1...\1.....\1 を検索した。
その結果に基づき、CyberChef の Substitute で用いる以下の対応関係が得られた。

Files print_flag.py.enc and jumble.py were given.

jumble.py had a program to generate a random set of substitution rules and output the input with each characters replaced according to the rules.
In print_flag.py.enc, the first line looked like some part of the plaintext and the other lines looked like a ciphertext.

To begin with, I searched for a regular expression (.).....\1.....\1...\1.....\1 using Sakura Editor to find a part of the ciphertext corresponding to the part _quick_brown_fox_jumps_ in the plaintext.
Using the result, I created this rule for "Substitute" in CyberChef.

Plaintext: y8Pp X\t%DSlXGEU\x0beX@U$Xz%Mo\XUa EXPp XWtk\x0cXYU{
Ciphertext: {'the_quick_brown_fox_jumps_over_the_lazy_dog

さらに、復号結果がPythonのプログラムとして成り立つように対応関係を加えていくと、以下のようになった。

Then, I added rules to make the decryption result be a valid Python program. This is the result.

Plaintext: y8Pp X\t%DSlXGEU\x0beX@U$Xz%Mo\XUa EXPp XWtk\x0cXYU{4bZ.=Aw^g/J]>Rm:h~_63j?V,
Ciphertext: {'the_quick_brown_fox_jumps_over_the_lazy_dog/\n #!64():=".12357809[]}N

この時点で、復号結果は以下のようになった。

This is the result of decryption using this rule.

#! /usr/bin/env python3 import base64 coded_flag = "c21jdF57ss91blKhdjNs5O1fd7gzn3NuN2shf`==" def reverse(s): return "".join(reversed(s)) def check(): """F+ _f5}I_7|0_17s+_B&N)K_n+(_NO+1q_CQ*)`_7|0""" assert decode_flag.__doc__ is not None and decode_flag.__doc__.upper()[2:45] == reverse(check.__doc__) def decode_flag(code): """{'the_quick_brown_fox_jumps_over_the_lazy_dog': 123456789.0' 'items':[]}""" return base64.b64decode(code).decode() if __name__ == "__main__": check() print(decode_flag(coded_flag))

この復号結果から、関数 check の冒頭部分に the_quick_brown_fox_jumps_over_the_lazy_dog を反転させて大文字にしたものが入るはずであることが読み取れる。
これを用いて対応関係を加えると、以下のようになった。

This result is suggesting that the first part of the function check should contain what can be obtained by reversing the_quick_brown_fox_jumps_over_the_lazy_dog and converting to upper-case letters.
I added rules based on this. This is the result.

Plaintext: y8Pp X\t%DSlXGEU\x0beX@U$Xz%Mo\XUa EXPp XWtk\x0cXYU{4bZ.=Aw^g/J]>Rm:h~_63j?V,F+\nf5}I7|01sOB&N)Kn(OqCQ*`
Ciphertext: {'the_quick_brown_fox_jumps_over_the_lazy_dog/\n #!64():=".12357809[]}NGODYZALEHTRVOSPMUJXFWBKCIQ

復号結果は以下のようになった。

This is the result of decryption.

#! /usr/bin/env python3 import base64 coded_flag = "c2RjdGZ7VV91blJhdjNsZWRfdEgzX3NuM2shfQ==" def reverse(s): return "".join(reversed(s)) def check(): """GOD_YZAL_EHT_REVO_SPMUJ_XOF_NWORB_KCIUQ_EHT""" assert decode_flag.__doc__ is not None and decode_flag.__doc__.upper()[2:45] == reverse(check.__doc__) def decode_flag(code): """{'the_quick_brown_fox_jumps_over_the_lazy_dog': 123456789.0' 'items':[]}""" return base64.b64decode(code).decode() if __name__ == "__main__": check() print(decode_flag(coded_flag))

ここに現れた文字列 c2RjdGZ7VV91blJhdjNsZWRfdEgzX3NuM2shfQ== をBase64デコードすることで、flagが得られた。

I obtained the flag by Base64-decoding the string appeared here: c2RjdGZ7VV91blJhdjNsZWRfdEgzX3NuM2shfQ==.

sdctf{U_unRav3led_tH3_sn3k!}

SDCTF 2023