最初に、実際にはそれほどハックされない方法があります。私たちがやりたいことは、print
プリントを変更することですよね?
_print = print
def print(*args, **kw):
args = (arg.replace('cat', 'dog') if isinstance(arg, str) else arg
for arg in args)
_print(*args, **kw)
または、同様に、のsys.stdout
代わりにmonkeypatchを使用できますprint
。
また、exec … getsource …
アイデアに問題はありません。まあ、もちろんそれには多くの誤りがありますが、ここに続くものよりは少ない…
しかし、関数オブジェクトのコード定数を変更したい場合は、それを行うことができます。
実際にコードオブジェクトをいじりたい場合は、手動で行うのではなく、(bytecode
終了するとき)またはbyteplay
(それまで、または古いバージョンのPythonの場合)などのライブラリを使用する必要があります。些細なことでも、CodeType
初期化子は苦痛です。実際に修正などを行う必要がある場合はlnotab
、狂人だけが手動で修正します。
また、すべてのPython実装がCPythonスタイルのコードオブジェクトを使用するわけではありません。このコードはCPython 3.7で機能し、おそらくすべてのバージョンが少なくとも2.2に戻り、いくつかのマイナーな変更が加えられます(コードハッキングではなく、ジェネレーターの式など)。ただし、IronPythonのどのバージョンでも機能しません。
import types
def print_function():
print ("This cat was scared.")
def main():
# A function object is a wrapper around a code object, with
# a bit of extra stuff like default values and closure cells.
# See inspect module docs for more details.
co = print_function.__code__
# A code object is a wrapper around a string of bytecode, with a
# whole bunch of extra stuff, including a list of constants used
# by that bytecode. Again see inspect module docs. Anyway, inside
# the bytecode for string (which you can read by typing
# dis.dis(string) in your REPL), there's going to be an
# instruction like LOAD_CONST 1 to load the string literal onto
# the stack to pass to the print function, and that works by just
# reading co.co_consts[1]. So, that's what we want to change.
consts = tuple(c.replace("cat", "dog") if isinstance(c, str) else c
for c in co.co_consts)
# Unfortunately, code objects are immutable, so we have to create
# a new one, copying over everything except for co_consts, which
# we'll replace. And the initializer has a zillion parameters.
# Try help(types.CodeType) at the REPL to see the whole list.
co = types.CodeType(
co.co_argcount, co.co_kwonlyargcount, co.co_nlocals,
co.co_stacksize, co.co_flags, co.co_code,
consts, co.co_names, co.co_varnames, co.co_filename,
co.co_name, co.co_firstlineno, co.co_lnotab,
co.co_freevars, co.co_cellvars)
print_function.__code__ = co
print_function()
main()
コードオブジェクトのハッキングで何が問題になるのでしょうか?セグメンテーション違反、ほとんどがちょうどRuntimeError
スタック全体を食べるの、より正常なRuntimeError
おそらくちょうど引き上げる扱うことができ、S、またはごみ値TypeError
またはAttributeError
あなたがそれらを使用しようとします。例RETURN_VALUE
として、スタックに何もない(b'S\0'
3.6+ b'S'
以前のバイトコード)、またはバイトコードにco_consts
aがある場合は空のタプルを使用するLOAD_CONST 0
か、varnames
1 LOAD_FAST
ずつデクリメントしてコードオブジェクトを作成してみてください。/ cellvarセル。実際の楽しみとして、lnotab
十分に間違えた場合、コードはデバッガーで実行されたときにのみsegfaultになります。
これらのすべての問題を使用するbytecode
かbyteplay
、保護しませんが、いくつかの基本的な健全性チェックと、コードのチャンクの挿入などの操作を可能にする優れたヘルパーがあり、すべてのオフセットとラベルの更新を心配できるため、 t間違えるなど。(さらに、ばかげた6行のコンストラクターを入力したり、そうしたことによる愚かなタイプミスをデバッグしたりする必要がなくなります。)
次に#2に進みます。
コードオブジェクトは不変であると述べました。もちろん、constはタプルなので、直接変更することはできません。また、constタプルの内容は文字列であり、直接変更することもできません。新しいコードオブジェクトを作成するために、新しい文字列を作成して新しいタプルを作成する必要があったのはそのためです。
しかし、文字列を直接変更できるとしたらどうでしょう。
ええと、カバーの下で十分に深く、すべてはいくつかのCデータへの単なるポインタですよね?あなたはCPythonのを使用している場合は、がありますCのAPIは、オブジェクトがアクセスすると、あなたは使用することができctypes
、彼らが置かれるように恐ろしい考えであるのPython自体、内部からのアクセスそのAPIにpythonapi
STDLIBの中で右がctypes
モジュール。:)あなたが知る必要がある最も重要なトリックid(x)
は、それがx
(としてint
)メモリ内の実際のポインタであることです。
残念ながら、文字列用のC APIでは、既に凍結された文字列の内部ストレージに安全にアクセスできません。安全にねじ込みます。ヘッダーファイルを読んで、自分でそのストレージを見つけましょう。
CPython 3.4-3.7を使用している場合(古いバージョンとは異なり、将来的には誰が知っているか)、純粋なASCIIで作成されたモジュールからの文字列リテラルは、コンパクトなASCII形式を使用して格納されます。メモリ内ですぐに終了し、ASCIIバイトのバッファがすぐに続きます。これは(おそらくsegfaultのように)文字列に非ASCII文字を入れたり、特定の種類の非リテラル文字列を入れたりすると壊れますが、さまざまな種類の文字列のバッファにアクセスする他の4つの方法を読むことができます。
少し簡単にするために、私はsuperhackyinternals
GitHub以外のプロジェクトを使用しています。(インタープリターのローカルビルドなどを試す場合を除いて、これを実際に使用するべきではないため、意図的にpipでインストールできません。)
import ctypes
import internals # https://github.com/abarnert/superhackyinternals/blob/master/internals.py
def print_function():
print ("This cat was scared.")
def main():
for c in print_function.__code__.co_consts:
if isinstance(c, str):
idx = c.find('cat')
if idx != -1:
# Too much to explain here; just guess and learn to
# love the segfaults...
p = internals.PyUnicodeObject.from_address(id(c))
assert p.compact and p.ascii
addr = id(c) + internals.PyUnicodeObject.utf8_length.offset
buf = (ctypes.c_int8 * 3).from_address(addr + idx)
buf[:3] = b'dog'
print_function()
main()
あなたがこのようなもので遊びたいのであれば、int
はカバーするよりもずっと簡単ですstr
。そして、の値を2
に変更することで何を壊すことができるかを推測する方がはるかに簡単ですよね1
?実際には、想像するのを忘れて、それをやりましょう(superhackyinternals
もう一度からの型を使用):
>>> n = 2
>>> pn = PyLongObject.from_address(id(n))
>>> pn.ob_digit[0]
2
>>> pn.ob_digit[0] = 1
>>> 2
1
>>> n * 3
3
>>> i = 10
>>> while i < 40:
... i *= 2
... print(i)
10
10
10
…コードボックスに無限長のスクロールバーがあるとしましょう。
私はIPythonで同じことを試しましたが、最初に2
プロンプトで評価しようとしたとき、それはある種の中断できない無限ループに入りました。おそらくそれ2
はREPLループで何かの数を使用していますが、株式インタープリターはそうではありませんか?
42
と23
、それはの値を変更するために悪い考えですなぜより"My name is Y"
にします"My name is X"
。