在运行时修改函数(拉出局部变量)

时间:2017-04-03 13:22:49

标签: python function bytecode disassembly

想象一下这个简单的函数创建变量defaultmodified的修改值:

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变量的值,所以我可能有一种方法可以通过print(modified)模块删除函数的特定部分(dis),但是我没找到任何东西。

有什么办法可以在return_value之后删除16 CALL_FUNCTION以外的所有内容,并将其替换为例如return modified16 ...?或者有没有其他方法如何在不实际执行最后一行的情况下拉出局部变量?

作为一种可能的解决方案,我看到了三种方式:

  • 根据它们删除反汇编的代码并创建我自己的函数(或就地)删除我不想要的代码(modified之后的所有内容)
  • 修改函数的 return 值,使其返回ConfusedByCode(不幸的是调用操作系统函数)
  • 根据源代码手动重新创建函数

我想避免第二种方式,这可能比第一种方式更容易,但我必须避免第三种方式,所以......有什么方法可以解决我的问题吗?

1 个答案:

答案 0 :(得分:5)

有第四种选择:替换print()全局:

printed = []
print = lambda *args: printed.extend(args)
modify()
del print
modified = printed[0]

否则可能会产生修改后的字节码,但这很容易导致破坏解释器的错误(对无效字节码没有保护),所以请注意。

您可以使用带有更新字节码的新代码对象创建新的函数对象;基于你展示的dis中的偏移量,我手动创建了新的字节码,它将返回索引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返回堆栈顶部的对象;我所做的只是注入一个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允许您映射回字节值。

有一些模块试图让这更容易;如果您想进一步探索,请查看byteplaybytecode module of the pwnypack project或其他几个。

我还衷心建议您观看2016年PyCon的Scott Sanderson,Joe Jevnik给出的Playing with Python Bytecode presentation,并与他们codetransformer module一起玩。非常有趣,信息量很大。