リバースエンジニアリング専門のCTF Flare-On Challenge。今年で12回目の開催で、自分は6回目の参戦である。(去年はOSCPに備えていたため不参加)
今年は2問目でリタイアしてしまった。
1問目の解法及び2問目の四苦八苦をまとめておく。
主催者公式のWrite Upはこちら。
01 Drill Baby Drill!
以下は解析対象のファイル群。
Date Time Attr Size Compressed Name
------------------- ----- ------------ ------------ ------------------------
2025-09-17 23:35:17 DR..A 0 0 fonts
2025-09-17 23:35:17 DR..A 0 0 img
2025-09-19 09:58:30 ....A 11426 269216 DrillBabyDrill.py
2024-09-25 13:39:48 ....A 4367 fonts/SIL Open Font License.txt
2024-09-25 13:39:48 ....A 153116 fonts/VT323-Regular.ttf
2025-09-17 14:54:01 ....A 2311 img/bear.png
2025-09-17 14:33:03 ....A 800 img/boulder.png
2025-09-17 13:08:14 ....A 480 img/dirt.png
2025-09-17 14:46:26 ....A 3426 img/drillbaby.png
2025-09-17 13:51:30 ....A 555 img/drillbit.png
2025-09-17 13:52:51 ....A 501 img/drillshaft.png
2025-09-17 13:14:08 ....A 500 img/emptyholebottom.png
2025-09-17 13:11:45 ....A 573 img/intermediate.png
2025-09-17 13:15:53 ....A 477 img/shaft.png
2025-09-17 13:03:49 ....A 368 img/sky.png
2025-09-17 13:05:29 ....A 410 img/surface.png
2025-09-17 14:26:14 ....A 399 img/surfacehole.png
2025-09-17 22:15:07 ....A 220665 img/victory.png
2025-09-17 23:27:33 ....A 414 README.txt
2025-09-19 10:03:31 ....A 17146594 14255840 DrillBabyDrill.exe
------------------- ----- ------------ ------------ ------------------------
2025-09-19 10:03:31 17547382 14525056 18 files, 2 folders
以下はREADME.txtの中身。
└─$ cat README.txt
This game is written in PyGame. It is about a baby trying to drill to recover its lost teddy bears.
The source code is provided, as well as a runnable pyinstaller EXE file.
To launch the game run DrillBabyDrill.exe on a Windows computer. Otherwise, follow these basic python execution instructions:
1. Install Python 3
2. Install PyGame ("pip install pygame")
3. Run the game: "python DrillBabyDrill.py"
DrillBabyDrill.pyを眺めていると、フラグを復号するためのGenerateFlagText関数を発見。
def GenerateFlagText(sum):
key = sum >> 8
encoded = "\xd0\xc7\xdf\xdb\xd4\xd0\xd4\xdc\xe3\xdb\xd1\xcd\x9f\xb5\xa7\xa7\xa0\xac\xa3\xb4\x88\xaf\xa6\xaa\xbe\xa8\xe3\xa0\xbe\xff\xb1\xbc\xb9"
plaintext = []
for i in range(0, len(encoded)):
plaintext.append(chr(ord(encoded[i]) ^ (key+i)))
return ''.join(plaintext)
以下はGenerateFlagText関数の呼び出し箇所。
if bear_mode:
screen.blit(bearimage, (player.rect.x, screen_height - tile_size))
if current_level == len(LevelNames) - 1 and not victory_mode:
victory_mode = True
flag_text = GenerateFlagText(bear_sum)
print("Your Flag: " + flag_text)
以下はGenerateFlagText関数に渡されるbear_sum変数が定義されている箇所。
if player.hitBear():
player.drill.retract()
bear_sum *= player.x
bear_mode = True
どうやらゲームのプレイヤーキャラの座標情報がフラグの復号鍵としてGenerateFlagText関数に渡される模様。座標情報は恐らく整数型なので、総当たりできそう。
以下の簡単なPythonスクリプトを書いて鍵を総当たりしてみた。
def GenerateFlagText(sum):
key = sum >> 8
encoded = "\xd0\xc7\xdf\xdb\xd4\xd0\xd4\xdc\xe3\xdb\xd1\xcd\x9f\xb5\xa7\xa7\xa0\xac\xa3\xb4\x88\xaf\xa6\xaa\xbe\xa8\xe3\xa0\xbe\xff\xb1\xbc\xb9"
plaintext = []
for i in range(0, len(encoded)):
plaintext.append(chr(ord(encoded[i]) ^ (key+i)))
return ''.join(plaintext)
mysum = 0
while True:
#print(mysum)
flag = GenerateFlagText(mysum)
if '@flare-on.com' in flag:
print(flag)
break
else:
mysum += 1
$ python3 drillbaby-solver.py
drilling_for_teddies@flare-on.com
フラグはdrilling_for_teddies@flare-on.com
02 project_chimera
Pythonスクリプト project_chimera.pyを解析する問題。
以下はスクリプトの内容。
└─$ cat project_chimera.py
# ================================================================= #
# == PROJECT CHIMERA - Dr. Alistair Khem's Journal == #
# == -- EYES ONLY -- == #
# ================================================================= #
#
# Journal Entry 734:
#
# Success is within my grasp! After years of research, I have finally
# synthesized the two key components. The first, my 'Genetic Sequencer,'
# is stable and ready. It's designed to read and execute the final,
# most crucial part of my experiment: the 'Catalyst Serum.'
#
# The Catalyst is the key to creating a true digital lifeform.
# However, it is keyed to my specific biometric signature to prevent
# my research from falling into the wrong hands. Only I, the Lead
# Researcher, can successfully run this final protocol.
#
# If anyone else finds this, I urge you: DO NOT RUN THIS SCRIPT.
# The results could be... unpredictable.
#
# - Dr. A. Khem
#
import zlib
import marshal
# These are my encrypted instructions for the Sequencer.
encrypted_sequencer_data = b'x\x9cm\x96K\xcf\xe2\xe6\x15\xc7\xfd\xcedf\x92\xe6\xd2J\x93\xceTI\x9b\x8c\x05&\x18\xe4\t\x06\x03/\xc2\xdc1w\xcc\x1dl/\x00_\x01\xe3\x1b6\xc6\xe6\xfa\x15\x9a\xae\xd2\xae\xba\xae\xd2/Q\xf5\x0b\xbc\xd1\xa4JJVUV\xdd\xa5\xca\xae\xab\xf2\xceM\x89\x9ag\xe1\xf3\x9cs~\xe7\xfc\x8f\x1f\xc9\xd6\xf3\x1d\xf0\xa3u\xef\xa5\xfd\xe1\xce\x15\x00|\x0e\x08\x80p\xa5\x00\xcc\x0b{\xc5\\=\xb7w\x98;\xcf\xed]\xe6\xaep\x87y\xe3\x0e \xde\x13\xee~q\xf5\xa2\xf0\nx\xee\xbf\xf1\x13\x1f\x90\xdf\x01\xfeo\x89\xaf\x19\xe6\xc1\x85\xb9\x92\x7f\xf53\xcc\x83\xd7\xcc[\x17\xe6\x8e\xfc\xfe\xcf0o\xbdf\xde~\xae}\xef\'\xdaw\xe5\xdf\xfcL\xcd-\xf9\xee\x17/\xbd/\xee\xbc\xac\x7f\xef\x12}\xefU\xf4\n\xd8^\xc1\xf7\xff}\xbb%\xad\xbf\xbe\t\x00\xbc\xf7 \x06[\xe9\xb8\x0f\x89MU\xb0\xbbc\x97\'E!\x0ea<\t\xfa\xc7\x81aG\xf3\xac\x88\xca\xe1\xe0\x12a\xce\x1b\x18\xa5v\xce59:\x85\xd5Y\xb5)G\xac\x92\xbc\xdbB8Y\xeb\\cc\xeff%\xf6\xcb>\xb5\x10\xdc\xce\x15"\x16\x8f\xcb\xc85\\\xc2\xb4b\xfa\x94\xc1\xcb\xabF\x0c\xd3\x95M\xde\xf2r\x0c\xb6_\x11\xc9\xfd!ed\x9bX\x8e\x13\xb9q ]\xd8U\r\xb361\x0bT\x83B\xb3K8\x8ay+\x95AC\xab\x8a\xd16\xa2\xc0\xb9\xb9\x0c\x06b\xce\xbexR \xaa\xe9\x14\xdb\xb6G.\xd2sj\\$\xf7\xabh\xe7\x10EF+\x08\xcd*y\xf7x<lH\xd48\r\xaa\xd7s84\xf0i=4R\x9c\x1d\xdd\xeb\xfa\x98@\xfc+\xaf\x11:b\xa0\xb2E u\x1f\xaa\x08\xe9q0\x12\xc0[\xfb\x80\x15\xaa#\xca\xf2p\xcc7*\xa3z\xcd\x11;&\xb9\x8b\xee\xa1\x12\x92\xcc\x12\x93\xbd\x10\xac\xaa}%\x8e\xe8q\xdf\xb1\xb5\x87l\x8e\x85\x1d\xb4\xdb\x08\x0cr]*\x10O\xac\x83!|\x9c\xcf\xecT\xa5U\xa4\x12\x870\xb73&\xbb\xb5#o\'}\xa1\xce\xc1($\xb61\x01\xa1\xd6\x8b\x10=\x93\x97\x13\xc8\x01\xc7\x10\xea\xdaMr\x831\xd7>\x7f` \xc6\'\xe3\x12\xb7E\xb5H2X\xc6\x87\xc5\x9c\xb4Z\x8c\xe7h:\x94M\x11\xcbE\x14l\x9eL\xd5\x82X\xc9\x9d\x06m\x97\r\x05\x92\xa5\x9d-\x18+R\xd1\xa2M<\x0b\xb6V\x9a\xc0\xc0]|3\xc7l\xdf\xccPU\x8dm\x8a\x0e\xd7\x0fuk\xdc6\xe3\x97\xd885\xf2\x98i\xa6\x83\r\x08\x9f}8)\x8cE\xd0\'D1\xa4QS\nM\x82\xc6\x10\xa9L\xdbTU3\x1cu\xab\x9fTf\xba\x96\x06\xf5\x8c\xdf[\xaf\xb0\x90\xba!\x15}\xc3$i\xb8\x18\x14c\xb6\x13T\xe9X\x83\xcc\x87\xe9\x84\x8f]r#\x83\xc9*\xf3To\x81\x83\xb5\xec\xfaP(_\xc7\x88),\x1b\xa0\x82\xb9\x04\xed\x9f\xc7\xb3^E\xc9a\xc7|B0\x1a\x01\x19\x16\x1b\xfb\xcd\x90\xe7\xb6M7:\xd9sh\x04&\xb3\x0e{\x12\x8d\xde5#\xe9\xbe\xe1\x84\xf6H\xcd\xc0,\x91\xcc\xc6 9\x05-\xa0Q>\x94\xea\xf4"\xa2#gC\xa7<\xb8Xp6\xde\\\x99f\xadZ\xd9\xab\xbe\x92\x9e+\xe7#\x9e\x10)%]\xf0$l:\x87\x84\'\xc2\x1f\xe1j#\xb6$6\xf3\xfc\xb6\xb6\xc9\xed\xf3\th\xb0\xa2B\xfdY\x00\t\xe6\x96\'r\xe4\xbb\x1cK>\xc3\xc6\x1c\x91\xb88\xe6\xae\xbb\x083y0\x86\xc5+#%76\xcb\xd8l#G\xe8\xb5\xa8GB\xbe\xc01\x19M$\xe3Z\xad\x14\x17\xe7\xf1\x8dLP\x8e\xe3\xb6G\xa3]1\x10\xc1\xab\x1b\xa6\xe7Q\xaa\r\xbf\x12\xc8\xd8\xde$Q^Hu\xa9Q4\x86\\\xc0\xa4\x1a[\x07\xcc\xb5OL\x7f\x8c\xf4R\x18\xb5\x8f\xa0\xeb\x95\x88\xb7\xd0\xa5S\xf6\xce\xf2\x8cf_\x8b\x1b6r\x8a%\xb1\x82k\xf2\x15t\xdf\x99\xed\x9b\xc9r?\x9a\xcd\x0b\xab5d\xed\xdde?Y\xdc\xb2\xf9%\xbcI\xf3}\xd3\x93\xa2\x9aY\xbe\x83\x0c\x19\xa6\x86\xb2\xbb\xf9\x1e-J\'\xc9\x91\xfc\xaa@/\'<Q\x98N=;S\xdc\x0cl\tE\xaa\xf1b\xa5\xber\x13|\xbc)f\x02\x0b\xd26\x13\x17-\x1d\xce\xa19\xb5\xc2\xd5\xc1\x98g\x89\x0b\xc1\x8eJ\xc9\xfa@1s|\xaa\x8b\\\x13\x12\xb1\xd1\xbc\xfd6\x94a\xb804E\x92N)\xcc\xc4\xf9Sg\x0ev\x06\x06\x94-\xc5\x05\x7f\'Y]g5%\x82.\x1c~L\x16\xfa}S\x0e\xb4F0GT\xd2yZ\xe9xiu1\xef\r\xc3\x9d\xa2k\x16\xac:\xd9\xd7\t\xd5"\x17\xd2)\x89T\x1b\xe5\xa0\xe2\xcd\x9e\xacf\x91\xd7\x88\n]\xe5d.\xd3@,G\x87\xd2$I\xc7B\x9dZt\x1anP~\x9f\xb7P\x92\x02#?\xaf\xc4\xd7\xd7\xa1D$\x91\xedT\x82\xe9$\xb8\xaccr\xb3\xbfhur\xc7]3+\xf4\x82\x8e\xba\xc42\xdd\xb5\xb5\xaaZ~rm3\xa6\x9fpd|\xe7R\xecP_[`\x0c?\x0e\xda\xd1\xb4F\x1a\xe8LZ\x8a\x16\xd6\x0f\xec\x84=\x1c\x9b#\xe5\x12\x96&{\x9d\xd6\xb1\x1bH\xa0{~\xba\x04SE\xa4x\xe4X\xd2\x8bJ\xf6\x904\x07\xc5MyA\x0f\xa9\x11\x9d\xafb\xd1\xd8^-\x94\xa7\xf6\xd2f$\x83\x84s\xb8\xbb\xe5R\xd6\x91\xdb\x12\xfe\xe2\x86\x91T\xa3\xbb\xdc\xe8X\xa19\x0b\x96\x02\x91\x02$\xc5<\x19u?\xcb\xf61\x1b)\xe3\'5\x7fr\xca\xd4,I\x0e\x9b\xa5\xa2\xec\x93\xa28\xbc*\xa3\x9e\xb8\xab\xd0B\x89\xe8L\xe4J\xd7\x0e\x88\xbe\xd2@\xed\xa05\xbcl\x1c1\xaf\xbb\xcanY\xa5\xe0w\xe1\x1eR\xaa\x12\xb3\x8e\x18\xac\xba\xb9n\xa3\xd6\xee\xaa\xd9"\xe5\xfa\xd6A|\x1em\x84Z\xdd\x1aN\xe0\xbcs\x8c)Z,#\xba\x8d\xca\xf6\x98\x98\x08\x04f\xec\xd0\xb8\xde\xf0\x9f\x88\xe9\x9e\x9d\x12\x88\xa6\xc73\xd3(l\x14\t\x83\xa4\xfdHl\xc8\xd62\x851^K\xf8\xcb$\x98Kj\xd3v\xbf]d\xf2DrD\xa6\xa3\xcb\x14\xabZS{\xbb\xc5]\x95\xa1\x85lkv\x08a{t\xe0\x0f\xa0\xedr\xa3\x9b\x9eGFT\x86eF\x1d\xe9\x14Kdd\xa4d\xa9\x8dqyS\xd5\xcc\xd9B\xd0\x9b\xe1\xa3\x89\xda\xbe#\x95\x0f\xae\x8ezy\x86\x90]\x8f6\xa6\x02\x98\xbd\xcao3\xe8\x8a\xf6b\xb8\xbck\xe6\xe7T\x0eN\xee\xda\x92\x1b\t\xb8\x03p8\xf2z\xa4\x12\xebk\x16ZR\xb72\xd4BPly\xcd\xb2]\'!\xd0\x198\x0e\xdamP+W\x08\xce\xb3\x0c\xd6\\\xfa\x10\x9e\xa7\x97\xd4\x9e\xdcC\xe0\xb4*m\xda\xd4\xa1\x97\x15A-\x17\xa9nO\x1e\xbe>4a\x88/\xb9{\x95\xee\x95\xe5\xc4\x1c\xadL:1QX\xce\xed\xf2\x12\x8e0\x89\xd9\xc8\x98\x9e\xd4\xda\xae\x1c\xc7\xd4\xb8\x1f\xac\x8du?\x18\x16\xc4\xa9\xda\xcaD\xaa\xc5\x1d?Lz\xbb\x9diV\xd2\x17tE\x91\xa1\xfd\xe5\x87\x9c\xf6,\xfa\x87zz\x83L\xe9\n\xdc\xee\xbb\x1e\xa9k\xfb\x0f\xd9\x9cU\xef{\xdac\x98\xd7X\xf0\x90\xb0\x06\xdb\x01\xd2\\\xe7\xdc\xf6\xb1\x99v\x0e\x05\x1e\xb5\xb0I\xbd\x9a\x98+Fx{\x18\xe4\x88\x9a\xb7\x10\xf6b\xady\xec\x94\xb5e\x04\xa4\x91\xe8\x9a\xd8V\xbd4T\'\n$f\xc7\x14<\x90\x91x\xa7;\x91\x8a\xe3CP\x90\x8b\xd5Z\xd4\x06\xd39\x1fJ&\x16ku\x8fGt\xc4\xd6\x92\x08|\x9d\x18{\x8cj[\xd8\x0f\x9d\xed\xae2AG\xad\xed\x8a\xf1V\xe0\xa5\x97\xa2\x8a\x88\xcb\x0fXi&s)\xd2\xb3\x00\x83-MC\xfa2\xc2\x13:\x17\xf4\x83\xfe|k\xc4\xa6K\xebB2\x8c\x16+{h\\\xad\xe8)\x1eJ\x9aI\xd9Z\x93ht\xd5\x9b\x0c\xc6\xa5T\x8e\xf3\xf2\xd1\xd6<:\xcaH4\x08\x8d7\x02%\x11\xe9(-\x81f\xa54\xc6\xd9\xd24\x1f\xe0\xc4@#\xe5/\x94\xfc\x10B\xe0\x19\x18\xe2B\xde|\r>HaF.C\xd5\x9e\x13d\xae)\xbe0\x95\x830g,\xf1x\x82\xa6F\xc4R`\x87q\xd5)O\x96\x8b\xd6\xe5S\xa3\xb7\xaa\xaf\xe0[\xb8~\xc2\xc8\xc5IO\xe6x`\xbbn\xce\xea\xaaI0,B"\xccb\xb9\r\xa3U\x06\xed\x8dS`3\x9c\xaf\xb5\xa8\xe8\xfa\x0eB\x10\xe4I\x81U\x16\x9c\xc9\xae\x17\xda\xecIY\xd4\xc4\xf5\x82\x7f\xd2\x13W\xb6\xa8\xf1\xa2\xf9\xe4B\xec>.\x8a\xbc.\xdc\xe6yv\xcd*[k\xfd\xa4H\xe6\x9eXk\x93\xd5\x84\xa7O\x9f\xee>\xeam\xb5\xf5\\\xb4\x16\xbb[\xa8\xf0\n\xea\x89\xa6\xad^\xf2\xf0/\xcf\xf79\xd6\x12c\xd8\xf9\x8d\xddE\xec\xfc@eMk\xce*\xe7{\xeb\xad!Z\xe7\xc7\x17-]\x10\x85\xc9\xab\xfe\x93\x17\xbd\xcf\xf7\x0cs\xa1\xad\xcfoq\xd7Q\xe1v\x06\xf1\xfc\x90\xd7U\xc3\x14-\xebG\xf4\xf9\x17\xb7\xc9\x17\xe1\xf3\xe3\x97\xbd\x95\x0b0{\xf1:\x93\xe7\x95\xf7\x14\x9d\x15\xac\xf3\xfb\xaf5n\xa3\x13\x9d\x93E~}~\xa7dk\xfcz\xa1k\xfd\xcb@\xe7\x073E\xe7X\xc5:\x7f\xf8\x1a^h\xb7\xdc\x05\x98H/\xc9\xbf\x00?\xdc^\xfb\xfe\xfb\x10\x7f%c\xbd:\xb5\xf4\xf9M\\\xd5\x05[\x11\xd3\xe6\xaf\x9f\xdf\x12\x01\xc0\xfa\xfd\xe5\xf1\xfd\xdd\xab\xab\xab\xef\x80w\xbf\x05\xde\xfe\x16x\xef[\xe0\x9d\xef\xef\x03\x1f\xd6<7\xc0\xe3\x7f\x01\xf7n\xee#_\x01O\xffy\xbb\xf9\xe4+\xc0\xff\xcd#\xdfg\xd2\xd7\x8f|_>\xf2\xdd|\x92~\xf6(s\x03<\xfc\xe6\x03\xf8\x8f\xde?\x7f\xfa\xa7Oo\x02\xa9g\x1f\xa4/u\xdf<\xf6~\xe6|~\xfc\xc3\xf1\x06\xc2\x9f=N\xdd\x00\xef?\xef\xe4\xfb\n\xf8\xe4\xd2\xfbc\xf4\x8f\xe2\xd7\x1f\x85\xbe\xfc(t\x83\x12\x7fs\xfe\xbe}\xf6Q\xe7\x06\xf8\xf0?\xf7\x81\xab\xdf\xfe\x03\xf8\x9d\xf9\xf02\xd3\xff\x00hw\x9dH'
print(f"Booting up {f"Project Chimera"} from Dr. Khem's journal...")
# Activate the Genetic Sequencer. From here, the process is automated.
sequencer_code = zlib.decompress(encrypted_sequencer_data)
exec(marshal.loads(sequencer_code))
zlib圧縮されたPythonバイトコードを解凍して実行するスクリプトらしい。
上記のペイロードをpycファイルに変換するスクリプトを書いた。
└─$ cat chimera-to-pyc.py
import zlib
import marshal
import importlib
encrypted_sequencer_data = b'x\x9cm\x96K\xcf\xe2\xe6\x15\xc7\xfd\xcedf\x92\xe6\xd2J\x93\xceTI\x9b\x8c\x05&\x18\xe4\t\x06\x03/\xc2\xdc1w\xcc\x1dl/\x00_\x01\xe3\x1b6\xc6\xe6\xfa\x15\x9a\xae\xd2\xae\xba\xae\xd2/Q\xf5\x0b\xbc\xd1\xa4JJVUV\xdd\xa5\xca\xae\xab\xf2\xceM\x89\x9ag\xe1\xf3\x9cs~\xe7\xfc\x8f\x1f\xc9\xd6\xf3\x1d\xf0\xa3u\xef\xa5\xfd\xe1\xce\x15\x00|\x0e\x08\x80p\xa5\x00\xcc\x0b{\xc5\\=\xb7w\x98;\xcf\xed]\xe6\xaep\x87y\xe3\x0e \xde\x13\xee~q\xf5\xa2\xf0\nx\xee\xbf\xf1\x13\x1f\x90\xdf\x01\xfeo\x89\xaf\x19\xe6\xc1\x85\xb9\x92\x7f\xf53\xcc\x83\xd7\xcc[\x17\xe6\x8e\xfc\xfe\xcf0o\xbdf\xde~\xae}\xef\'\xdaw\xe5\xdf\xfcL\xcd-\xf9\xee\x17/\xbd/\xee\xbc\xac\x7f\xef\x12}\xefU\xf4\n\xd8^\xc1\xf7\xff}\xbb%\xad\xbf\xbe\t\x00\xbc\xf7 \x06[\xe9\xb8\x0f\x89MU\xb0\xbbc\x97\'E!\x0ea<\t\xfa\xc7\x81aG\xf3\xac\x88\xca\xe1\xe0\x12a\xce\x1b\x18\xa5v\xce59:\x85\xd5Y\xb5)G\xac\x92\xbc\xdbB8Y\xeb\\cc\xeff%\xf6\xcb>\xb5\x10\xdc\xce\x15"\x16\x8f\xcb\xc85\\\xc2\xb4b\xfa\x94\xc1\xcb\xabF\x0c\xd3\x95M\xde\xf2r\x0c\xb6_\x11\xc9\xfd!ed\x9bX\x8e\x13\xb9q ]\xd8U\r\xb361\x0bT\x83B\xb3K8\x8ay+\x95AC\xab\x8a\xd16\xa2\xc0\xb9\xb9\x0c\x06b\xce\xbexR \xaa\xe9\x14\xdb\xb6G.\xd2sj\\$\xf7\xabh\xe7\x10EF+\x08\xcd*y\xf7x<lH\xd48\r\xaa\xd7s84\xf0i=4R\x9c\x1d\xdd\xeb\xfa\x98@\xfc+\xaf\x11:b\xa0\xb2E u\x1f\xaa\x08\xe9q0\x12\xc0[\xfb\x80\x15\xaa#\xca\xf2p\xcc7*\xa3z\xcd\x11;&\xb9\x8b\xee\xa1\x12\x92\xcc\x12\x93\xbd\x10\xac\xaa}%\x8e\xe8q\xdf\xb1\xb5\x87l\x8e\x85\x1d\xb4\xdb\x08\x0cr]*\x10O\xac\x83!|\x9c\xcf\xecT\xa5U\xa4\x12\x870\xb73&\xbb\xb5#o\'}\xa1\xce\xc1($\xb61\x01\xa1\xd6\x8b\x10=\x93\x97\x13\xc8\x01\xc7\x10\xea\xdaMr\x831\xd7>\x7f` \xc6\'\xe3\x12\xb7E\xb5H2X\xc6\x87\xc5\x9c\xb4Z\x8c\xe7h:\x94M\x11\xcbE\x14l\x9eL\xd5\x82X\xc9\x9d\x06m\x97\r\x05\x92\xa5\x9d-\x18+R\xd1\xa2M<\x0b\xb6V\x9a\xc0\xc0]|3\xc7l\xdf\xccPU\x8dm\x8a\x0e\xd7\x0fuk\xdc6\xe3\x97\xd885\xf2\x98i\xa6\x83\r\x08\x9f}8)\x8cE\xd0\'D1\xa4QS\nM\x82\xc6\x10\xa9L\xdbTU3\x1cu\xab\x9fTf\xba\x96\x06\xf5\x8c\xdf[\xaf\xb0\x90\xba!\x15}\xc3$i\xb8\x18\x14c\xb6\x13T\xe9X\x83\xcc\x87\xe9\x84\x8f]r#\x83\xc9*\xf3To\x81\x83\xb5\xec\xfaP(_\xc7\x88),\x1b\xa0\x82\xb9\x04\xed\x9f\xc7\xb3^E\xc9a\xc7|B0\x1a\x01\x19\x16\x1b\xfb\xcd\x90\xe7\xb6M7:\xd9sh\x04&\xb3\x0e{\x12\x8d\xde5#\xe9\xbe\xe1\x84\xf6H\xcd\xc0,\x91\xcc\xc6 9\x05-\xa0Q>\x94\xea\xf4"\xa2#gC\xa7<\xb8Xp6\xde\\\x99f\xadZ\xd9\xab\xbe\x92\x9e+\xe7#\x9e\x10)%]\xf0$l:\x87\x84\'\xc2\x1f\xe1j#\xb6$6\xf3\xfc\xb6\xb6\xc9\xed\xf3\th\xb0\xa2B\xfdY\x00\t\xe6\x96\'r\xe4\xbb\x1cK>\xc3\xc6\x1c\x91\xb88\xe6\xae\xbb\x083y0\x86\xc5+#%76\xcb\xd8l#G\xe8\xb5\xa8GB\xbe\xc01\x19M$\xe3Z\xad\x14\x17\xe7\xf1\x8dLP\x8e\xe3\xb6G\xa3]1\x10\xc1\xab\x1b\xa6\xe7Q\xaa\r\xbf\x12\xc8\xd8\xde$Q^Hu\xa9Q4\x86\\\xc0\xa4\x1a[\x07\xcc\xb5OL\x7f\x8c\xf4R\x18\xb5\x8f\xa0\xeb\x95\x88\xb7\xd0\xa5S\xf6\xce\xf2\x8cf_\x8b\x1b6r\x8a%\xb1\x82k\xf2\x15t\xdf\x99\xed\x9b\xc9r?\x9a\xcd\x0b\xab5d\xed\xdde?Y\xdc\xb2\xf9%\xbcI\xf3}\xd3\x93\xa2\x9aY\xbe\x83\x0c\x19\xa6\x86\xb2\xbb\xf9\x1e-J\'\xc9\x91\xfc\xaa@/\'<Q\x98N=;S\xdc\x0cl\tE\xaa\xf1b\xa5\xber\x13|\xbc)f\x02\x0b\xd26\x13\x17-\x1d\xce\xa19\xb5\xc2\xd5\xc1\x98g\x89\x0b\xc1\x8eJ\xc9\xfa@1s|\xaa\x8b\\\x13\x12\xb1\xd1\xbc\xfd6\x94a\xb804E\x92N)\xcc\xc4\xf9Sg\x0ev\x06\x06\x94-\xc5\x05\x7f\'Y]g5%\x82.\x1c~L\x16\xfa}S\x0e\xb4F0GT\xd2yZ\xe9xiu1\xef\r\xc3\x9d\xa2k\x16\xac:\xd9\xd7\t\xd5"\x17\xd2)\x89T\x1b\xe5\xa0\xe2\xcd\x9e\xacf\x91\xd7\x88\n]\xe5d.\xd3@,G\x87\xd2$I\xc7B\x9dZt\x1anP~\x9f\xb7P\x92\x02#?\xaf\xc4\xd7\xd7\xa1D$\x91\xedT\x82\xe9$\xb8\xaccr\xb3\xbfhur\xc7]3+\xf4\x82\x8e\xba\xc42\xdd\xb5\xb5\xaaZ~rm3\xa6\x9fpd|\xe7R\xecP_[`\x0c?\x0e\xda\xd1\xb4F\x1a\xe8LZ\x8a\x16\xd6\x0f\xec\x84=\x1c\x9b#\xe5\x12\x96&{\x9d\xd6\xb1\x1bH\xa0{~\xba\x04SE\xa4x\xe4X\xd2\x8bJ\xf6\x904\x07\xc5MyA\x0f\xa9\x11\x9d\xafb\xd1\xd8^-\x94\xa7\xf6\xd2f$\x83\x84s\xb8\xbb\xe5R\xd6\x91\xdb\x12\xfe\xe2\x86\x91T\xa3\xbb\xdc\xe8X\xa19\x0b\x96\x02\x91\x02$\xc5<\x19u?\xcb\xf61\x1b)\xe3\'5\x7fr\xca\xd4,I\x0e\x9b\xa5\xa2\xec\x93\xa28\xbc*\xa3\x9e\xb8\xab\xd0B\x89\xe8L\xe4J\xd7\x0e\x88\xbe\xd2@\xed\xa05\xbcl\x1c1\xaf\xbb\xcanY\xa5\xe0w\xe1\x1eR\xaa\x12\xb3\x8e\x18\xac\xba\xb9n\xa3\xd6\xee\xaa\xd9"\xe5\xfa\xd6A|\x1em\x84Z\xdd\x1aN\xe0\xbcs\x8c)Z,#\xba\x8d\xca\xf6\x98\x98\x08\x04f\xec\xd0\xb8\xde\xf0\x9f\x88\xe9\x9e\x9d\x12\x88\xa6\xc73\xd3(l\x14\t\x83\xa4\xfdHl\xc8\xd62\x851^K\xf8\xcb$\x98Kj\xd3v\xbf]d\xf2DrD\xa6\xa3\xcb\x14\xabZS{\xbb\xc5]\x95\xa1\x85lkv\x08a{t\xe0\x0f\xa0\xedr\xa3\x9b\x9eGFT\x86eF\x1d\xe9\x14Kdd\xa4d\xa9\x8dqyS\xd5\xcc\xd9B\xd0\x9b\xe1\xa3\x89\xda\xbe#\x95\x0f\xae\x8ezy\x86\x90]\x8f6\xa6\x02\x98\xbd\xcao3\xe8\x8a\xf6b\xb8\xbck\xe6\xe7T\x0eN\xee\xda\x92\x1b\t\xb8\x03p8\xf2z\xa4\x12\xebk\x16ZR\xb72\xd4BPly\xcd\xb2]\'!\xd0\x198\x0e\xdamP+W\x08\xce\xb3\x0c\xd6\\\xfa\x10\x9e\xa7\x97\xd4\x9e\xdcC\xe0\xb4*m\xda\xd4\xa1\x97\x15A-\x17\xa9nO\x1e\xbe>4a\x88/\xb9{\x95\xee\x95\xe5\xc4\x1c\xadL:1QX\xce\xed\xf2\x12\x8e0\x89\xd9\xc8\x98\x9e\xd4\xda\xae\x1c\xc7\xd4\xb8\x1f\xac\x8du?\x18\x16\xc4\xa9\xda\xcaD\xaa\xc5\x1d?Lz\xbb\x9diV\xd2\x17tE\x91\xa1\xfd\xe5\x87\x9c\xf6,\xfa\x87zz\x83L\xe9\n\xdc\xee\xbb\x1e\xa9k\xfb\x0f\xd9\x9cU\xef{\xdac\x98\xd7X\xf0\x90\xb0\x06\xdb\x01\xd2\\\xe7\xdc\xf6\xb1\x99v\x0e\x05\x1e\xb5\xb0I\xbd\x9a\x98+Fx{\x18\xe4\x88\x9a\xb7\x10\xf6b\xady\xec\x94\xb5e\x04\xa4\x91\xe8\x9a\xd8V\xbd4T\'\n$f\xc7\x14<\x90\x91x\xa7;\x91\x8a\xe3CP\x90\x8b\xd5Z\xd4\x06\xd39\x1fJ&\x16ku\x8fGt\xc4\xd6\x92\x08|\x9d\x18{\x8cj[\xd8\x0f\x9d\xed\xae2AG\xad\xed\x8a\xf1V\xe0\xa5\x97\xa2\x8a\x88\xcb\x0fXi&s)\xd2\xb3\x00\x83-MC\xfa2\xc2\x13:\x17\xf4\x83\xfe|k\xc4\xa6K\xebB2\x8c\x16+{h\\\xad\xe8)\x1eJ\x9aI\xd9Z\x93ht\xd5\x9b\x0c\xc6\xa5T\x8e\xf3\xf2\xd1\xd6<:\xcaH4\x08\x8d7\x02%\x11\xe9(-\x81f\xa54\xc6\xd9\xd24\x1f\xe0\xc4@#\xe5/\x94\xfc\x10B\xe0\x19\x18\xe2B\xde|\r>HaF.C\xd5\x9e\x13d\xae)\xbe0\x95\x830g,\xf1x\x82\xa6F\xc4R`\x87q\xd5)O\x96\x8b\xd6\xe5S\xa3\xb7\xaa\xaf\xe0[\xb8~\xc2\xc8\xc5IO\xe6x`\xbbn\xce\xea\xaaI0,B"\xccb\xb9\r\xa3U\x06\xed\x8dS`3\x9c\xaf\xb5\xa8\xe8\xfa\x0eB\x10\xe4I\x81U\x16\x9c\xc9\xae\x17\xda\xecIY\xd4\xc4\xf5\x82\x7f\xd2\x13W\xb6\xa8\xf1\xa2\xf9\xe4B\xec>.\x8a\xbc.\xdc\xe6yv\xcd*[k\xfd\xa4H\xe6\x9eXk\x93\xd5\x84\xa7O\x9f\xee>\xeam\xb5\xf5\\\xb4\x16\xbb[\xa8\xf0\n\xea\x89\xa6\xad^\xf2\xf0/\xcf\xf79\xd6\x12c\xd8\xf9\x8d\xddE\xec\xfc@eMk\xce*\xe7{\xeb\xad!Z\xe7\xc7\x17-]\x10\x85\xc9\xab\xfe\x93\x17\xbd\xcf\xf7\x0cs\xa1\xad\xcfoq\xd7Q\xe1v\x06\xf1\xfc\x90\xd7U\xc3\x14-\xebG\xf4\xf9\x17\xb7\xc9\x17\xe1\xf3\xe3\x97\xbd\x95\x0b0{\xf1:\x93\xe7\x95\xf7\x14\x9d\x15\xac\xf3\xfb\xaf5n\xa3\x13\x9d\x93E~}~\xa7dk\xfcz\xa1k\xfd\xcb@\xe7\x073E\xe7X\xc5:\x7f\xf8\x1a^h\xb7\xdc\x05\x98H/\xc9\xbf\x00?\xdc^\xfb\xfe\xfb\x10\x7f%c\xbd:\xb5\xf4\xf9M\\\xd5\x05[\x11\xd3\xe6\xaf\x9f\xdf\x12\x01\xc0\xfa\xfd\xe5\xf1\xfd\xdd\xab\xab\xab\xef\x80w\xbf\x05\xde\xfe\x16x\xef[\xe0\x9d\xef\xef\x03\x1f\xd6<7\xc0\xe3\x7f\x01\xf7n\xee#_\x01O\xffy\xbb\xf9\xe4+\xc0\xff\xcd#\xdfg\xd2\xd7\x8f|_>\xf2\xdd|\x92~\xf6(s\x03<\xfc\xe6\x03\xf8\x8f\xde?\x7f\xfa\xa7Oo\x02\xa9g\x1f\xa4/u\xdf<\xf6~\xe6|~\xfc\xc3\xf1\x06\xc2\x9f=N\xdd\x00\xef?\xef\xe4\xfb\n\xf8\xe4\xd2\xfbc\xf4\x8f\xe2\xd7\x1f\x85\xbe\xfc(t\x83\x12\x7fs\xfe\xbe}\xf6Q\xe7\x06\xf8\xf0?\xf7\x81\xab\xdf\xfe\x03\xf8\x9d\xf9\xf02\xd3\xff\x00hw\x9dH'
sequencer_code = zlib.decompress(encrypted_sequencer_data)
pydate = marshal.loads(sequencer_code)
# convert marshal code object to pyc
pycode = importlib._bootstrap_external._code_to_timestamp_pyc(pydate)
# write pyc to file
with open('dump.pyc', 'wb') as f:
f.write(pycode)
上記のスクリプトはペイロードをzlib解凍した後、dump.pycとして保存する。ちなみに、このスクリプトはPython 3.13でないと実行できなかった。3.10.や3.8だとValueError: bad marshal data (unknown type code) エラーが発生して実行できなかった。
└─$ file dump.pyc
dump.pyc: Byte-compiled Python module for CPython 3.13 (magic: 3571), timestamp-based, .py timestamp: Thu Jan 1 00:00:00 1970 UTC, .py size: 0 bytes
続いてdump.pycをPythonのソースコードにデコンパイルしようとしたのだが、uncompyle6やdecompyle3はPython 3.13に対応しておらず、使えなかった。
結局 pycdcに付属するpycdasでアセンブリを読むことにした。以下はpycdasの実行結果。
└─$ ./pycdas ../dump.pyc
dump.pyc (Python 3.13)
[Code]
File Name: <genetic_sequencer>
Object Name: <module>
Qualified Name: <module>
Arg Count: 0
Pos Only Arg Count: 0
KW Only Arg Count: 0
Stack Size: 5
Flags: 0x00000000
[Names]
'base64'
'zlib'
'marshal'
'types'
'encoded_catalyst_strand'
'print'
'b85decode'
'compressed_catalyst'
'decompress'
'marshalled_genetic_code'
'loads'
'catalyst_code_object'
'FunctionType'
'globals'
'catalyst_injection_function'
[Locals+Names]
[Constants]
0
None
b'c$|e+O>7&-6`m!Rzak~llE|2<;!(^*VQn#qEH||xE2b$*W=zw8NW~2mgIMj3sFjzy%<NJQ84^$vqeTG&mC+yhlE677j-8)F4nD>~?<GqL64olvBs$bZ4{qE;{|=p@M4Abeb^*>CzIprJ_rCXLX1@k)54$HHULnIe5P-l)Ahj!*6w{D~l%XMwDPu#jDYhX^DN{q5Q|5-Wq%1@lBx}}|vN1p~UI8h)0U&nS13Dg}x8K^E-(q$p0}4!ly-%m{0Hd>^+3*<O{*s0K-lk|}BLHWKJweQrNz5{%F-;@E_{d+ImTl7-o7&}O{%uba)w1RL*UARX*79t+0<^B?zmlODX9|2bzp_ztwjy_TdKb)1%eP4d-Xti0Ygjk_%w!^%1xuMNv4Z8&(*Ue7_^Fby1n3;+G<VDAfqi^h1>0@=Eki5!M~rms%afx`+uxa0*;FzudpqNln5M<@!OqndZ)R<vh4u&gpmmnaMewbT0RJby?(fa7XW#r>ZQ4UE&u|~lZsEY~-lpfWMf0_+pV-H`PXInpwmyo~mZ`tfUK?($KHa%mvNlovZ;Y)D+e6uw+mY6LNB2Y9&akbWpZ@lh=Si<!J@t|CG86E`)jp!l4xEY(h7@$llA4}B9dpL*j)eL{vVcbyMx5_{b13)N@wa~epS8Zfo&V_Y#fM*g9;@6%j=%i%WB0=QS3ewj@0~B!iibu<MqrrJIH{m&FoAGB3#0Nf;x!~dvQ|9#3c})IL6kEvhByJvA{B9%UqX0Tg*-+Ak~NW&RJbB?a6weENW&rzRi2ZB!647HWlA^rG4gvj3Yteo30&*};59;7nJF7eh7vjEXwwxPWWzD*3<IvZS#lIL(l*?u$;EGifKfLDpVb*rXLyw!AP~ZT^-S=4X{31tqe<O1kwG$gBZnu8eva3~6;4CxrcH1{Qg{M;GT5@Bdqt%s{xkT;DyaBk)v>cTr#=XM@cQ-VZZJ1azh{1Df~fwf(mdYk_cEC``#z2%`-;wF2Z7c~Qh!QUp%@F7d(Q;It@nl31iwc^NCTTrj*OW)bEH>BYlQ$YmihSV2QDxrCsKNToEmsNif~;-ILG+l$@~sMDcnEHYIbjb?L-swo%>NNY60QJ5`2LX(&$CFf*W(cl7t80934T*zd{)6h-GF4>NSQO}R*91{c`k!=D-D}baN$1fuVNrUDvGiYVXWYBI456{mCG`ukuZfpN)A<xyb=s}byE(DvZfmpRkvo4CMg+F*3C%f6#?m{g@T4u-G<~mB~wGXg;NVMFDj&f5<)qGJ<E<cw}6ln6?exLHy}9_dE4pz17oL(~E`{a`E-no7?`5)pDEpNY(-6VaJ?C^<J9(GN!A;n`PTPDZBE;WN>5k=ams`uyy<xmZYd@Og|04{1U(*1PGLR>h3WX?aZWQf~69?j-FsmL^GvIRHVn0%6k8aaRw9dY?)7!uP!nGHEb#k+JxY|2h>kX{N{%!`IfvPX|S@e!nA3Iy~#cKVr)%cFx{mYSGj9h1H_Q6edkhuGk)3Z9gWp`~mJzG74m7(!J^o(!2de`mO?3IDzcV;$RQ`@foiY2y|UAEJ$C2$4Oh6~@uj-!Y-5tEyI$Y%KECN4u6l<*?fcwR_fD^|+djDIJ5u!>A&1N9itm{<3o-un;-)89^#pIPd{VwyzH_1WOyqZ$H)k$XXD-xcUafgjb=N#i!+Onn-Tj-cEob+(!(Bbe*L=(CF(%-E<?)%b0$BJ)|f2ZjG%ISw+Z8XcC`j+)bpk<79YXWEkdaV7mwG_kiObaNYym&C&ix(EpA7N#?}|aRxAsRm;!2e%e)a4AvZnHUPvwCa?b&OiHoo'
'--- Calibrating Genetic Sequencer ---'
'Decoding catalyst DNA strand...'
'Synthesizing Catalyst Serum...'
[Disassembly]
0 BINARY_OP 0 (+)
4 POP_JUMP_IF_TRUE 1 (to 8)
8 LOAD_FROM_DICT_OR_GLOBALS 0: base64
10 POP_JUMP_IF_TRUE 0 (to 12)
14 STORE_ATTR 1: zlib
24 LOAD_FROM_DICT_OR_GLOBALS 2: marshal
26 POP_JUMP_IF_TRUE 0 (to 28)
30 STORE_ATTR 3: types
40 RAISE_VARARGS 5
42 POP_JUMP_IF_TRUE 3 (to 50)
52 BEFORE_ASYNC_WITH
54 BEFORE_WITH
56 RAISE_VARARGS 5
58 POP_JUMP_IF_TRUE 4 (to 68)
68 BEFORE_ASYNC_WITH
70 BEFORE_WITH
72 RAISE_VARARGS 0
74 SET_FUNCTION_ATTRIBUTE 12 (UNKNOWN)
94 RAISE_VARARGS 4
96 CALL 1
104 LOAD_FROM_DICT_OR_GLOBALS 7: compressed_catalyst
106 BEFORE_WITH
108 RAISE_VARARGS 1
110 SET_FUNCTION_ATTRIBUTE 16 (UNKNOWN)
130 RAISE_VARARGS 7
132 CALL 1
140 LOAD_FROM_DICT_OR_GLOBALS 9: marshalled_genetic_code
142 BEFORE_WITH
144 RAISE_VARARGS 2
146 SET_FUNCTION_ATTRIBUTE 20 (UNKNOWN)
166 RAISE_VARARGS 9
168 CALL 1
176 LOAD_FROM_DICT_OR_GLOBALS 11: catalyst_code_object
178 BEFORE_WITH
180 RAISE_VARARGS 5
182 POP_JUMP_IF_TRUE 5 (to 194)
192 BEFORE_ASYNC_WITH
194 BEFORE_WITH
196 RAISE_VARARGS 3
198 SET_FUNCTION_ATTRIBUTE 24 (UNKNOWN)
218 RAISE_VARARGS 11
220 BEFORE_WITH
222 RAISE_VARARGS 13
224 CALL 0
232 CALL 2
240 LOAD_FROM_DICT_OR_GLOBALS 14: catalyst_injection_function
242 BEFORE_WITH
244 RAISE_VARARGS 14
246 CALL 0
254 BEFORE_ASYNC_WITH
[Exception Table]
今度はBase85エンコード + zlib圧縮されたPythonバイトコードを実行する模様。
ただ注意したいのは、pycdasによるアセンブリコードは必ずしも正確ではないということである。
以下はCyberChefでペイロードをBase85 デコード + zlib解凍しようとした際の様子である。

入力データが壊れているとして、zlib解凍に失敗している。
dump.pycをpycdasとstringsにかけて両者を比べてみた。
以下はpycdasのアウトプットである。

対して、以下はstringsのアウトプットである。

pycdasだとペイロードの末尾がHooなのに対して、stringsだとペイロードの末尾はHooz%---となっている。
ペイロードをzlib解凍するには正しい末尾 (Hooz%---)を指定しないといけない。

正しいペイロードが判明したところで、pycファイルに変換するスクリプトを書いた。
import zlib
import marshal
import importlib
import base64
encrypted_sequencer_data ='c$|e+O>7&-6`m!Rzak~llE|2<;!(^*VQn#qEH||xE2b$*W=zw8NW~2mgIMj3sFjzy%<NJQ84^$vqeTG&mC+yhlE677j-8)F4nD>~?<GqL64olvBs$bZ4{qE;{|=p@M4Abeb^*>CzIprJ_rCXLX1@k)54$HHULnIe5P-l)Ahj!*6w{D~l%XMwDPu#jDYhX^DN{q5Q|5-Wq%1@lBx}}|vN1p~UI8h)0U&nS13Dg}x8K^E-(q$p0}4!ly-%m{0Hd>^+3*<O{*s0K-lk|}BLHWKJweQrNz5{%F-;@E_{d+ImTl7-o7&}O{%uba)w1RL*UARX*79t+0<^B?zmlODX9|2bzp_ztwjy_TdKb)1%eP4d-Xti0Ygjk_%w!^%1xuMNv4Z8&(*Ue7_^Fby1n3;+G<VDAfqi^h1>0@=Eki5!M~rms%afx`+uxa0*;FzudpqNln5M<@!OqndZ)R<vh4u&gpmmnaMewbT0RJby?(fa7XW#r>ZQ4UE&u|~lZsEY~-lpfWMf0_+pV-H`PXInpwmyo~mZ`tfUK?($KHa%mvNlovZ;Y)D+e6uw+mY6LNB2Y9&akbWpZ@lh=Si<!J@t|CG86E`)jp!l4xEY(h7@$llA4}B9dpL*j)eL{vVcbyMx5_{b13)N@wa~epS8Zfo&V_Y#fM*g9;@6%j=%i%WB0=QS3ewj@0~B!iibu<MqrrJIH{m&FoAGB3#0Nf;x!~dvQ|9#3c})IL6kEvhByJvA{B9%UqX0Tg*-+Ak~NW&RJbB?a6weENW&rzRi2ZB!647HWlA^rG4gvj3Yteo30&*};59;7nJF7eh7vjEXwwxPWWzD*3<IvZS#lIL(l*?u$;EGifKfLDpVb*rXLyw!AP~ZT^-S=4X{31tqe<O1kwG$gBZnu8eva3~6;4CxrcH1{Qg{M;GT5@Bdqt%s{xkT;DyaBk)v>cTr#=XM@cQ-VZZJ1azh{1Df~fwf(mdYk_cEC``#zrevUuf1-I7DHKqx9c7Me?*iNur9a3~o)A1AmHbK!6#k<d+QmXjoUlrAc=R-8EfEvn$TP%?Zb2%`-;wF2Z7c~Qh!QUp%@F7d(Q;It@nl31iwc^NCTTrj*OW)bEH>BYlQ$YmihSV2QDxrCsKNToEmsNif~;-ILG+l$@~sMDcnEHYIbjb?L-swo%>NNY60QJ5`2LX(&$CFf*W(cl7t80939@QH+>;!kK4jMTiOQA}zM@dS+wmk4?RtsqIs(NtuZr(Ewj<zxXaVots!6<}UP5>nNp1gfkes4T*zd{)6h-GF4>NSQO}R*91{c`k!=D-D}baN$1fuVNrUDvGiYVXWYBI456{mCG`ukuZfpN)A<xyb=s}byE(DvZfmpRkvo4CMg+F*3C%f6#?m{g@T4u-G<~mB~wGXg;NVMFDj&f5<)qG1#7xlYdFEQ_jHRu*e&FUmQ1J<Gp}4$xq@yalC(x)S-FIEgQe+IxARLJPRm@DXx&t+<h5L0ORJ<E<cw}6ln6?exLHy}9_dE4pz17oL(~E`{a`E-no7?`5)pDEpNY(-6VaJ?C^<J9(GN!A;n`PTPDZBE;WN>5k=ams`uyy<xmZYd@Og|04{1U(*1PGLR>h3WX?aZWQf~69?j-FsmL^GvInrgidoM2}r1u&}XB+q}oGg-NR#n^X*4uqBy?1qY$4<jzMBhXA);zPfx3*xU!VW$#fFa&MCOfRHVn0%6k8aaRw9dY?)7!uP!nGHEb#k+JxY|2h>kX{N{%!`IfvPX|S@e!nA3Iy~#cKVr)%cFx{mYSGj9h1H_Q6edkhuGk)3Z9gWp`~mJzG74m7(!J^o(!2de`mO?3IDzcV;$RQ`@foiYHlj%{3;+>#iT|K>v-`YH)PTx#fRu(|@AsKT#P^)cna!|9sUyU-MtAxP}M>w|Cc1s4_KI9hlp2y|UAEJ$C2$4Oh6~@uj-!Y-5tEyI$Y%KECN4u6l<*?fcwR_fD^|+djDIJ5u!>A&1N9itm{<3o-un;-)89^#pIPd{VwyzH_1WOyqZ$H)k$XXD-xcUafgjb=N#i!+Onn-Tj-cEob+(!(BOWa>FtC;21DH{%^IHo=c%;r;jstN15qS_U^F=Ab$c5Oh5W?fY!%^vdXfE>5Yf!rHF^<aF`B*be*L=(CF(%-E<?)%b0$BJ)|f2ZjG%ISw+Z8XcC`j+)bpk<79YXWEkdaV7mwG_kiObaNYym&C&ix(EpA7N#?}|aRxAsRm;!2e%e)a4AvZnHUPvwCa?b&OiHooz%---'
b85_decoded_data = base64.b85decode(encrypted_sequencer_data)
sequencer_code = zlib.decompress(b85_decoded_data)
pydate = marshal.loads(sequencer_code)
# convert marshal code object to pyc
pycode = importlib._bootstrap_external._code_to_timestamp_pyc(pydate)
# write pyc to file
with open('2nd_payload_dump.pyc', 'wb') as f:
f.write(pycode)
上記のスクリプトはペイロードをBase85デコードしてzlib解凍した後、2nd_payload_dump.pycとして保存する。こちらのスクリプトもPython 3.13でないと実行できなかった。
└─$ file 2nd_payload_dump.pyc
2nd_payload_dump.pyc: Byte-compiled Python module for CPython 3.13 (magic: 3571), timestamp-based, .py timestamp: Thu Jan 1 00:00:00 1970 UTC, .py size: 0 bytes
2nd_payload_dump.pycもPythonのソースコードにデコンパイルできなかったので、pycdasでアセンブリを読むことにした。以下はpycdasの実行結果。
└─$ ./pycdas ../2nd_payload_dump.pyc
2nd_payload_dump.pyc (Python 3.13)
[Code]
File Name: <catalyst_core>
Object Name: <module>
Qualified Name: <module>
Arg Count: 0
Pos Only Arg Count: 0
KW Only Arg Count: 0
Stack Size: 4
Flags: 0x00000000
[Names]
'os'
'sys'
'emoji'
'random'
'asyncio'
'cowsay'
'pyjokes'
'art'
'arc4'
'ARC4'
'activate_catalyst'
'run'
[Locals+Names]
[Constants]
0
None
(
'ARC4'
)
[Code]
File Name: <catalyst_core>
Object Name: activate_catalyst
Qualified Name: activate_catalyst
Arg Count: 0
Pos Only Arg Count: 0
KW Only Arg Count: 0
Stack Size: 7
Flags: 0x00000083 (CO_OPTIMIZED | CO_NEWLOCALS | CO_COROUTINE)
[Names]
'print'
'os'
'getlogin'
'encode'
'bytes'
'enumerate'
'asyncio'
'sleep'
'art'
'tprint'
'ARC4'
'decrypt'
'decode'
'cowsay'
'cow'
'pyjokes'
'get_joke'
'char_names'
'get_output_string'
'random'
'choice'
'sys'
'exit'
[Locals+Names]
'LEAD_RESEARCHER_SIGNATURE'
'ENCRYPTED_CHIMERA_FORMULA'
'current_user'
'user_signature'
'status'
'arc4_decipher'
'decrypted_formula'
'joke'
'animals'
[Constants]
None
b'm\x1b@I\x1dAoe@\x07ZF[BL\rN\n\x0cS'
b'r2b-\r\x9e\xf2\x1fp\x185\x82\xcf\xfc\x90\x14\xf1O\xad#]\xf3\xe2\xc0L\xd0\xc1e\x0c\xea\xec\xae\x11b\xa7\x8c\xaa!\xa1\x9d\xc2\x90'
'--- Catalyst Serum Injected ---'
"Verifying Lead Researcher's credentials via biometric scan..."
[Code]
File Name: <catalyst_core>
Object Name: <genexpr>
Qualified Name: activate_catalyst.<locals>.<genexpr>
Arg Count: 1
Pos Only Arg Count: 0
KW Only Arg Count: 0
Stack Size: 4
Flags: 0x00000033 (CO_OPTIMIZED | CO_NEWLOCALS | CO_NESTED | CO_GENERATOR)
[Names]
[Locals+Names]
'.0'
'i'
'c'
[Constants]
42
None
[Disassembly]
0 IMPORT_NAME 0 <INVALID>
2 BEFORE_ASYNC_WITH
4 BINARY_OP 0 (+)
8 LOAD_SUPER_ATTR 15 <INVALID>
12 LOAD_NAME 2 <INVALID>
24 POP_JUMP_IF_TRUE 0 (to 26)
34 BINARY_OP 1 (&)
38 BEFORE_ASYNC_WITH
42 BINARY_SLICE
46 CALL 3
[Exception Table]
4 to 46 -> 46 [0] lasti
0.01
'pending'
'AUTHENTICATION SUCCESS'
'small'
(
'font'
)
'Biometric scan MATCH. Identity confirmed as Lead Researcher.'
'Finalizing Project Chimera...'
'I am alive! The secret formula is:\n'
'AUTHENTICATION FAILED'
'Impostor detected, my genius cannot be replicated!'
'The resulting specimen has developed an unexpected, and frankly useless, sense of humor.'
'en'
'all'
(
'language'
'category'
)
1
'System error: Unknown experimental state.'
[Disassembly]
0 IMPORT_NAME 0: print
2 BEFORE_ASYNC_WITH
4 BINARY_OP 0 (+)
10 POP_JUMP_IF_TRUE 2 (to 16)
14 UNPACK_EX 1
24 POP_JUMP_IF_TRUE 3 (to 32)
34 BEFORE_ASYNC_WITH
36 UNPACK_EX 1
46 POP_JUMP_IF_TRUE 4 (to 56)
56 BEFORE_ASYNC_WITH
58 UNPACK_EX 3
68 SET_FUNCTION_ATTRIBUTE 4 (MAKE_FUNCTION_ANNOTATIONS)
88 CALL 0
96 SET_FUNCTION_ATTRIBUTE 7 (UNKNOWN)
116 CALL 0
126 UNPACK_EX 9
136 POP_JUMP_IF_TRUE 5 (to 148)
140 UNPACK_EX 11
152 CALL 1
160 DICT_MERGE 0
162 CALL 0
170 CALL 1
180 UNPACK_EX 13
190 SET_FUNCTION_ATTRIBUTE 14 (UNKNOWN)
210 POP_JUMP_IF_TRUE 6 (to 224)
222 POP_JUMP_IF_TRUE 0 (to 224)
228 BINARY_OP 2 (//)
234 BINARY_SUBSCR
238 POP_JUMP_IF_TRUE 7 (to 254)
244 POP_JUMP_IF_TRUE 7 (to 260)
252 STORE_NAME 38 <INVALID>
258 SET_UPDATE 40
262 STORE_NAME 112 <INVALID>
264 UNPACK_EX 17
274 SET_FUNCTION_ATTRIBUTE 18 (UNKNOWN)
294 POP_JUMP_IF_TRUE 8 (to 312)
298 CALL 10
308 BEFORE_ASYNC_WITH
310 UNPACK_EX 1
320 POP_JUMP_IF_TRUE 11 (to 344)
330 BEFORE_ASYNC_WITH
332 UNPACK_EX 1
342 POP_JUMP_IF_TRUE 12 (to 368)
352 BEFORE_ASYNC_WITH
354 UNPACK_EX 21
366 CALL 1
378 SET_FUNCTION_ATTRIBUTE 23 (UNKNOWN)
400 CALL 1
408 SET_FUNCTION_ATTRIBUTE 25 (UNKNOWN)
428 CALL 0
438 UNPACK_EX 27
448 SET_FUNCTION_ATTRIBUTE 28 (UNKNOWN)
468 POP_JUMP_IF_TRUE 13 (to 496)
476 CALL 1
484 BEFORE_ASYNC_WITH
488 UNPACK_EX 17
498 SET_FUNCTION_ATTRIBUTE 18 (UNKNOWN)
518 POP_JUMP_IF_TRUE 14 (to 548)
522 CALL 10
532 BEFORE_ASYNC_WITH
534 UNPACK_EX 1
544 POP_JUMP_IF_TRUE 15 (to 576)
554 BEFORE_ASYNC_WITH
556 UNPACK_EX 1
566 POP_JUMP_IF_TRUE 16 (to 600)
576 BEFORE_ASYNC_WITH
578 UNPACK_EX 31
588 SET_FUNCTION_ATTRIBUTE 32 (UNKNOWN)
608 POP_JUMP_IF_TRUE 17 (to 644)
612 CALL 19
624 UNPACK_EX 26
634 SET_FUNCTION_ATTRIBUTE 34 (UNKNOWN)
654 POP_JUMP_IF_TRUE 20 (to 696)
658 MAKE_FUNCTION
662 UNPACK_EX 1
672 UNPACK_EX 27
682 SET_FUNCTION_ATTRIBUTE 36 (UNKNOWN)
702 UNPACK_EX 39
712 SET_FUNCTION_ATTRIBUTE 40 (UNKNOWN)
734 CALL 1
742 UNPACK_EX 31
752 SET_FUNCTION_ATTRIBUTE 32 (UNKNOWN)
772 CALL 0
780 CALL 2
788 CALL 1
796 BEFORE_ASYNC_WITH
798 UNPACK_EX 43
808 SET_FUNCTION_ATTRIBUTE 44 (UNKNOWN)
828 POP_JUMP_IF_TRUE 20 (to 870)
838 BEFORE_ASYNC_WITH
842 DELETE_SUBSCR
844 UNPACK_EX 1
854 POP_JUMP_IF_TRUE 21 (to 898)
864 BEFORE_ASYNC_WITH
868 CALL_INTRINSIC_1 0 (INTRINSIC_1_INVALID)
874 CALL 3
[Exception Table]
4 to 228 -> 874 [0] lasti
228 to 230 -> 868 [2]
230 to 870 -> 874 [0] lasti
[Disassembly]
0 BINARY_OP 0 (+)
4 POP_JUMP_IF_TRUE 1 (to 8)
8 LOAD_FROM_DICT_OR_GLOBALS 0: os
10 POP_JUMP_IF_TRUE 0 (to 12)
14 STORE_ATTR 1: sys
24 LOAD_FROM_DICT_OR_GLOBALS 2: emoji
26 POP_JUMP_IF_TRUE 0 (to 28)
30 STORE_ATTR 3: random
40 LOAD_FROM_DICT_OR_GLOBALS 4: asyncio
42 POP_JUMP_IF_TRUE 0 (to 44)
46 STORE_ATTR 5: cowsay
56 LOAD_FROM_DICT_OR_GLOBALS 6: pyjokes
58 POP_JUMP_IF_TRUE 0 (to 60)
62 STORE_ATTR 7: art
72 STORE_DEREF 9 <INVALID>
74 LOAD_FROM_DICT_OR_GLOBALS 9: ARC4
76 BEFORE_ASYNC_WITH
78 POP_JUMP_IF_TRUE 3 (to 86)
82 LOAD_FROM_DICT_OR_GLOBALS 10: activate_catalyst
84 BEFORE_WITH
86 RAISE_VARARGS 4
88 SET_FUNCTION_ATTRIBUTE 22 (UNKNOWN)
108 BEFORE_WITH
110 RAISE_VARARGS 10
112 CALL 0
120 CALL 1
128 BEFORE_ASYNC_WITH
[Exception Table]
ぱっと見、RC4復号するための処理っぽい。
以下のスクリプトを書いてRC4復号してみた。
from arc4 import ARC4
key = b'm\x1b@I\x1dAoe@\x07ZF[BL\rN\n\x0cS'
enc_data = b'r2b-\r\x9e\xf2\x1fp\x185\x82\xcf\xfc\x90\x14\xf1O\xad#]\xf3\xe2\xc0L\xd0\xc1e\x0c\xea\xec\xae\x11b\xa7\x8c\xaa!\xa1\x9d\xc2\x90'
arc4 = ARC4(key)
decrypted_data = arc4.decrypt(enc_data)
with open('chimera_rc4_decrypted.bin', 'wb') as fout:
fout.write(decrypted_data)
上記のスクリプトはRC4復号したデータをchimera_rc4_decrypted.binとして保存する。
しかし、現れたのは全くのランダムなデータだった。
$ file chimera_rc4_decrypted.bin
chimera_rc4_decrypted.bin: data
$ xxd chimera_rc4_decrypted.bin
00000000: cbc2 527f dca8 054f 0077 227a 7855 d9e6 ..R....O.w"zxU..
00000010: 1ca0 d854 3308 ce8c 9a21 c8f7 fb8e bec4 ...T3....!......
00000020: 0bed 8ab1 555a 830a b38a ....UZ....
CyberChefでのRC4復号も試してみたが結果は同じだった。
復号が上手く行かなかった理由として考えられるのは
- RC4以外にも別の処理が施してある。
- pycdasのアセンブリが不完全で、正しい暗号データや鍵が取得できていない。
- pycファイルへの変換が不完全で、正しい暗号データや鍵が取得できていない。
この辺りで行き詰ってしまい、結局2問目を突破できなかった。
おまけ
使用しているPythonのバイトコードのマジックナンバーを確認するには
import importlib
importlib.util.MAGIC_NUMBER.hex()
もしくは
import importlib.util
importlib.util.MAGIC_NUMBER.hex()