2020 AIS3 EOF Final - ⚔ Cat Slayer ⚔
題目:⚔ Cat Slayer ⚔
⚔ Cat Slayer ⚔
Challenge Info
三十七年前,我只是某個隨處可見、普通的男高中生。
在十八歲生日前的那天放學,我一如往常地走在那條有些不平整的人行道上,然而一回神,我就被迎面衝來的兩百二十二隻貓咪撞暈了過去——再睜開眼,便來到這個異世界了。
…(字太多以下略XD)
Author: splitline
game.pyc, cat_slayer.data.meow
Solution
Part 0: Try
執行 game.pyc,是一個選單打怪小遊戲,並且錢夠多可以買 Flag

看到 .pyc 就會想反編譯
| |
看起來一切順利:
| |
所以程式流程是:
- 從
'./cat_slayer.data'讀取資料,並且最後 8 bytes 用 big-endian 表示money - 名字隨便打
- 直接買 Flag,並且輸入 token
d656d6f266c65637f236f62707f2 - 執行
get_flag()並得到 flag
換句話說,這是 flag
| |
理想上輸出要是:🐱 FLAG = b'FLAG{MEOWMEOW}'
但實際執行卻是:
| |
Part 1:Long function name QQ
其實把 .pyc 的 bytecode 反組譯就能看出端倪:
| |
會發現其中有一個 code object 長相特殊
| |
也就是,事實上框起來的部分是 function name

也就是 get_flag() 其實是執行「原本的」 save_game()
| |
所以真正的執行流程是:
- 執行
get_flag()之後,修改/proc/self/mem,也就是 self modifying - 對
shop() + 240的位置寫入struct.pack('>HHHH', 25626, 24833, 29707, 33536) - 對
fight() + offset的位置寫入b't\x13d\x93d\x94\x83\x02\x01\x00q$'
Part 2:dis.dis()
先分析 shop() + 240 的部分:
首先要知道 shop() + 240 在哪裡,用 dis.dis() 分析 game.pyc 內的 shop() code object
| |
也就是把 print(':) 🐱') 之後的內容蓋掉(get_flag() 結束後執行)
而覆蓋的 byte code 內容如下:
| |
變數內容可以從 shop() byte code 的 co_consts, co_names 取得:
| |
同理,寫到 fight() + offset 的內容也可以用同樣方式解出來
他會用有點遞迴的方式把 fight() 寫掉
- 先蓋掉
fight() + 0的部分 - 執行
fight()之後會再寫掉fight() + 16,並且跳進去 - 以此類推
寫個腳本把所有 patch 的 instruction 印出來:
| |
內容其實不多,所以手動把輸出的 instruction 轉換成 python code:
| |
弱點明顯是 random.seed(int(time.time()))
因為可以知道檔案創建時間,稍微爆破一下 timestamp 就能解出檔案內容

| |
Flag
AIS3{d4rkness_c4ts_ar3_ev1l_qwq}
順手附上之前做的 pyc 簡報:https://hackmd.io/@C5qogZpXS6m0aedcVROJ6A/rkGBI_1ru#/
Bonus
實際改改看 function name,讓 uncompyle6 反編譯出來的結果跟實際不同
先創建 test.py
| |
python3 -m compileall test.py -b 得到 test.pyc
寫個腳本 patch.py 將 func2 替換掉,也就是修改 func2() 的 co_name 再用 marshal 序列化存起來
| |
| |
最後反編譯得到 test_patched.py
| |
成功!