Writeup: AlpacaHack Round 4 - pytecode
TL;DR §
- PickleバイトコードでPythonのコードオブジェクトを構築しながらフラグをチェックする
- co_argcountとco_codestring以外は固定でこれらをSETITEMSオペコードを実行してセットしたものをNEWOBJオペコードでインスタンス化している
- 構築されるコードオブジェクトにもいくらかの難読化が施されている
- co_consts や co_names が空: 定数等が変数として使えないため、引数の数値同士を除算、減算する形で1や0を作ってそこを起点に他の数値も強引に作っている
- co_varnamesが全てクエスチョン (
"?"
) になっている
- 構築されるコードオブジェクトとその引数を取得して動作を逆アセンブラと気合で解析し、フラグを逆算する
- (突然の宣伝) 筆者は既にPickle動的解析用のフレームワークを書いているのでそれで命令をフックして取得した
Prerequisite §
- Pickle
- Pythonのバイトコード
- コードオブジェクトについて
- ディスアセンブルの方法:
dis
モジュールのdis
関数等
Writeup §
配布ソースコードは次の通り
import sys
import pickle
# check your python version
if (v := sys.version_info) and v.major == 3 and v.minor == 11:
print("[+] version check: ok")
else:
print("[+] Requirement: Python 3.11")
print(" Please change the version of Python")
print(" Or use the Dockerfile in distfiles")
exit()
# get input and sanity check
inp = input("flag> ").encode("ascii")
if len(inp) != 64:
print("[!] check the length of flag")
exit(1)
for c in inp:
if c > 0x7f:
print("[!] ASCII printable only")
exit(1)
# rev time
hex_code = "8c0574797065738c0c46756e6374696f6e547970659372390500008c0574797065738c08436f64655479706593723a05000028284b024b00324d3905324b0043e697007c007c006b03000000007d027c007c02190000000000000000007d037c037c037a0a00007d047c047c047a0800007d057c057c057a0300007d067c057c067a0300007d077c077c057a0300007d087c077c057a0a00007d097c087c097a0a00007d0a7c087c067a0a00007d0b7c087c057a0a00007d0c7c047c057c067c097c077c0a7c0b7c0c67087d0d7c0467017c087a0500007d0e7c0d44005d227d0f7c0f7c087a0500007d107c107c087a0000007d1102007c017c007c107c11850219000000000000000000a6010000ab0100000000000000007c0e7c0f3c0000008c237c0e53002932288c086275696c74696e738c0767657461747472938c013f8c075f5f6d756c5f5f86524d39058552696275696c74696e730a7475706c650a8c037468658c0463616b658c0269734d390543016143036c69656c72a31c0000696275696c74696e730a7475706c650a817d86818c085f5f6d61696e5f5f8c03696e70938c086275696c74696e738c0767657461747472938c086275696c74696e738c03696e74938c0a66726f6d5f62797465738652865272193400003067313333370a67313333380a2867373333310a284b004b034b06432697007c007c0178027802190000000000000000007c027a190000630363023c0000007c00530075696275696c74696e730a7475706c650a817d86816731333333370a4b008a083713dec0adde371387523067313333370a67313333380a2867373333310a284b004b034b06433497007c007c02190000000000000000007c007c011900000000000000000063027c007c013c0000007c007c023c0000007c00530075696275696c74696e730a7475706c650a817d86816731333333370a4b024b0787523067313333370a67313333380a2867373333310a284b004b034b06434697007c007c01190000000000000000007d037c037c027a0a00007d047c047c047a0a00007d057c047c056b000000000072037c040b006e017c047c007c013c0000007c00530075696275696c74696e730a7475706c650a817d86816731333333370a4b048a0980808080808080800087523067313333370a67313333380a2867373333310a284b004b034b06438c97007c007c01190000000000000000007d037c037c037a0200007d047c047c027a0300007c047a0a00007d057c037c057a0100007d067c037c027a1600007d037c047c047a0000007d077c077c077a0500007c077a0000007d087c047c087a0300007d097c097c027a0a00007d0a7c037c067c0a7a0300007a1400007d037c037c007c013c0000007c00530075696275696c74696e730a7475706c650a817d86816731333333370a4b014b0d87523067313333370a67313333380a2867373333310a284b004b044b06432e97007c007c01190000000000000000007c007c02190000000000000000007a0c00007c007c033c0000007c00530075696275696c74696e730a7475706c650a817d8681286731333333370a4b014b064b0174523067313333370a67313333380a2867373333310a284b004b034b06433497007c007c01190000000000000000007c007c01190000000000000000007c027a0900007a0c00007c007c013c0000007c00530075696275696c74696e730a7475706c650a817d86816731333333370a4b054b0c87523067313333370a67313333380a2867373333310a284b004b034b06433497007c007c02190000000000000000007c007c011900000000000000000063027c007c013c0000007c007c023c0000007c00530075696275696c74696e730a7475706c650a817d86816731333333370a4b034b0687523067313333370a67313333380a2867373333310a284b004b024b0643f097007c007c007a0600007d027c007c007a0200007d037c037c037a0000007d047c037c047a0000007d057c047c047a0800007d067c047c057a0000007d077c057c047a0500007d087c067c047a0500007c037a0a00007d097c037c087a0300007d0a7c0a7c047a0500007d0b7c037c0a7a0300007c037a0a00007d0c7c097c087a0500007d0d7c097c0b7c0a7a0000007c037a0a00007a0500007d0e7c027c037c047c057c067c077c087c0967087d0f7c0f44005d1b7d107c017c10190000000000000000007c007a0c00007c017c103c0000007c0d7c007a0500007c0e7a0000007c0c7a0100007d008c1c7c01530075696275696c74696e730a7475706c650a817d86818a0831733173371337136731333333370a86523067313333370a67313333380a2867373333310a284b004b024b06430e97007c007c016b0200000000530075696275696c74696e730a7475706c650a817d86816731333333370a288a08681b8ed0fbbd6c418a0941c7497ece39ad89008a084aeddba97935e5158a095ea56bfce5024e9f008a0894076c3bc5a9d72b8a08376e39db1e2c8a6d8a0982c47dc69701a288008a080a5558e826cbfd656c86522e"
if pickle.loads(bytes.fromhex(hex_code)):
print("Congrats!! Submit your flag.")
else:
print("nope")
Pythonバージョンと文字数のチェックを突破すると、Pickleのバイトコードをloadしていることから内部でREDUCEやらで関数を実行していると推定できる。
いつものようにpickletools.pyを使うとクソ長い結果が得られるが気合で読む1。先頭の方を見ると次のようになっている。
0: \x8c SHORT_BINUNICODE 'types'
7: \x8c SHORT_BINUNICODE 'FunctionType'
21: \x93 STACK_GLOBAL
22: r LONG_BINPUT 1337
27: \x8c SHORT_BINUNICODE 'types'
34: \x8c SHORT_BINUNICODE 'CodeType'
44: \x93 STACK_GLOBAL
45: r LONG_BINPUT 1338
50: ( MARK
51: ( MARK
52: K BININT1 2
54: K BININT1 0
56: 2 DUP
57: M BININT2 1337
60: 2 DUP
61: K BININT1 0
63: C SHORT_BINBYTES b'\x97\x00|\x00|\x00k\x03\x00\x00\x00\x00}\x02|\x00|\x02\x19\x00\x00\x00\x00\x00\x00\x00\x00\x00}\x03|\x03|\x03z\n\x00\x00}\x04|\x04|\x04z\x08\x00\x00}\x05|\x05|\x05z\x03\x00\x00}\x06|\x05|\x06z\x03\x00\x00}\x07|\x07|\x05z\x03\x00\x00}\x08|\x07|\x05z\n\x00\x00}\t|\x08|\tz\n\x00\x00}\n|\x08|\x06z\n\x00\x00}\x0b|\x08|\x05z\n\x00\x00}\x0c|\x04|\x05|\x06|\t|\x07|\n|\x0b|\x0cg\x08}\r|\x04g\x01|\x08z\x05\x00\x00}\x0e|\rD\x00]"}\x0f|\x0f|\x08z\x05\x00\x00}\x10|\x10|\x08z\x00\x00\x00}\x11\x02\x00|\x01|\x00|\x10|\x11\x85\x02\x19\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00|\x0e|\x0f<\x00\x00\x00\x8c#|\x0eS\x00'
295: ) EMPTY_TUPLE
296: 2 DUP
297: ( MARK
298: \x8c SHORT_BINUNICODE 'builtins'
308: \x8c SHORT_BINUNICODE 'getattr'
317: \x93 STACK_GLOBAL
318: \x8c SHORT_BINUNICODE '?'
321: \x8c SHORT_BINUNICODE '__mul__'
330: \x86 TUPLE2
331: R REDUCE
332: M BININT2 1337
335: \x85 TUPLE1
336: R REDUCE
337: i INST 'builtins tuple' (MARK at 297)
353: \x8c SHORT_BINUNICODE 'the'
358: \x8c SHORT_BINUNICODE 'cake'
364: \x8c SHORT_BINUNICODE 'is'
368: M BININT2 1337
371: C SHORT_BINBYTES b'a'
374: C SHORT_BINBYTES b'lie'
379: l LIST (MARK at 51)
380: r LONG_BINPUT 7331
385: i INST 'builtins tuple' (MARK at 50)
401: \x81 NEWOBJ
402: } EMPTY_DICT
403: \x86 TUPLE2
404: \x81 NEWOBJ
Pythonのコードオブジェクトをtypes.CodeType
のインスタンスを作る形で構築して、types.FunctionType
に渡して関数を生成している事がわかる。dis.dis
を使うとこの関数を逆アセンブル出来るので確認すると次のようになっている。なお、そのままだとco_varnames
が("X" for _ in range(1337))
で非常に読みづらいため差し替えている。
1339 0 RESUME 0
2 LOAD_FAST 0 (var_0)
4 LOAD_FAST 0 (var_0)
6 COMPARE_OP 3 (!=)
12 STORE_FAST 2 (var_2)
14 LOAD_FAST 0 (var_0)
16 LOAD_FAST 2 (var_2)
18 BINARY_SUBSCR
28 STORE_FAST 3 (var_3)
30 LOAD_FAST 3 (var_3)
32 LOAD_FAST 3 (var_3)
34 BINARY_OP 10 (-)
38 STORE_FAST 4 (var_4)
40 LOAD_FAST 4 (var_4)
42 LOAD_FAST 4 (var_4)
44 BINARY_OP 8 (**)
48 STORE_FAST 5 (var_5)
50 LOAD_FAST 5 (var_5)
52 LOAD_FAST 5 (var_5)
54 BINARY_OP 3 (<<)
58 STORE_FAST 6 (var_6)
60 LOAD_FAST 5 (var_5)
62 LOAD_FAST 6 (var_6)
64 BINARY_OP 3 (<<)
68 STORE_FAST 7 (var_7)
70 LOAD_FAST 7 (var_7)
72 LOAD_FAST 5 (var_5)
74 BINARY_OP 3 (<<)
78 STORE_FAST 8 (var_8)
80 LOAD_FAST 7 (var_7)
82 LOAD_FAST 5 (var_5)
84 BINARY_OP 10 (-)
88 STORE_FAST 9 (var_9)
90 LOAD_FAST 8 (var_8)
92 LOAD_FAST 9 (var_9)
94 BINARY_OP 10 (-)
98 STORE_FAST 10 (var_10)
100 LOAD_FAST 8 (var_8)
102 LOAD_FAST 6 (var_6)
104 BINARY_OP 10 (-)
108 STORE_FAST 11 (var_11)
110 LOAD_FAST 8 (var_8)
112 LOAD_FAST 5 (var_5)
114 BINARY_OP 10 (-)
118 STORE_FAST 12 (var_12)
120 LOAD_FAST 4 (var_4)
122 LOAD_FAST 5 (var_5)
124 LOAD_FAST 6 (var_6)
126 LOAD_FAST 9 (var_9)
128 LOAD_FAST 7 (var_7)
130 LOAD_FAST 10 (var_10)
132 LOAD_FAST 11 (var_11)
134 LOAD_FAST 12 (var_12)
136 BUILD_LIST 8
138 STORE_FAST 13 (var_13)
140 LOAD_FAST 4 (var_4)
142 BUILD_LIST 1
144 LOAD_FAST 8 (var_8)
146 BINARY_OP 5 (*)
150 STORE_FAST 14 (var_14)
152 LOAD_FAST 13 (var_13)
154 GET_ITER
>> 156 FOR_ITER 34 (to 226)
158 STORE_FAST 15 (var_15)
160 LOAD_FAST 15 (var_15)
162 LOAD_FAST 8 (var_8)
164 BINARY_OP 5 (*)
168 STORE_FAST 16 (var_16)
170 LOAD_FAST 16 (var_16)
172 LOAD_FAST 8 (var_8)
174 BINARY_OP 0 (+)
178 STORE_FAST 17 (var_17)
180 PUSH_NULL
182 LOAD_FAST 1 (var_1)
184 LOAD_FAST 0 (var_0)
186 LOAD_FAST 16 (var_16)
188 LOAD_FAST 17 (var_17)
190 BUILD_SLICE 2
192 BINARY_SUBSCR
202 PRECALL 1
206 CALL 1
216 LOAD_FAST 14 (var_14)
218 LOAD_FAST 15 (var_15)
220 STORE_SUBSCR
224 JUMP_BACKWARD 35 (to 156)
>> 226 LOAD_FAST 14 (var_14)
228 RETURN_VALUE
序盤に長い処理があり、続いてループがあるという構造になっている。序盤の処理はco_consts
が空のタプルであることからLOAD_CONST
で取得できないため、intの引数を利用して気合で作っている。特に次のようにして0と1を作れば後は四則演算の組み合わせで幾らでも作ることが出来るため最序盤にこれを作るコードが存在している。
- 0:
x - x
, 後にx % x
も登場 - 1:
x // x
このコードを人力デコンパイルして動作を把握すると、だいたい次のような事をしている。
def f(var_0, var_1):
var_2 = var_0 != var_0 # always False
var_3 = var_0[False] # = var_0[0]
var_4 = var_3 - var_3 # always 0
var_5 = var_4 ** var_4 # always 0**0 (= 1 at python)
var_6 = var_5 << var_5 # 1 << 1 = 2
var_7 = var_5 << var_6 # 1 << 2 = 4
var_8 = var_7 << var_5 # 4 << 1 = 8
var_9 = var_7 - var_5 # 7 - 5 = 3
var_10 = var_8 - var_9 # 8 - 3 = 5
var_11 = var_8 - var_6 # 8 - 2 = 6
var_12 = var_8 - var_5 # 8 - 1 = 7
var_13 = [
var_4,
var_5,
var_6,
var_9,
var_7,
var_10,
var_11,
var_12] # = [0, 1, 2, 3, 4, 5, 6, 7]
var_14 = [var_4] * var_8 # [0, 0, 0, 0, 0, 0, 0, 0]
for var_15 in var_13:
var_16 = var_8 * var_15 # 8*var_15
var_17 = var_16 + var_8 # 8*var_15+8
var_14[var_15] = var_1(var_0[var_16:var_17])
return var_14 # the element on TOS is returned
もう少しわかりやすくすると次のような単純な処理になる。
def f(l, f):
ret = []
for i in range(8):
j = 8*i
k = j+8 # 8*(i+1)
ret[i] = f(l[j:k])
return ret
2つの引数をとって、8要素ずつ何らかの関数を通した値の配列を返している。
なお、pycdcのようなデコンパイラを使おうとしてもPython 3.11には対応していないため、未対応命令に到達するまで (list(range(8))
に相当するものを作る) しかデコンパイルされない。今後同じようにして関数が作られていくが、幾つかの関数 (おそらく Specializing Adaptive Interpreter に関連する命令を含んでいるもの) に関しても同様のことが起こっている。
この関数に渡す引数が何かを確認すると次のようになっている。
405: \x8c SHORT_BINUNICODE '__main__'
415: \x8c SHORT_BINUNICODE 'inp'
420: \x93 STACK_GLOBAL
421: \x8c SHORT_BINUNICODE 'builtins'
431: \x8c SHORT_BINUNICODE 'getattr'
440: \x93 STACK_GLOBAL
441: \x8c SHORT_BINUNICODE 'builtins'
451: \x8c SHORT_BINUNICODE 'int'
456: \x93 STACK_GLOBAL
457: \x8c SHORT_BINUNICODE 'from_bytes'
469: \x86 TUPLE2
470: R REDUCE
471: \x86 TUPLE2
472: R REDUCE
inp
はソースコード中にあるinp
であり、それとint.from_bytes
を渡していることから入力を8バイトずつ区切ってint.from_bytes
でビッグエンディアンの数値に変換した配列としている。
これ以後は次のような処理が続く。
479: g GET 1337
485: g GET 1338
491: ( MARK
492: g GET 7331
498: ( MARK
499: K BININT1 0
501: K BININT1 3
503: K BININT1 6
505: C SHORT_BINBYTES b'\x97\x00|\x00|\x01x\x02x\x02\x19\x00\x00\x00\x00\x00\x00\x00\x00\x00|\x02z\x19\x00\x00c\x03c\x02<\x00\x00\x00|\x00S\x00'
545: u SETITEMS (MARK at 498)
546: i INST 'builtins tuple' (MARK at 491)
562: \x81 NEWOBJ
563: } EMPTY_DICT
564: \x86 TUPLE2
565: \x81 NEWOBJ
メモからインデックス7331で引っ張ってきたリスト (↑でCodeオブジェクトを作る際に使ったもの) に対し数値とバイト列をそれぞれインデックス0, 6で代入している (SETITEMSオペコード)。これはそれぞれコードオブジェクトのco_argcount
と co_codestring
に対応することから、これらを変えながらコードオブジェクトと関数を構築し、引数を渡して実行しながらフラグをチェックしている。
ここまで分かればバイトコードと引数の数を抽出して同じように処理を把握するだけである。詳しくはCode節で掲載しているスクリプトに譲るが、intの配列に対してスワップやXOR、ビットローテート、減算の後に絶対値を取得、glibc heapに登場するsafe-linkingのようなビットシフトとXORといった処理が確認出来る。
最後にハードコードされたリストとこれらの処理が施されたリストを比較した結果を返し、これがTrueであればフラグとなる。
Code §
Pythonバイトコードの逆アセンブル結果取得 §
from pickaxe import CustomUnpickler
import types
hex_code = "8c0574797065738c0c46756e6374696f6e547970659372390500008c0574797065738c08436f64655479706593723a05000028284b024b00324d3905324b0043e697007c007c006b03000000007d027c007c02190000000000000000007d037c037c037a0a00007d047c047c047a0800007d057c057c057a0300007d067c057c067a0300007d077c077c057a0300007d087c077c057a0a00007d097c087c097a0a00007d0a7c087c067a0a00007d0b7c087c057a0a00007d0c7c047c057c067c097c077c0a7c0b7c0c67087d0d7c0467017c087a0500007d0e7c0d44005d227d0f7c0f7c087a0500007d107c107c087a0000007d1102007c017c007c107c11850219000000000000000000a6010000ab0100000000000000007c0e7c0f3c0000008c237c0e53002932288c086275696c74696e738c0767657461747472938c013f8c075f5f6d756c5f5f86524d39058552696275696c74696e730a7475706c650a8c037468658c0463616b658c0269734d390543016143036c69656c72a31c0000696275696c74696e730a7475706c650a817d86818c085f5f6d61696e5f5f8c03696e70938c086275696c74696e738c0767657461747472938c086275696c74696e738c03696e74938c0a66726f6d5f62797465738652865272193400003067313333370a67313333380a2867373333310a284b004b034b06432697007c007c0178027802190000000000000000007c027a190000630363023c0000007c00530075696275696c74696e730a7475706c650a817d86816731333333370a4b008a083713dec0adde371387523067313333370a67313333380a2867373333310a284b004b034b06433497007c007c02190000000000000000007c007c011900000000000000000063027c007c013c0000007c007c023c0000007c00530075696275696c74696e730a7475706c650a817d86816731333333370a4b024b0787523067313333370a67313333380a2867373333310a284b004b034b06434697007c007c01190000000000000000007d037c037c027a0a00007d047c047c047a0a00007d057c047c056b000000000072037c040b006e017c047c007c013c0000007c00530075696275696c74696e730a7475706c650a817d86816731333333370a4b048a0980808080808080800087523067313333370a67313333380a2867373333310a284b004b034b06438c97007c007c01190000000000000000007d037c037c037a0200007d047c047c027a0300007c047a0a00007d057c037c057a0100007d067c037c027a1600007d037c047c047a0000007d077c077c077a0500007c077a0000007d087c047c087a0300007d097c097c027a0a00007d0a7c037c067c0a7a0300007a1400007d037c037c007c013c0000007c00530075696275696c74696e730a7475706c650a817d86816731333333370a4b014b0d87523067313333370a67313333380a2867373333310a284b004b044b06432e97007c007c01190000000000000000007c007c02190000000000000000007a0c00007c007c033c0000007c00530075696275696c74696e730a7475706c650a817d8681286731333333370a4b014b064b0174523067313333370a67313333380a2867373333310a284b004b034b06433497007c007c01190000000000000000007c007c01190000000000000000007c027a0900007a0c00007c007c013c0000007c00530075696275696c74696e730a7475706c650a817d86816731333333370a4b054b0c87523067313333370a67313333380a2867373333310a284b004b034b06433497007c007c02190000000000000000007c007c011900000000000000000063027c007c013c0000007c007c023c0000007c00530075696275696c74696e730a7475706c650a817d86816731333333370a4b034b0687523067313333370a67313333380a2867373333310a284b004b024b0643f097007c007c007a0600007d027c007c007a0200007d037c037c037a0000007d047c037c047a0000007d057c047c047a0800007d067c047c057a0000007d077c057c047a0500007d087c067c047a0500007c037a0a00007d097c037c087a0300007d0a7c0a7c047a0500007d0b7c037c0a7a0300007c037a0a00007d0c7c097c087a0500007d0d7c097c0b7c0a7a0000007c037a0a00007a0500007d0e7c027c037c047c057c067c077c087c0967087d0f7c0f44005d1b7d107c017c10190000000000000000007c007a0c00007c017c103c0000007c0d7c007a0500007c0e7a0000007c0c7a0100007d008c1c7c01530075696275696c74696e730a7475706c650a817d86818a0831733173371337136731333333370a86523067313333370a67313333380a2867373333310a284b004b024b06430e97007c007c016b0200000000530075696275696c74696e730a7475706c650a817d86816731333333370a288a08681b8ed0fbbd6c418a0941c7497ece39ad89008a084aeddba97935e5158a095ea56bfce5024e9f008a0894076c3bc5a9d72b8a08376e39db1e2c8a6d8a0982c47dc69701a288008a080a5558e826cbfd656c86522e"
functions = {}
prev_f_idx = -1
# extract code objects
class PytecodeUnpickler(CustomUnpickler):
def load_reduce(self):
f: types.FunctionType = self.stack[-2]
args = self.stack[-1]
if hasattr(f, "__code__"):
code = f.__code__
if code.co_filename == "the":
functions[self.ip] = (f, args)
super().load_reduce()
def change_varnames(self: CustomUnpickler):
self.stack[-1] = tuple(f"var_{i}" for i in range(1337))
up = PytecodeUnpickler(bytes.fromhex(hex_code))
up.set_breakpoint(353, change_varnames)
inp = b"X" * 0x40
up.load()
import dis, marshal, subprocess
pycdc_path = "/path/to/pycdc"
for idx, (f, args) in functions.items():
print(f"[+] Called at {idx}: f{args}")
dis.dis(f)
print("=" * 0x60)
cd = marshal.dumps(f.__code__)
marshaled_name = f"./marshaled/func_{idx}"
with open(marshaled_name, "wb") as _f:
_f.write(cd)
decomp_f = open(marshaled_name+".py", "w")
res = subprocess.run([pycdc_path, "-c", "-v", "3.11", marshaled_name], stdout=decomp_f)
decomp_f.close()
フラグの取得 §
import pickletools, pickle
hex_code = "8c0574797065738c0c46756e6374696f6e547970659372390500008c0574797065738c08436f64655479706593723a05000028284b024b00324d3905324b0043e697007c007c006b03000000007d027c007c02190000000000000000007d037c037c037a0a00007d047c047c047a0800007d057c057c057a0300007d067c057c067a0300007d077c077c057a0300007d087c077c057a0a00007d097c087c097a0a00007d0a7c087c067a0a00007d0b7c087c057a0a00007d0c7c047c057c067c097c077c0a7c0b7c0c67087d0d7c0467017c087a0500007d0e7c0d44005d227d0f7c0f7c087a0500007d107c107c087a0000007d1102007c017c007c107c11850219000000000000000000a6010000ab0100000000000000007c0e7c0f3c0000008c237c0e53002932288c086275696c74696e738c0767657461747472938c013f8c075f5f6d756c5f5f86524d39058552696275696c74696e730a7475706c650a8c037468658c0463616b658c0269734d390543016143036c69656c72a31c0000696275696c74696e730a7475706c650a817d86818c085f5f6d61696e5f5f8c03696e70938c086275696c74696e738c0767657461747472938c086275696c74696e738c03696e74938c0a66726f6d5f62797465738652865272193400003067313333370a67313333380a2867373333310a284b004b034b06432697007c007c0178027802190000000000000000007c027a190000630363023c0000007c00530075696275696c74696e730a7475706c650a817d86816731333333370a4b008a083713dec0adde371387523067313333370a67313333380a2867373333310a284b004b034b06433497007c007c02190000000000000000007c007c011900000000000000000063027c007c013c0000007c007c023c0000007c00530075696275696c74696e730a7475706c650a817d86816731333333370a4b024b0787523067313333370a67313333380a2867373333310a284b004b034b06434697007c007c01190000000000000000007d037c037c027a0a00007d047c047c047a0a00007d057c047c056b000000000072037c040b006e017c047c007c013c0000007c00530075696275696c74696e730a7475706c650a817d86816731333333370a4b048a0980808080808080800087523067313333370a67313333380a2867373333310a284b004b034b06438c97007c007c01190000000000000000007d037c037c037a0200007d047c047c027a0300007c047a0a00007d057c037c057a0100007d067c037c027a1600007d037c047c047a0000007d077c077c077a0500007c077a0000007d087c047c087a0300007d097c097c027a0a00007d0a7c037c067c0a7a0300007a1400007d037c037c007c013c0000007c00530075696275696c74696e730a7475706c650a817d86816731333333370a4b014b0d87523067313333370a67313333380a2867373333310a284b004b044b06432e97007c007c01190000000000000000007c007c02190000000000000000007a0c00007c007c033c0000007c00530075696275696c74696e730a7475706c650a817d8681286731333333370a4b014b064b0174523067313333370a67313333380a2867373333310a284b004b034b06433497007c007c01190000000000000000007c007c01190000000000000000007c027a0900007a0c00007c007c013c0000007c00530075696275696c74696e730a7475706c650a817d86816731333333370a4b054b0c87523067313333370a67313333380a2867373333310a284b004b034b06433497007c007c02190000000000000000007c007c011900000000000000000063027c007c013c0000007c007c023c0000007c00530075696275696c74696e730a7475706c650a817d86816731333333370a4b034b0687523067313333370a67313333380a2867373333310a284b004b024b0643f097007c007c007a0600007d027c007c007a0200007d037c037c037a0000007d047c037c047a0000007d057c047c047a0800007d067c047c057a0000007d077c057c047a0500007d087c067c047a0500007c037a0a00007d097c037c087a0300007d0a7c0a7c047a0500007d0b7c037c0a7a0300007c037a0a00007d0c7c097c087a0500007d0d7c097c0b7c0a7a0000007c037a0a00007a0500007d0e7c027c037c047c057c067c077c087c0967087d0f7c0f44005d1b7d107c017c10190000000000000000007c007a0c00007c017c103c0000007c0d7c007a0500007c0e7a0000007c0c7a0100007d008c1c7c01530075696275696c74696e730a7475706c650a817d86818a0831733173371337136731333333370a86523067313333370a67313333380a2867373333310a284b004b024b06430e97007c007c016b0200000000530075696275696c74696e730a7475706c650a817d86816731333333370a288a08681b8ed0fbbd6c418a0941c7497ece39ad89008a084aeddba97935e5158a095ea56bfce5024e9f008a0894076c3bc5a9d72b8a08376e39db1e2c8a6d8a0982c47dc69701a288008a080a5558e826cbfd656c86522e"
payload = bytes.fromhex(hex_code)
pickletools.dis(payload)
extract_list = payload[:1854] + pickle.STOP
inp = b"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
l = pickle.loads(extract_list)
print(l)
ctr_init = 1384596539316466481
_pts = []
for i in range(8):
_pts.append(l[i] ^ ctr_init)
ctr_init = (42 * ctr_init + 1337) & ((1 << 64)-1)
_pts[3], _pts[6] = _pts[6], _pts[3]
x = _pts[5]
known_bits = 0
while known_bits < 64:
known_bits = min(known_bits + 12, 64)
known_mask = ((1 << known_bits) - 1) << (64 - known_bits)
known = x & known_mask
key = known >> 12
x = _pts[5] ^ key
_pts[5] = x
_pts[1] = _pts[1] ^ _pts[6]
mask = (1 << 61) - 1
msb13 = _pts[1] >> (64-13)
_pts[1] = (_pts[1] << 13) & mask
_pts[1] |= msb13
_pts[4] = -_pts[4] + 0x8080808080808080
_pts[2], _pts[7] = _pts[7], _pts[2]
_pts[0] ^= 0x1337deadc0de1337
from Crypto.Util.number import long_to_bytes
print(b"".join(long_to_bytes(pt) for pt in _pts))
Flag §
Alpaca{not_only_the_cake,_varnames,_filenames_and_etc._are_lies}
実はpickletools.pyも完全ではなく難読化を施されると途中で中断したりエラーを吐いて死ぬことがあるので、自作の実行時にログを取得するツールを使った