ก่อนอื่นมีวิธีแฮ็คที่น้อยกว่ามาก สิ่งที่เราต้องการทำคือเปลี่ยนสิ่งที่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)
หรือในทำนองเดียวกันคุณสามารถ monkeypatch แทนsys.stdout
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
s ที่กินสแต็กทั้งหมด, RuntimeError
s ปกติมากขึ้นที่สามารถจัดการได้หรือค่าขยะที่อาจเพิ่มTypeError
หรือAttributeError
เมื่อคุณพยายามที่จะใช้พวกเขา ตัวอย่างเช่นลองสร้างวัตถุรหัสที่มีเพียงRETURN_VALUE
ไม่มีอะไรในสแต็ก (bytecode b'S\0'
สำหรับ 3.6+ b'S'
ก่อน) หรือกับ tuple ที่ว่างเปล่าco_consts
เมื่อมีLOAD_CONST 0
ใน bytecode หรือvarnames
ลดลง 1 โดยที่สูงที่สุดLOAD_FAST
จริงโหลด freevar / เซลล์ cellvar เพื่อความสนุกที่แท้จริงหากคุณทำlnotab
ผิดพลาดรหัสของคุณจะเป็น segfault เมื่อทำงานในโปรแกรมดีบั๊กเท่านั้น
การใช้bytecode
หรือbyteplay
ไม่ป้องกันคุณจากปัญหาเหล่านั้นทั้งหมด แต่มีการตรวจสอบขั้นพื้นฐานบางอย่างและผู้ช่วยที่ดีที่ให้คุณทำสิ่งต่าง ๆ เช่นใส่รหัสขนาดยาวและให้กังวลเกี่ยวกับการอัปเดตและฉลากทั้งหมดเพื่อให้คุณสามารถ ไม่เข้าใจผิดและอื่น ๆ (นอกจากนี้พวกเขายังป้องกันไม่ให้คุณพิมพ์ในตัวสร้าง 6 บรรทัดที่ไร้สาระและต้องแก้จุดบกพร่องที่ผิดพลาดที่มาจากการทำเช่นนั้น)
ตอนนี้ไปที่ # 2
ฉันพูดถึงว่ารหัสวัตถุไม่เปลี่ยนรูป และแน่นอนว่า consts นั้นเป็นสิ่งอันดับดังนั้นเราจึงไม่สามารถเปลี่ยนแปลงได้โดยตรง และสิ่งที่อยู่ใน const tuple คือสตริงซึ่งเราก็ไม่สามารถเปลี่ยนแปลงได้โดยตรง นั่นเป็นเหตุผลที่ฉันต้องสร้างสตริงใหม่เพื่อสร้าง tuple ใหม่เพื่อสร้างวัตถุรหัสใหม่
แต่ถ้าคุณสามารถเปลี่ยนสตริงได้โดยตรง
ลึกพอที่จะครอบคลุมทุกอย่างเป็นเพียงตัวชี้ไปยังข้อมูล C ใช่ไหม? หากคุณกำลังใช้ CPython มีซี API เพื่อการเข้าถึงวัตถุและคุณสามารถใช้ctypes
เพื่อเข้าถึง 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"