-
Notifications
You must be signed in to change notification settings - Fork 454
/
idaplugin.py
221 lines (175 loc) · 7.22 KB
/
idaplugin.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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# Copyright (C) 2017 Mandiant, Inc. All Rights Reserved.
#!/usr/bin/env python3
"""
Run FLOSS to automatically extract obfuscated strings and apply them to the
currently loaded module in IDA Pro.
author: Willi Ballenthin
email: [email protected]
"""
import os
import time
import logging
from typing import List, Union
from pathlib import Path
import idc
import viv_utils
import floss
import floss.main
import floss.utils
import floss.render
import floss.identify
import floss.stackstrings
import floss.tightstrings
import floss.string_decoder
from floss.results import AddressType, StackString, TightString, DecodedString
logger = logging.getLogger("floss.idaplugin")
MIN_LENGTH = 4
def append_comment(ea: int, s: str, repeatable: bool = False) -> None:
"""
add the given string as a (possibly repeating) comment to the given address.
does not add the comment if it already exists.
adds the comment on its own line.
Args:
ea: the address at which to add the comment.
s: the comment text.
repeatable: if True, set a repeatable comment.
"""
# see: http://blogs.norman.com/2011/security-research/improving-ida-analysis-of-x64-exception-handling
if repeatable:
cmt = idc.get_cmt(ea, True)
else:
cmt = idc.get_cmt(ea, False)
if not cmt:
cmt = s # no existing comment
else:
if s in cmt: # ignore duplicates
return
cmt = cmt + "\n" + s
if repeatable:
idc.set_cmt(ea, cmt, True)
else:
idc.set_cmt(ea, cmt, False)
def append_lvar_comment(fva: int, frame_offset: int, s: str, repeatable: bool = False) -> None:
"""
add the given string as a (possibly repeatable) stack variable comment to the given function.
does not add the comment if it already exists.
adds the comment on its own line.
Args:
fva: the address of the function with the stack variable.
frame_offset: the offset into the stack frame at which the variable is found.
s: the comment text.
repeatable: if True, set a repeatable comment.
"""
stack = idc.get_func_attr(fva, idc.FUNCATTR_FRAME)
if not stack:
raise RuntimeError("failed to find stack frame for function: 0x%x" % fva)
lvar_offset = (
idc.get_func_attr(fva, idc.FUNCATTR_FRSIZE) - frame_offset
) # alternative: idc.get_frame_lvar_size(fva) - frame_offset
if not lvar_offset:
raise RuntimeError("failed to compute local variable offset: 0x%x 0x%x %s" % (fva, stack, s))
if lvar_offset <= 0:
raise RuntimeError("failed to compute positive local variable offset: 0x%x 0x%x %s" % (fva, stack, s))
string = idc.get_member_cmt(stack, lvar_offset, repeatable)
if not string:
string = s
else:
if s in string: # ignore duplicates
return
string = string + "\n" + s
if not idc.set_member_cmt(stack, lvar_offset, string, repeatable):
raise RuntimeError("failed to set comment: 0x%08x 0x%08x 0x%08x: %s" % (fva, stack, lvar_offset, s))
def apply_decoded_strings(decoded_strings: List[DecodedString]) -> None:
for ds in decoded_strings:
if not ds.string:
continue
if ds.address_type == AddressType.GLOBAL:
logger.info("decoded string at global address 0x%x: %s", ds.address, ds.string)
append_comment(ds.address, ds.string)
else:
logger.info("decoded string for function call at 0x%x: %s", ds.decoded_at, ds.string)
append_comment(ds.decoded_at, ds.string)
def apply_stack_strings(
stack_strings: List[StackString], tight_strings: List[TightString], lvar_cmt: bool = True, cmt: bool = True
) -> None:
"""
lvar_cmt: apply stack variable comment
cmt: apply regular comment
"""
strings = stack_strings + tight_strings
for s in strings:
if not s.string:
continue
logger.info(
"decoded stack/tight string in function 0x%x (pc: 0x%x): %s", s.function, s.program_counter, s.string
)
if lvar_cmt:
try:
# TODO this often fails due to wrong frame offset
append_lvar_comment(s.function, s.frame_offset, s.string)
except RuntimeError as e:
logger.warning("failed to apply stack/tight string: %s", str(e))
if cmt:
append_comment(s.program_counter, s.string)
def ignore_floss_logs():
logging.getLogger("floss.api_hooks").setLevel(logging.WARNING)
logging.getLogger("floss.function_argument_getter").setLevel(logging.WARNING)
logging.getLogger("viv_utils").setLevel(logging.CRITICAL)
logging.getLogger("viv_utils.emulator_drivers").setLevel(logging.ERROR)
floss.utils.set_vivisect_log_level(logging.CRITICAL)
def main(argv=None):
logging.basicConfig(level=logging.INFO)
logging.getLogger().setLevel(logging.INFO)
ignore_floss_logs()
idb_path = Path(idc.get_idb_path())
fpath = idb_path.with_suffix("")
viv_path = fpath.with_suffix(".viv")
if viv_path.exists():
logger.info("loading vivisect workspace from %r", str(viv_path))
vw = viv_utils.getWorkspace(str(viv_path))
else:
logger.info("loading vivisect workspace from IDB...")
vw = viv_utils.loadWorkspaceFromIdb()
logger.info("loaded vivisect workspace")
selected_functions = set(vw.getFunctions())
time0 = time.time()
logger.info("identifying decoding functions...")
decoding_function_features, library_functions = floss.identify.find_decoding_function_features(
vw, selected_functions, disable_progress=True
)
logger.info("extracting stackstrings...")
selected_functions = floss.identify.get_functions_without_tightloops(decoding_function_features)
stack_strings = floss.stackstrings.extract_stackstrings(
vw, selected_functions, MIN_LENGTH, verbosity=floss.render.Verbosity.VERBOSE, disable_progress=True
)
logger.info("decoded %d stack strings", len(stack_strings))
logger.info("extracting tightstrings...")
tightloop_functions = floss.identify.get_functions_with_tightloops(decoding_function_features)
tight_strings = floss.tightstrings.extract_tightstrings(
vw,
tightloop_functions,
min_length=MIN_LENGTH,
verbosity=floss.render.Verbosity.VERBOSE,
disable_progress=True,
)
logger.info("decoded %d tight strings", len(tight_strings))
apply_stack_strings(stack_strings, tight_strings)
logger.info("decoding strings...")
top_functions = floss.identify.get_top_functions(decoding_function_features, 20)
fvas_to_emulate = floss.identify.get_function_fvas(top_functions)
fvas_tight_functions = floss.identify.get_tight_function_fvas(decoding_function_features)
fvas_to_emulate = floss.identify.append_unique(fvas_to_emulate, fvas_tight_functions)
decoded_strings = floss.string_decoder.decode_strings(
vw,
fvas_to_emulate,
MIN_LENGTH,
verbosity=floss.render.Verbosity.VERBOSE,
disable_progress=True,
)
logger.info("decoded %d strings", len(decoded_strings))
apply_decoded_strings(decoded_strings)
time1 = time.time()
logger.debug("finished execution after %f seconds", (time1 - time0))
return 0
if __name__ == "__main__":
main()