-
Notifications
You must be signed in to change notification settings - Fork 16
/
boring_flutter.py
158 lines (121 loc) · 5.61 KB
/
boring_flutter.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
150
151
152
153
154
155
156
157
158
import r2pipe
import time
import sys
import os
search_scalar = '0x186' # TODO: currently HARD CODED, add argument parameter
def argument_parsing():
if len(sys.argv) < 2:
print('❌ Usage: python {} libflutter.so'.format(sys.argv[0]))
exit(-1)
if not os.path.exists(sys.argv[1]):
print('❌ File "{}" not found...'.format(sys.argv[1]))
exit(-1)
if not os.path.isfile(sys.argv[1]):
print('❌ "{}" is a directory, please provide a valid libflutter.so file...'.format(sys.argv[1]))
exit(-1)
return sys.argv[1]
def arch_parsing(r2):
info = r2.cmdj('ij')
info_bin = info.get('bin')
if not info_bin:
print('❌ File "{}" is not a binary...'.format(sys.argv[1]))
exit(0)
if info_bin.get('os') != 'android':
print('❌ Currently only supporting Android...')
exit(0)
if info_bin.get('arch') != 'arm':
print('❌ Currently only supporting ARM...')
exit(0)
return int(info_bin.get('class')[3:])
def perform_64bits_analysis(r2):
print('🔥 Performing Advanced analysis (aaaa)...')
r2.cmd('aaaa')
print('🔥 Searching for instructions with scalar value (/aij {})...'.format(search_scalar))
search = r2.cmdj('/aij {},'.format(search_scalar))
mov_instructions = []
for hit in search:
if hit['code'].startswith('mov '):
print('\033[31m{} {}\033[0m'.format(hex(hit['offset']), hit['code']))
mov_instructions.append(hit)
else:
print('{} {}'.format(hex(hit['offset']), hit['code']))
if not mov_instructions:
print('❌ Could not find an instruction with {} scalar value...'.format(search_scalar))
exit(0)
print('🔥 Performing simple instruction matching to find ssl_crypto_x509_session_verify_cert_chain()...')
target = ''
for mov_instruction in mov_instructions:
instructions = r2.cmdj('pdj 3 @{}'.format(mov_instruction['offset']))
if len(instructions) == 3 and instructions[1]['disasm'].startswith('bl ') and instructions[2]['disasm'].startswith('mov '):
print('✅ {} {} (match)'.format(hex(mov_instruction['offset']), mov_instruction['code']))
target = hex(mov_instruction['offset'])
break
else:
print('❌ {} {} (no match)'.format(hex(mov_instruction['offset']), mov_instruction['code']))
if not target:
print('❌ Could not find a matching function ...')
exit(0)
print('🔥 Seeking to target (s {})...'.format(target))
r2.cmd('s {}'.format(target))
fcn_addr = r2.cmd('afi.')
address = '0x' + fcn_addr.split('.')[1].strip()
print('🔥 Found ssl_crypto_x509_session_verify_cert_chain @ {} (afi.)...'.format(address))
return address
def perform_32bits_analysis(r2):
print('🔥 Performing analysis (aaa)...')
r2.cmd('aaa')
print('🔥 Searching for instructions with scalar value (/aij {})...'.format(search_scalar))
search = r2.cmdj('/aij {},'.format(search_scalar))
mov_instructions = []
for hit in search:
if hit['code'].startswith('mov'):
print('\033[31m{} {}\033[0m'.format(hex(hit['offset']), hit['code']))
mov_instructions.append(hit)
else:
print('{} {}'.format(hex(hit['offset']), hit['code']))
if not mov_instructions:
print('❌ Could not find an instruction with {} scalar value...'.format(search_scalar))
exit(0)
print('🔥 Performing simple instruction matching to find ssl_crypto_x509_session_verify_cert_chain()...')
target = ''
for mov_instruction in mov_instructions:
print('🔥 Find prelude for current offset @ {}'.format(hex(mov_instruction['offset'])))
r2.cmd('s {}'.format(mov_instruction['offset']))
prelude = r2.cmd('ap').splitlines()[-1]
print('🔥 Pattern matching on prelude @ {}'.format(prelude))
instructions = r2.cmdj('pdj 5 @{}'.format(prelude))
if len(instructions) == 5 and instructions[0]['type'] == 'push' and instructions[1]['type'] == 'sub'\
and instructions[2]['type'] == 'mov' and instructions[3]['type'] == 'mov'\
and instructions[3]['val'] == 0x50 and instructions[4]['type'] == 'store':
print('✅ scalar offset @ {} -> prelude offset @ {} (match)'.format(mov_instruction['offset'], prelude))
target = prelude
break
else:
print('❌ scalar offset @ {} -> prelude offset @ {} (no match)'.format(mov_instruction['offset'], prelude))
if not target:
print('❌ Could not find a matching function ...')
exit(0)
print('🔥 Found ssl_crypto_x509_session_verify_cert_chain @ {} ...'.format(target))
return hex(int(target, 16) + 1) # Off by one because it's a THUMB function
def save_to_frida_script(address):
with open('template_frida_hook.js') as f:
template = f.read()
output_script = 'frida_libflutter_{}.js'.format(time.strftime("%Y.%m.%d_%H.%M.%S"))
with open(output_script, 'w') as f:
f.write(template.replace('0x00000000', address))
print('🔥 Wrote script to {}...'.format(output_script))
if __name__ == "__main__":
start_time = time.time()
file = argument_parsing()
r2 = r2pipe.open(file)
bits = arch_parsing(r2)
print('🔥 Detected Android ARM {} bits...'.format(bits))
if bits == 64:
address = perform_64bits_analysis(r2)
elif bits == 32:
address = perform_32bits_analysis(r2)
else:
print('❌ Quantum???')
exit(-1)
save_to_frida_script(address)
print('🚀 exec time: {}s'.format(time.time() - start_time))