-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.py
126 lines (98 loc) · 3.63 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
"""
Introducing GOTO statement into Python.
"Your scientists were so preoccupied with whether or not they could, they didn’t stop to think if they should."
Should we call it QPython now *grimace*
"""
import dis
from types import CodeType
DEBUG = True
GOTO_TOKEN = '#GOTO_'
LABEL_TOKEN = '#GOTOLABEL_'
INST_TO_REPLACE = 'LOAD_CONST'
JUMP_OP_CODE = dis.opmap.get('JUMP_ABSOLUTE').to_bytes(1, byteorder='little')
def fix_function(func, payload):
""" Patches function with a new CodeObject where the
bytecode has been replaced with new.
Credit: Artem Golubin
Url: https://rushter.com/blog/python-bytecode-patch/
"""
fn_code = func.__code__
print(fn_code.co_argcount)
func.__code__ = CodeType(fn_code.co_argcount,
fn_code.co_posonlyargcount,
fn_code.co_kwonlyargcount,
fn_code.co_nlocals,
fn_code.co_stacksize,
fn_code.co_flags,
payload,
fn_code.co_consts,
fn_code.co_names,
fn_code.co_varnames,
fn_code.co_filename,
fn_code.co_name,
fn_code.co_firstlineno,
fn_code.co_lnotab,
fn_code.co_freevars,
fn_code.co_cellvars,
)
CodeType
def goto_mutator(f):
""" Patches decorated function to allow "goto statements"
"""
def inner_func():
pass
code_obj = f.__code__
byte_code = code_obj.co_code
consts = code_obj.co_consts
index_to_label_map = {}
for index, const in enumerate(consts):
if isinstance(const, str) and (GOTO_TOKEN in const or LABEL_TOKEN in const):
prefix, label_name = const.split('_', 1)
prefix += '_'
index_to_label_map[index] = (label_name, prefix)
jmp_table = {}
for inst in dis.get_instructions(f):
if inst.opname == INST_TO_REPLACE:
lbl_or_jmp_location = index_to_label_map.get(inst.arg)
if lbl_or_jmp_location:
label, prefix = lbl_or_jmp_location
jmp_table.setdefault(label, {}).setdefault(prefix, []).append(inst.offset)
patched_bytecode = []
last_offset = 0
for inst in dis.get_instructions(f):
if inst.opname == INST_TO_REPLACE \
and inst.arg in index_to_label_map:
label_name, prefix = index_to_label_map[inst.arg]
if prefix == GOTO_TOKEN:
jmp = jmp_table[label_name]
# jmp_indxs = jmp.get(GOTO_TOKEN)
lbl_indxs = jmp.get(LABEL_TOKEN)
if not lbl_indxs:
raise ValueError('Label not defined')
if len(lbl_indxs) > 1:
raise ValueError(f'Label redifinitions at bytecode offsets {lbl_indxs}')
label_index = lbl_indxs[0]
patched_bytecode += \
byte_code[last_offset:inst.offset] + JUMP_OP_CODE + label_index.to_bytes(1, byteorder='little')
last_offset = inst.offset + 2
patched_bytecode += byte_code[last_offset:]
fix_function(f, bytes(patched_bytecode))
if DEBUG:
print('JUMP TABLE IS:\n', jmp_table, '\n')
print('Original ByteCode:')
print(byte_code.hex(), '\n')
print('ByteCode patched with GOTO\'s')
print(bytes(patched_bytecode).hex(), '\n')
return f
@goto_mutator
def mutate_me(b):
c = 7
x = '#GOTO_something' # Actual GOTO statement NOTE: these must inline/ constant strings
a = 1 + b
pass
pass
pass
print('DID NOT SKIP PRINT')
x = '#GOTOLABEL_something' # The goto label
print('WENT TO LABEL')
mutate_me(3)