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
|
|
成功!