-
Notifications
You must be signed in to change notification settings - Fork 3
/
r2-retdec.py
149 lines (106 loc) · 3.61 KB
/
r2-retdec.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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#!/usr/bin/env python3
import argparse
import json
import os
import subprocess
import sys
import r2pipe
def parse_args(_args):
parser = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('file',
metavar='FILE',
help='The input file.')
parser.add_argument('-r', '--retdec',
dest='retdec',
metavar='FILE',
required=True,
help='Path to the main retdec decompilation script (retdec-decompiler.py).')
parser.add_argument('-f', '--function',
dest='function',
metavar='ADDRESS',
required=True,
help='Address inside a function to decompile.')
return parser.parse_args(_args)
def to_address(a):
return hex(a)
def drop_prefix(str, prefix):
if str.startswith(prefix):
return str[len(prefix):]
return str
def to_calling_convention(cc):
"""Translate r2 ccs to retdec ccs if they are not the same."""
return cc
def generate_basic(r2, config):
file_info = r2.cmdj('ij')
config['ida'] = True
config['inputFile'] = file_info['core']['file']
try:
config['entryPoint'] = to_address(r2.cmdj('iej')[0]['vaddr'])
except IndexError:
pass
def generate_functions(r2, config):
funcs = list()
rfncs = r2.cmdj('aflj')
for rf in rfncs:
f = generate_function(r2, rf)
if f:
funcs.append(f)
config['functions'] = funcs
def generate_function(r2, rf):
f = dict()
f['name'] = drop_prefix(rf['name'], 'sym.')
f['startAddr'] = to_address(int(rf['minbound']))
f['endAddr'] = to_address(int(rf['maxbound']))
f['callingConvention'] = to_calling_convention(rf['calltype'])
f['fncType'] = 'userDefined'
return f
def generate_config(r2):
config = dict()
generate_basic(r2, config)
generate_functions(r2, config)
return config
def generate_retdec_arguments(args, config_name, selected_fnc, output):
rargs = list()
rargs.append('python')
rargs.append(args.retdec)
rargs.append(args.file)
rargs.append('--config')
rargs.append(config_name)
rargs.append('--select-decode-only')
rargs.append('--select-ranges')
start = selected_fnc[0]['offset']
end = start + selected_fnc[0]['size']
rargs.append(hex(start) + '-' + hex(end))
rargs.append('-o')
rargs.append(output)
return rargs
if __name__ == '__main__':
args = parse_args(sys.argv[1:])
# Analyze input file in r2.
#
r2 = r2pipe.open(args.file)
r2.cmd('aaa')
# Find the selected function.
#
selected_fnc = r2.cmdj('afij ' + args.function)
if not selected_fnc:
print('No function at selected address: %s' % args.function, file=sys.stderr)
sys.exit(1)
# Generate JSON config.
#
config = generate_config(r2)
config_name = os.path.abspath(args.file) + '.json'
with open(config_name, 'w') as config_file:
json.dump(config, config_file, sort_keys=True, indent=4)
# Execute RetDec command.
#
output = args.file + '.retdec.c'
rargs = generate_retdec_arguments(args, config_name, selected_fnc, output)
if subprocess.call(rargs, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL):
print('Decompilation failed. Arguments: %s' % str(rargs), file=sys.stderr)
sys.exit(1)
# Dump output C.
#
with open(output, 'r') as output_file:
print(output_file.read())