सबसे पहले, वहाँ वास्तव में एक बहुत कम hacky तरीका है। हम सब करना चाहते हैं क्या 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
बजाय बंदरों को पकड़ सकते हैं print
।
इसके अलावा, exec … getsource …
विचार के साथ कुछ भी गलत नहीं है । ठीक है, निश्चित रूप से इसके साथ बहुत गलत है, लेकिन यहाँ क्या है की तुलना में कम ...
लेकिन यदि आप फ़ंक्शन ऑब्जेक्ट के कोड कॉन्स्टेंट को संशोधित करना चाहते हैं, तो हम ऐसा कर सकते हैं।
यदि आप वास्तव में असली के लिए कोड ऑब्जेक्ट्स के साथ खेलना चाहते हैं, तो आपको मैन्युअल रूप से करने के बजाय एक लाइब्रेरी का उपयोग करना चाहिए bytecode
( जैसे कि यह समाप्त हो गया है) या byteplay
(तब तक, या पुराने पायथन संस्करणों के लिए)। यहां तक कि कुछ के लिए यह तुच्छ, CodeType
इनिशियलाइज़र एक दर्द है; यदि आपको वास्तव में सामान को ठीक करने की आवश्यकता है lnotab
, तो केवल एक चंचल व्यक्ति स्वयं ऐसा करेगा।
इसके अलावा, यह कहे बिना जाता है कि सभी पायथन कार्यान्वयन CPython- शैली कोड ऑब्जेक्ट का उपयोग नहीं करते हैं। यह कोड CPython 3.7 में काम करेगा, और शायद सभी संस्करण कुछ मामूली बदलावों के साथ कम से कम 2.2 में वापस आ जाएंगे (और कोड-हैकिंग सामान नहीं, लेकिन जनरेटर के भाव जैसी चीजें), लेकिन यह आयरनपिथॉन के किसी भी संस्करण के साथ काम नहीं करेगा।
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()
कोड वस्तुओं को हैक करने में क्या गलत हो सकता है? ज्यादातर सिर्फ segfaults, RuntimeError
s जो पूरे स्टैक को खाते हैं, अधिक सामान्य RuntimeError
s जो संभाला जा सकता है, या कचरा मान जो शायद सिर्फ एक को उठाएगा TypeError
या AttributeError
जब आप उनका उपयोग करने का प्रयास करेंगे। उदाहरण के लिए, RETURN_VALUE
स्टैक पर कुछ भी नहीं के साथ एक कोड ऑब्जेक्ट बनाने का प्रयास करें (बायटेकोड b'S\0'
3.6+ के लिए, b'S'
इससे पहले), या co_consts
जब बायटेकोड LOAD_CONST 0
में है, या varnames
1 से घटाया गया है, तो सबसे अधिक LOAD_FAST
वास्तव में एक फ्रीवर लोड करता है के लिए एक खाली ट्यूपल के साथ / सेलवार सेल। कुछ वास्तविक मौज-मस्ती के लिए, यदि आप lnotab
गलत गलत करते हैं, तो आपका कोड केवल डिबगर में चलने पर सीगफॉल्ट होगा।
का उपयोग करना bytecode
या byteplay
उन समस्याओं के सभी से बचाने नहीं होगा, लेकिन वे कुछ बुनियादी विवेक चेक, और अच्छा सहायकों कि आप कोड का एक हिस्सा डालने जैसे कार्य करने देते हैं और यह तो आप कर सकते हैं 'सभी ऑफसेट और लेबल अपडेट के बारे में चिंता करते हैं टी यह गलत है, और इतने पर। (साथ ही, वे आपको उस हास्यास्पद 6-लाइन कंस्ट्रक्टर में टाइप करने के लिए रखते हैं, और ऐसा करने से आने वाले मूर्ख टाइपो को डीबग करने के लिए रखते हैं।)
अब # 2 पर।
मैंने उल्लेख किया है कि कोड ऑब्जेक्ट अपरिवर्तनीय हैं। और निश्चित रूप से कब्ज टपल हैं, इसलिए हम इसे सीधे नहीं बदल सकते हैं। और const tuple में चीज़ एक स्ट्रिंग है, जिसे हम सीधे बदल भी नहीं सकते हैं। इसलिए मुझे नए कोड ऑब्जेक्ट बनाने के लिए एक नया टपल बनाने के लिए एक नया स्ट्रिंग बनाना था।
लेकिन क्या होगा अगर आप सीधे एक स्ट्रिंग बदल सकते हैं?
अच्छी तरह से, कवर के नीचे पर्याप्त गहरा, सब कुछ बस कुछ सी डेटा के लिए एक संकेतक है, है ना? यदि आप CPython का उपयोग कर रहे हैं, तो वस्तुओं तक पहुँचने के लिए एक C API है , और आप ctypes
Python के भीतर से ही उस API तक पहुँचने के लिए उपयोग कर सकते हैं , जो इतना भयानक विचार है कि वे pythonapi
वहाँ stdlib के ctypes
मॉड्यूल में एक सही जगह डालते हैं । :) सबसे महत्वपूर्ण चाल जिसे आपको जानना आवश्यक है वह id(x)
यह है कि x
मेमोरी में वास्तविक सूचक है (एक int
)।
दुर्भाग्य से, तार के लिए सी एपीआई हमें पहले से जमे हुए स्ट्रिंग के आंतरिक भंडारण में सुरक्षित रूप से नहीं आने देगा। तो सुरक्षित रूप से पेंच, चलो हेडर फ़ाइलों को पढ़ते हैं और उस भंडारण को स्वयं ढूंढते हैं।
यदि आप CPython 3.4 - 3.7 का उपयोग कर रहे हैं (यह पुराने संस्करणों के लिए अलग है, और जो भविष्य के लिए जानता है), एक मॉड्यूल से एक स्ट्रिंग शाब्दिक जो शुद्ध ASCII से बना है, कॉम्पैक्ट ASCII प्रारूप का उपयोग करके संग्रहीत किया जा रहा है, जिसका अर्थ है कि संरचना जल्दी समाप्त हो जाता है और ASCII बाइट्स का बफर स्मृति में तुरंत बाद में आता है। यह टूट जाएगा (जैसा कि शायद सेगफॉल्ट में) यदि आप स्ट्रिंग में एक गैर-एएससीआईआई चरित्र, या कुछ प्रकार के गैर-शाब्दिक तार लगाते हैं, लेकिन आप विभिन्न प्रकार के तारों के लिए बफर तक पहुंचने के अन्य 4 तरीकों पर पढ़ सकते हैं।
चीजों को थोड़ा आसान बनाने के लिए, मैं superhackyinternals
अपने GitHub से परियोजना का उपयोग कर रहा हूं । (यह जानबूझकर पाइप-इंस्टॉल करने योग्य नहीं है क्योंकि आप वास्तव में इंटरप्रेटर और अपने स्थानीय बिल्ड के साथ प्रयोग करने के अलावा इसका उपयोग नहीं करना चाहिए।)
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
... बहाना है कि कोड बॉक्स में एक अनंत-लंबाई स्क्रॉलबार है।
मैंने आईपीथॉन में एक ही चीज़ की कोशिश की, और पहली बार जब मैंने 2
प्रॉम्प्ट पर मूल्यांकन करने की कोशिश की , तो यह किसी प्रकार के अबाधित अनंत लूप में चला गया। संभवतः यह 2
अपने REPL लूप में कुछ के लिए संख्या का उपयोग कर रहा है , जबकि स्टॉक दुभाषिया नहीं है?
42
करने के लिए23
कारण है कि यह एक बुरा विचार के मान बदलने के लिए है की तुलना में"My name is Y"
करने के लिए"My name is X"
।