pyjail wiki

TSJ CTF 2022 - Just a pyjail

Challenge

code object の操作が可能な環境。

import marshal
code = input()
exec(code)

Solution

marshal によるコード操作

import marshal
import 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 dis
dis.opmap['LOAD_CONST'] # 100
dis.opmap['RETURN_VALUE'] # 83

OOB の仕組み (修正済み)

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