forked from idapython/src
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathVirusTotal.py
More file actions
366 lines (284 loc) · 10.7 KB
/
Copy pathVirusTotal.py
File metadata and controls
366 lines (284 loc) · 10.7 KB
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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
from __future__ import print_function
# -----------------------------------------------------------------------
# VirusTotal IDA Plugin
# By Elias Bachaalany <elias at hex-rays.com>
# (c) Hex-Rays 2011-2025
#
# Special thanks:
# - VirusTotal team
# - Bryce Boe for his VirusTotal Python code
#
import idaapi
import idc
from idaapi import plugin_t
from ida_kernwin import Choose
import BboeVt as vt
import webbrowser
import urllib
import os
PLUGIN_TEST = 0
# -----------------------------------------------------------------------
# Configuration file
VT_CFGFILE = os.path.join(idaapi.get_user_idadir(), "virustotal.cfg")
# -----------------------------------------------------------------------
# VirusTotal Icon in PNG format
VT_ICON = (
"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52"
"\x00\x00\x00\x10\x00\x00\x00\x10\x04\x03\x00\x00\x00\xED\xDD\xE2"
"\x52\x00\x00\x00\x30\x50\x4C\x54\x45\x03\x8B\xD3\x5C\xB4\xE3\x9C"
"\xD1\xED\xF7\xFB\xFD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\xD3\xF2\x42\x61\x00\x00\x00"
"\x4B\x49\x44\x41\x54\x78\x9C\x2D\xCA\xC1\x0D\x80\x30\x0C\x43\x51"
"\x27\x2C\x50\x89\x05\x40\x2C\x40\xEB\xFD\x77\xC3\x76\xC9\xE9\xEB"
"\xC5\x20\x5F\xE8\x1A\x0F\x97\xA3\xD0\xE4\x1D\xF9\x49\xD1\x59\x29"
"\x4C\x43\x9B\xD0\x15\x01\xB5\x4A\x9C\xE4\x70\x14\x39\xB3\x31\xF8"
"\x15\x70\x04\xF4\xDA\x20\x39\x02\x8A\x0D\xA8\x0F\x94\xA7\x09\x0E"
"\xC5\x16\x2D\x54\x00\x00\x00\x00\x49\x45\x4E\x44\xAE\x42\x60\x82")
# -----------------------------------------------------------------------
class VirusTotalConfig(object):
def __init__(self):
self.Default()
def Default(self):
self.md5sum = retrieve_input_file_md5()
self.infile = idaapi.dbg_get_input_path()
if not self.infile:
self.infile = ""
# Persistent options
self.apikey = ""
self.options = 1 | 2
def Read(self):
"""
Read configuration from file
"""
if not os.path.exists(VT_CFGFILE):
return
f = open(VT_CFGFILE, 'r')
lines = f.readlines()
for i, line in enumerate(lines):
line = line.strip()
if i == 0:
self.apikey = line
elif i == 1:
self.options = int(line)
else:
break
def Write(self):
"""
Write configuration to file
"""
lines = (self.apikey.strip(), str(self.options))
try:
f = open(VT_CFGFILE, 'w')
f.write("\n".join(lines))
f.close()
except:
pass
# -----------------------------------------------------------------------
def VtReport(apikey, filename=None, md5sum=None):
if filename is None and md5sum is None:
return (False, "No parameters passed!")
# Check filename existance
if filename is not None and not os.path.exists(filename):
return (False, "Input file '%s' does not exist!" % filename)
#print("fn=%s md5=%s" % (filename, md5sum))
# Get file report from VirusTotal
try:
vt.set_apikey(apikey)
result = vt.get_file_report(filename=filename, md5sum=md5sum)
except Exception as e:
return (False, "Exception:\n%s" % str(e))
# Already analyzed?
if result is not None:
# Transform the results
items = []
for av, mwname in result.items():
mwname = str(mwname) if mwname else "n/a"
av = str(av)
items.append([av, mwname])
result = items
return (True, result)
# -----------------------------------------------------------------------
class VirusTotalChooser(Choose):
"""
Chooser class to display results from VT
"""
def __init__(self, title, items, icon, embedded=False):
Choose.__init__(self,
title,
[ ["Antivirus", 20], ["Result", 40] ],
embedded=embedded)
self.items = items
self.icon = icon
def GetItems(self):
return self.items
def SetItems(self, items):
self.items = [] if items is None else items
def OnGetLine(self, n):
return self.items[n]
def OnGetSize(self):
return len(self.items)
def OnSelectLine(self, n):
# Google search for the malware name and the antivirus name
s = urllib.urlencode({"q" : " ".join(self.items[n])})
webbrowser.open_new_tab("http://www.google.com/search?%s" % s)
return (NOTHING_CHANGED, )
# --------------------------------------------------------------------------
class VirusTotalForm(Form):
def __init__(self, icon):
self.EChooser = VirusTotalChooser("E1", [], icon, embedded=True)
Form.__init__(self, r"""STARTITEM {id:txtInput}
VirusTotal - IDAPython plugin v1.0 (c) Hex-Rays
{FormChangeCb}
<#API key#~A~pi key:{txtApiKey}>
Options:
<#Open results in a chooser when form closes#~P~opout results on close:{rOptRemember}>
<#Use MD5 checksum#~M~D5Sum:{rOptMD5}>
<#Use file on disk#~F~ile:{rOptFile}>{grpOptions}>
<#Type input (file or MD5 string)#~I~nput:{txtInput}>
<Results:{cEChooser}>
<#Get reports from VT#~R~eport:{btnReport}>
""", {
'FormChangeCb': Form.FormChangeCb(self.OnFormChange),
'txtApiKey' : Form.StringInput(swidth=80),
'grpOptions' : Form.ChkGroupControl(("rOptRemember", "rOptMD5", "rOptFile")),
'txtInput' : Form.FileInput(open=True),
'btnReport' : Form.ButtonInput(self.OnReportClick),
'cEChooser' : Form.EmbeddedChooserControl(self.EChooser)
})
def OnReportClick(self, code=0):
pass
def OnFormChange(self, fid):
if fid == self.rOptMD5.id or fid == self.rOptFile.id:
input = (self.cfg.md5sum, self.cfg.infile)
if fid == self.rOptMD5.id:
c1 = self.rOptMD5
c2 = self.rOptFile
idx = 0
else:
c1 = self.rOptFile
c2 = self.rOptMD5
idx = 1
v = not self.GetControlValue(c1)
if v: idx = not idx
# Uncheck the opposite input type
self.SetControlValue(c2, v)
# Set input field depending on input type
self.SetControlValue(self.txtInput, input[idx])
#
# Report button
#
elif fid == self.btnReport.id:
input = self.GetControlValue(self.txtInput)
as_file = self.GetControlValue(self.rOptFile)
apikey = self.GetControlValue(self.txtApiKey)
ok, r = VtReport(self.cfg.apikey,
filename=input if as_file else None,
md5sum=None if as_file else input)
# Error?
if not ok:
idc.warning(r)
return 1
# Pass the result
self.EChooser.SetItems(r)
# We have results and it was a file? Print its MD5
if r and as_file:
print("%s: %s" % (vt.LAST_FILE_HASH, input))
# Refresh the embedded chooser control
# (Could also clear previous results if not were retrieved during this run)
self.RefreshField(self.cEChooser)
# Store the input for the caller
self.cfg.input = input
# No results and file as input was supplied?
if r is None:
if as_file:
# Propose to upload
if idc.ask_yn(0, "HIDECANCEL\nNo previous results. Do you want to submit the file:\n\n'%s'\n\nto VirusTotal?" % input) == 0:
return 1
try:
r = vt.scan_file(input)
except Exception as e:
idc.warning("Exceptio during upload: %s" % str(e))
else:
if r is None:
idc.warning("Failed to upload the file!")
else:
idc.warning("File uploaded. Check again later to get the analysis report. Scan id: %s" % r)
else:
idc.warning("No results found for hash: %s" % input)
return 1
def Show(self, cfg):
# Compile the form once
if not self.Compiled():
_, args = self.Compile()
#print(args[0])
# Populate the form
self.txtApiKey.value = cfg.apikey
self.grpOptions.value = cfg.options
self.txtInput.value = cfg.infile if self.rOptFile.checked else cfg.md5sum
# Remember the config
self.cfg = cfg
# Execute the form
ok = self.Execute()
# Forget the cfg
del self.cfg
# Success?
if ok != 0:
# Update config
cfg.options = self.grpOptions.value
cfg.apikey = self.txtApiKey.value
# Popup results?
if self.rOptRemember.checked:
ok = 2
return ok
# -----------------------------------------------------------------------
class VirusTotalPlugin_t(plugin_t):
flags = idaapi.PLUGIN_UNL
comment = "VirusTotal plugin for IDA"
help = ""
wanted_name = "VirusTotal report"
wanted_hotkey = "Alt-F8"
def init(self):
# Some initialization
self.icon_id = 0
return idaapi.PLUGIN_OK
def run(self, arg=0):
# Load icon from the memory and save its id
self.icon_id = idaapi.load_custom_icon(data=VT_ICON, format="png")
if self.icon_id == 0:
raise RuntimeError("Failed to load icon data!")
# Create config object
cfg = VirusTotalConfig()
# Read previous config
cfg.Read()
# Create form
f = VirusTotalForm(self.icon_id)
# Show the form
ok = f.Show(cfg)
if ok == 0:
f.Free()
return
# Save configuration
cfg.Write()
# Spawn a non-modal chooser w/ the results if any
if ok == 2 and f.EChooser.GetItems():
VirusTotalChooser(
"VirusTotal results [%s]" % cfg.input,
f.EChooser.GetItems(),
self.icon_id).Show()
f.Free()
return
def term(self):
# Free the custom icon
if self.icon_id != 0:
idaapi.free_custom_icon(self.icon_id)
# -----------------------------------------------------------------------
def PLUGIN_ENTRY():
return VirusTotalPlugin_t()
# --------------------------------------------------------------------------
if PLUGIN_TEST:
# Create form
f = PLUGIN_ENTRY()
f.init()
f.run()
f.term()