Pertama, sebenarnya ada cara yang jauh lebih tidak retas. Yang ingin kami lakukan adalah mengubah printcetakan apa , bukan?
_print = print
def print(*args, **kw):
args = (arg.replace('cat', 'dog') if isinstance(arg, str) else arg
for arg in args)
_print(*args, **kw)
Atau, sama, Anda dapat monkeypatch sys.stdoutbukan print.
Juga, tidak ada yang salah dengan exec … getsource …idenya. Yah, tentu saja ada banyak yang salah dengan itu, tetapi kurang dari apa yang terjadi di sini ...
Tetapi jika Anda ingin memodifikasi konstanta kode objek fungsi, kita bisa melakukannya.
Jika Anda benar-benar ingin bermain-main dengan objek kode secara nyata, Anda harus menggunakan perpustakaan seperti bytecode(ketika selesai) atau byteplay(sampai saat itu, atau untuk versi Python yang lebih lama) daripada melakukannya secara manual. Bahkan untuk sesuatu yang sepele ini, CodeTypepenginisialisasi adalah rasa sakit; jika Anda benar-benar perlu melakukan hal-hal seperti memperbaiki lnotab, hanya orang gila yang akan melakukannya secara manual.
Juga, tidak perlu dikatakan bahwa tidak semua implementasi Python menggunakan objek kode gaya CPython. Kode ini akan bekerja di CPython 3.7, dan mungkin semua versi kembali ke setidaknya 2.2 dengan beberapa perubahan kecil (dan bukan hal peretasan kode, tetapi hal-hal seperti ekspresi generator), tetapi tidak akan berfungsi dengan versi IronPython apa pun.
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()
Apa yang salah dengan meretas objek kode? Sebagian besar hanya segfault, RuntimeErrors yang memakan seluruh tumpukan, lebih banyak RuntimeErrors normal yang dapat ditangani, atau nilai-nilai sampah yang mungkin hanya akan menaikkan TypeErroratau AttributeErrorketika Anda mencoba menggunakannya. Sebagai contoh, coba buat objek kode hanya RETURN_VALUEdengan tanpa apa-apa di stack (bytecode b'S\0'for 3.6+, b'S'sebelumnya), atau dengan tuple kosong co_constsketika ada LOAD_CONST 0dalam bytecode, atau dengan varnamesdecremented oleh 1 sehingga yang tertinggi LOAD_FASTsebenarnya memuat freevar / sel cellvar. Untuk bersenang-senang nyata, jika Anda mendapatkan lnotabkesalahan yang cukup, kode Anda hanya akan segfault ketika dijalankan di debugger.
Menggunakan bytecodeatau byteplaytidak akan melindungi Anda dari semua masalah itu, tetapi mereka memang memiliki beberapa pemeriksaan kewarasan dasar, dan pembantu yang baik yang memungkinkan Anda melakukan hal-hal seperti memasukkan sepotong kode dan membiarkannya khawatir tentang memperbarui semua offset dan label sehingga Anda dapat ' jangan salah, dan sebagainya. (Plus, mereka membuat Anda tidak perlu mengetikkan konstruktor 6-garis konyol itu, dan harus men-debug kesalahan ketik konyol yang muncul karena melakukan hal itu.)
Sekarang ke # 2.
Saya menyebutkan bahwa objek kode tidak dapat diubah. Dan tentu saja const adalah tuple, jadi kita tidak bisa mengubahnya secara langsung. Dan hal dalam tuple const adalah string, yang juga tidak dapat kita ubah secara langsung. Itu sebabnya saya harus membuat string baru untuk membangun tuple baru untuk membangun objek kode baru.
Tetapi bagaimana jika Anda bisa mengubah string secara langsung?
Nah, cukup dalam di bawah selimut, semuanya hanya sebuah penunjuk ke beberapa data C, kan? Jika Anda menggunakan CPython, ada API C untuk mengakses objek , dan Anda dapat menggunakannya ctypesuntuk mengakses API dari dalam Python itu sendiri, yang merupakan ide yang mengerikan sehingga mereka menempatkannya pythonapidi sana di ctypesmodul stdlib . :) Trik paling penting yang perlu Anda ketahui adalah itu id(x)adalah pointer aktual ke xdalam memori (sebagaiint ).
Sayangnya, API C untuk string tidak akan membiarkan kami dengan aman mendapatkan penyimpanan internal dari string yang sudah beku. Jadi sekrup aman, mari kita baca file header dan menemukan penyimpanan itu sendiri.
Jika Anda menggunakan CPython 3.4 - 3.7 (berbeda untuk versi yang lebih lama, dan siapa yang tahu untuk masa depan), string literal dari modul yang terbuat dari ASCII murni akan disimpan menggunakan format ASCII yang ringkas, yang berarti berakhir lebih awal dan buffer byte ASCII segera menyusul dalam memori. Ini akan pecah (seperti dalam mungkin segfault) jika Anda meletakkan karakter non-ASCII dalam string, atau jenis string non-literal tertentu, tetapi Anda dapat membaca tentang 4 cara lain untuk mengakses buffer untuk berbagai jenis string.
Untuk mempermudah, saya menggunakan superhackyinternalsproyek dari GitHub saya. (Sengaja tidak dapat diinstal melalui pip karena Anda benar-benar tidak boleh menggunakan ini kecuali untuk bereksperimen dengan penerjemah lokal Anda dan sejenisnya.)
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()
Jika Anda ingin bermain dengan barang-barang ini, intjauh lebih sederhana di bawah selimut daripada str. Dan jauh lebih mudah untuk menebak apa yang bisa Anda hancurkan dengan mengubah nilai 2to 1, kan? Sebenarnya, lupakan membayangkan, mari kita lakukan saja (menggunakan tipe dari superhackyinternalslagi):
>>> 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
... berpura-pura bahwa kotak kode memiliki bilah gulir panjang tak terbatas.
Saya mencoba hal yang sama di IPython, dan pertama kali saya mencoba untuk mengevaluasi 2pada prompt, itu masuk ke semacam loop tak terbatas yang tidak terputus. Mungkin itu menggunakan nomor 2untuk sesuatu dalam loop REPL, sedangkan penerjemah saham tidak?
42menjadi23daripada mengapa itu adalah ide yang buruk untuk mengubah nilai"My name is Y"menjadi"My name is X".