运行时修改函数(拉出局部变量)
Modify function at runtime (pulling local variable out)
想象一下这个创建变量 default
、modified
:
的修改值的简单函数
default = 0
def modify():
modified = default + 1
print(modified) # replace with OS call, I can't see the output
modify() # 1
default # 0
反汇编:
import dis
dis.dis(modify)
2 0 LOAD_GLOBAL 0 (default)
3 LOAD_CONST 1 (1)
6 BINARY_ADD
7 STORE_FAST 0 (modified)
3 10 LOAD_GLOBAL 1 (print)
13 LOAD_FAST 0 (modified)
16 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
19 POP_TOP
20 LOAD_CONST 0 (None)
23 RETURN_VALUE
我无法更改函数 modify()
,但我知道其中的内容,直接(我可以看到代码)或间接(反汇编)。我需要的是获取 modified
变量的值,所以我可能有一种方法可以通过 dis
模块删除函数的特定部分 (print(modified)
),但是我什么也没找到。
有什么方法可以删除 16 CALL_FUNCTION
之后除 return_value
之外的所有内容,并将其替换为例如return modified
?或者有没有其他方法可以在不实际执行最后一行的情况下提取局部变量?
作为一种可能的解决方案,我看到了 3 种方法:
- 提取反汇编代码并根据它们创建我自己的函数(或就地),删除我不想要的代码(
16 ...
之后的所有内容)
- 修改函数的return值,使其return变为
modified
(不幸的是调用了OS函数)
- 根据源代码手动重新创建函数
我想避免第二种方式,这可能比第一种方式更容易,但我必须避免第三种方式,所以...有什么办法可以解决我的问题吗?
还有第 4 个选项:替换 print()
全局:
printed = []
print = lambda *args: printed.extend(args)
modify()
del print
modified = printed[0]
否则可能会生成修改后的字节码,但这很容易导致导致解释器崩溃的错误(对无效字节码的保护为零),因此请注意。
您可以使用更新后的字节码的新代码对象创建新函数对象;根据您显示的 dis 中的偏移量,我手动创建了新的字节码,它将 return 索引 0:
处的局部变量
>>> altered_bytecode = modify.__code__.co_code[:8] + bytes(
... [dis.opmap['LOAD_FAST'], 0, # load local variable 0 onto the stack
... dis.opmap['RETURN_VALUE']])) # and return it.
>>> dis.dis(altered_bytecode)
0 LOAD_GLOBAL 0 (0)
2 LOAD_CONST 1 (1)
4 BINARY_ADD
6 STORE_FAST 0 (0)
8 LOAD_FAST 0 (0)
10 RETURN_VALUE
RETURN_VALUE
returns 栈顶对象;我所做的只是注入一个 LOAD_FAST
操作码来将 modified
引用加载到堆栈上。
您必须创建一个新的 code
对象,然后是一个新的 function
对象来包装代码对象,才能使其可调用:
>>> code = type(modify.__code__)
>>> function = type(modify)
>>> ocode = modify.__code__
>>> new_modify = function(
... code(ocode.co_argcount, ocode.co_kwonlyargcount, ocode.co_nlocals, ocode.co_stacksize,
... ocode.co_flags, altered_bytecode,
... ocode.co_consts, ocode.co_names, ocode.co_varnames, ocode.co_filename,
... 'new_modify', ocode.co_firstlineno, ocode.co_lnotab, ocode.co_freevars,
... ocode.co_cellvars),
... modify.__globals__, 'new_modify', modify.__defaults__, modify.__closure__)
>>> new_modify()
1
显然,这确实需要首先了解 Python 字节码的工作原理; dis
模块确实包含各种代码的描述,dis.opmap
dictionary 允许您映射回字节值。
有一些模块试图使这更容易;如果您想进一步探索,请查看 byteplay
, the bytecode
module of the pwnypack
project 或其他几个。
我也可以衷心推荐您观看 Playing with Python Bytecode presentation given by Scott Sanderson, Joe Jevnik at PyCon 2016, and play with their codetransformer
module。极具娱乐性和信息量。
想象一下这个创建变量 default
、modified
:
default = 0
def modify():
modified = default + 1
print(modified) # replace with OS call, I can't see the output
modify() # 1
default # 0
反汇编:
import dis
dis.dis(modify)
2 0 LOAD_GLOBAL 0 (default)
3 LOAD_CONST 1 (1)
6 BINARY_ADD
7 STORE_FAST 0 (modified)
3 10 LOAD_GLOBAL 1 (print)
13 LOAD_FAST 0 (modified)
16 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
19 POP_TOP
20 LOAD_CONST 0 (None)
23 RETURN_VALUE
我无法更改函数 modify()
,但我知道其中的内容,直接(我可以看到代码)或间接(反汇编)。我需要的是获取 modified
变量的值,所以我可能有一种方法可以通过 dis
模块删除函数的特定部分 (print(modified)
),但是我什么也没找到。
有什么方法可以删除 16 CALL_FUNCTION
之后除 return_value
之外的所有内容,并将其替换为例如return modified
?或者有没有其他方法可以在不实际执行最后一行的情况下提取局部变量?
作为一种可能的解决方案,我看到了 3 种方法:
- 提取反汇编代码并根据它们创建我自己的函数(或就地),删除我不想要的代码(
16 ...
之后的所有内容) - 修改函数的return值,使其return变为
modified
(不幸的是调用了OS函数) - 根据源代码手动重新创建函数
我想避免第二种方式,这可能比第一种方式更容易,但我必须避免第三种方式,所以...有什么办法可以解决我的问题吗?
还有第 4 个选项:替换 print()
全局:
printed = []
print = lambda *args: printed.extend(args)
modify()
del print
modified = printed[0]
否则可能会生成修改后的字节码,但这很容易导致导致解释器崩溃的错误(对无效字节码的保护为零),因此请注意。
您可以使用更新后的字节码的新代码对象创建新函数对象;根据您显示的 dis 中的偏移量,我手动创建了新的字节码,它将 return 索引 0:
处的局部变量>>> altered_bytecode = modify.__code__.co_code[:8] + bytes(
... [dis.opmap['LOAD_FAST'], 0, # load local variable 0 onto the stack
... dis.opmap['RETURN_VALUE']])) # and return it.
>>> dis.dis(altered_bytecode)
0 LOAD_GLOBAL 0 (0)
2 LOAD_CONST 1 (1)
4 BINARY_ADD
6 STORE_FAST 0 (0)
8 LOAD_FAST 0 (0)
10 RETURN_VALUE
RETURN_VALUE
returns 栈顶对象;我所做的只是注入一个 LOAD_FAST
操作码来将 modified
引用加载到堆栈上。
您必须创建一个新的 code
对象,然后是一个新的 function
对象来包装代码对象,才能使其可调用:
>>> code = type(modify.__code__)
>>> function = type(modify)
>>> ocode = modify.__code__
>>> new_modify = function(
... code(ocode.co_argcount, ocode.co_kwonlyargcount, ocode.co_nlocals, ocode.co_stacksize,
... ocode.co_flags, altered_bytecode,
... ocode.co_consts, ocode.co_names, ocode.co_varnames, ocode.co_filename,
... 'new_modify', ocode.co_firstlineno, ocode.co_lnotab, ocode.co_freevars,
... ocode.co_cellvars),
... modify.__globals__, 'new_modify', modify.__defaults__, modify.__closure__)
>>> new_modify()
1
显然,这确实需要首先了解 Python 字节码的工作原理; dis
模块确实包含各种代码的描述,dis.opmap
dictionary 允许您映射回字节值。
有一些模块试图使这更容易;如果您想进一步探索,请查看 byteplay
, the bytecode
module of the pwnypack
project 或其他几个。
我也可以衷心推荐您观看 Playing with Python Bytecode presentation given by Scott Sanderson, Joe Jevnik at PyCon 2016, and play with their codetransformer
module。极具娱乐性和信息量。