有没有可能;“黑客”;Python';什么是打印功能?
注:此问题仅供参考。我很感兴趣的是看看Python的内部结构有多深 不久前,在某个数据库内部开始讨论在调用有没有可能;“黑客”;Python';什么是打印功能?,python,python-3.x,printing,python-internals,Python,Python 3.x,Printing,Python Internals,注:此问题仅供参考。我很感兴趣的是看看Python的内部结构有多深 不久前,在某个数据库内部开始讨论在调用print之后/期间是否可以修改传递给print语句的字符串。例如,考虑函数: def print_something(): print('This cat was scared.') 现在,当运行print时,终端的输出应显示: This dog was scared. 请注意,“猫”一词已被“狗”一词取代。不知何故,某个地方的某些东西能够修改这些内部缓冲区以改变打印内容。假设
print
之后/期间是否可以修改传递给print语句的字符串。例如,考虑函数:
def print_something():
print('This cat was scared.')
现在,当运行print
时,终端的输出应显示:
This dog was scared.
请注意,“猫”一词已被“狗”一词取代。不知何故,某个地方的某些东西能够修改这些内部缓冲区以改变打印内容。假设这是在没有原始代码作者明确许可的情况下完成的(因此,黑客/劫持)
特别是来自wise@abarnert的这句话让我想到:
有几种方法可以做到这一点,但它们都非常丑陋,而且
不应该这样做。最不难看的方法可能是更换
code
函数中的对象,其中一个具有不同的co_consts
列表下一步可能是访问C API以访问str的
内部缓冲区。[……]
所以,看起来这实际上是可能的
以下是我处理这个问题的天真方式:
>>> import inspect
>>> exec(inspect.getsource(print_something).replace('cat', 'dog'))
>>> print_something()
This dog was scared.
当然,exec
不好,但这并不能真正回答问题,因为调用print
期间/之后,它实际上不会修改任何内容
正如@abarnert所解释的那样,这将如何实现呢?首先,实际上有一种更简单的方法。我们要做的就是改变
print
prints的内容,对吗
_print = print
def print(*args, **kw):
args = (arg.replace('cat', 'dog') if isinstance(arg, str) else arg
for arg in args)
_print(*args, **kw)
或者,类似地,您也可以使用monkeypatchsys.stdout
而不是print
另外,exec…getsource…的想法也没有错。嗯,当然有很多错误,但比下面的要少
但是如果您确实想修改函数对象的代码常量,我们可以这样做 如果您真的想真正地使用代码对象,那么应该使用一个库,比如(当它完成时)或(直到那时,或者对于较旧的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()
对代码对象进行黑客攻击会出现什么问题?主要是segfaults、RuntimeError
s,它们会吞噬整个堆栈,更正常的RuntimeError
s可以处理,或者垃圾值,当您尝试使用它们时,它们可能只会引发TypeError
或AttributeError
。例如,尝试创建一个代码对象,它只有一个返回值
,堆栈上没有任何内容(对于3.6+,b'S\0'
,b'S
之前),或者当字节码中有加载常量0
时,它有一个空元组,或者使用varnames
递减1,这样最高的LOAD\u FAST
实际上加载一个freewar/cellvar单元格。为了获得一些真正的乐趣,如果您的lnotab
错误得足够多,那么您的代码只有在调试器中运行时才会出现segfault
使用bytecode
或byteplay
不会保护您免受所有这些问题的影响,但它们确实有一些基本的健全性检查,以及很好的帮助程序,可以让您执行一些操作,例如插入一段代码,让它担心更新所有偏移量和标签,这样您就不会出错,等等。(此外,它们还使您不必输入荒谬的6行构造函数,也不必调试由此产生的愚蠢打字错误。)
现在转到第二部分 我提到代码对象是不可变的。当然常数是一个元组,所以我们不能直接改变它。常量元组中的东西是一个字符串,我们也不能直接改变它。这就是为什么我必须构建一个新的字符串来构建一个新的元组来构建一个新的代码对象 但是如果可以直接更改字符串呢 好吧,在足够深的掩蔽下,一切都只是指向一些C数据的指针,对吗?如果您使用的是CPython,则有,和。:)您需要知道的最重要的技巧是
id(x)
是指向内存中x
的实际指针(作为int
)
不幸的是,字符串的C API不能让我们安全地访问已经冻结的字符串的内部存储。所以,请放心,让我们自己去找那个储藏室
如果您使用的是CPython 3.4-3.7(对于较旧的版本,这是不同的,谁知道将来会怎样),则来自纯ASCII模块的字符串文字将使用压缩ASCII格式存储,这意味着结构提前结束,ASCII字节的缓冲区将立即在内存中跟随。如果在字符串中放入非ASCII字符或某些类型的非文字字符串,这将中断(可能是segfault),但您可以通过其他4种方式读取不同类型字符串的缓冲区
为了让事情稍微简单一点,我在GitHub上使用了这个项目。(这是故意不允许pip安装的,因为您真的不应该使用它,除非您在本地构建了解释器等等。)
如果你想玩这些东西,int
比str
更简单。通过将2
的值更改为1
,可以更容易地猜出可以破坏什么,对吗?实际上,忘了想象吧,我们就这么做吧(使用su中的类型)
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()
>>> 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
from functools import partial
output_buffer = None
print_orig = print
def ob_start(fname="print.txt"):
global print
global output_buffer
print = partial(print_orig, file=output_buffer)
output_buffer = open(fname, 'w')
def ob_end():
global output_buffer
close(output_buffer)
print = print_orig
def ob_get_contents(fname="print.txt"):
return open(fname, 'r').read()
print ("Hi John")
ob_start()
print ("Hi John")
ob_end()
print (ob_get_contents().replace("Hi", "Bye"))
# Store the real print function in another variable otherwise
# it will be inaccessible after being modified.
_print = print
# Actual implementation of the new print
def custom_print(*args, **options):
_print('custom print called')
_print(*args, **options)
# Change the print function globally
import builtins
builtins.print = custom_print
_print = print
def custom_print(*args, **options):
# Get the desired seperator or the default whitspace
sep = options.pop('sep', ' ')
# Create the final string
printed_string = sep.join(args)
# Modify the final string
printed_string = printed_string.replace('cat', 'dog')
# Call the default print function
_print(printed_string, **options)
import builtins
builtins.print = custom_print
>>> def print_something():
... print('This cat was scared.')
>>> print_something()
This dog was scared.
def print_something():
print('This cat was scared.')
print_something()
>>> import test_file
This dog was scared.
>>> test_file.print_something()
This dog was scared.
import builtins
class ChangePrint(object):
def __init__(self):
self.old_print = print
def __enter__(self):
def custom_print(*args, **options):
# Get the desired seperator or the default whitspace
sep = options.pop('sep', ' ')
# Create the final string
printed_string = sep.join(args)
# Modify the final string
printed_string = printed_string.replace('cat', 'dog')
# Call the default print function
self.old_print(printed_string, **options)
builtins.print = custom_print
def __exit__(self, *args, **kwargs):
builtins.print = self.old_print
>>> with ChangePrint() as x:
... test_file.print_something()
...
This dog was scared.
>>> test_file.print_something()
This cat was scared.
import io
import sys
class CustomStdout(object):
def __init__(self, *args, **kwargs):
self.current_stdout = sys.stdout
def write(self, string):
self.current_stdout.write(string.replace('cat', 'dog'))
>>> import contextlib
>>> with contextlib.redirect_stdout(CustomStdout()):
... test_file.print_something()
...
This dog was scared.
>>> test_file.print_something()
This cat was scared.
import sys
_print = print
def print(*args, **kw):
frame = sys._getframe(1)
_print(frame.f_code.co_name)
_print(*args, **kw)
def greetly(name, greeting = "Hi")
print(f"{greeting}, {name}!")
class Greeter:
def __init__(self, greeting = "Hi"):
self.greeting = greeting
def greet(self, name):
print(f"{self.greeting}, {name}!")