-
Notifications
You must be signed in to change notification settings - Fork 20
/
Copy pathvt.py
executable file
·1416 lines (950 loc) · 67.8 KB
/
vt.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
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
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/python2.7
# Full VT APIv2 functions by Andriy (aka doomedraven) Brukhovetskyy (Twitter : @d00m3dr4v3n)
# No Licence or warranty expressed or implied, use however you wish!
# For more information look at:
#
# https://www.virustotal.com/en/documentation/public-api
# https://www.virustotal.com/en/documentation/private-api
import os
import re
import sys
import time
import json
import glob
import time
import hashlib
import argparse
import requests
import ConfigParser
import texttable as tt
import ConfigParser
from operator import methodcaller
from dateutil.relativedelta import relativedelta
def private_api_access_error():
print '\n[!] You don\'t have permission for this operation, Looks like you trying to access to PRIVATE API functions\n'
sys.exit()
def parse_conf(file_name):
try:
confpath = os.path.expanduser(file_name)
if os.path.exists(confpath):
config = ConfigParser.RawConfigParser()
config.read(confpath)
apikey = config.get('vt', 'apikey')
return apikey
else:
sys.exit('\nFile {0} don\'t exists\n'.format(confpath))
except Exception:
sys.exit('No API key provided and cannot read ~/.vtapi. Specify an API key in vt.py or in ~/.vtapi or in your file')
def pretty_print(block, headers, sizes = False, align = False):
tab = tt.Texttable()
if isinstance(block, list):
plist = []
for line in block:
if len(headers) == 1:
plist.append([line])
else:
plist.append(map(lambda key: line[key] if line.get(key) else ' -- ', headers))
if len(plist) > 1 and isinstance(plist[0], list):
tab.add_rows(plist)
else:
tab.add_row(plist[0])
else:
row = map(lambda key: block[key] if block.get(key) else ' -- ', headers)
tab.add_row(row)
tab.header(headers)
if not align:
align = map(lambda key: 'l', headers)
if sizes:
tab.set_cols_width(sizes)
tab.set_cols_align(align)
print tab.draw()
def pretty_print_special(rows, headers, sizes = False, align = False):
tab = tt.Texttable()
tab.add_rows(rows)
if sizes:
tab.set_cols_width(sizes)
if align:
tab.set_cols_align(align)
tab.header(headers)
print '\n', tab.draw()
def is_file(value):
try:
if isinstance(value, list):
if os.path.isfile(value[0]):
return True, value[0]
else:
return False, value[0]
elif isinstance(value, basestring):
if os.path.isfile(value):
return True, value
else:
return False, value
except IndexError:
print '\n[!] You need to provide some arguments\n'
sys.exit()
def jsondump(jdata, md5):
jsondumpfile = open('VTDL_{name}.json'.format(name = md5), 'w')
json.dump(jdata, jsondumpfile)
jsondumpfile.close()
print '\n\tJSON Written to File -- VTDL_{md5}.json\n'.format(md5 = md5)
def load_file(file_path):
try:
log = open(file_path, 'r').read()
jdata = json.loads(log)
return jdata
except TypeError:
print '\n[!] Check your json dump file\n'
def print_results(jdata, undetected_downloaded_samples, detected_communicated,\
undetected_communicated_samples, detected_urls):
if jdata.get('undetected_downloaded_samples') and undetected_downloaded_samples:
print '\n[+] Latest undetected files that were downloaded from this domain/ip\n'
pretty_print(sorted(jdata['undetected_downloaded_samples'], key=methodcaller('get', 'date'), reverse=True), ['positives', 'total','date','sha256'], [15, 10, 20, 70], ['c', 'c', 'c', 'c'])
if jdata.get('detected_communicating_samples') and detected_communicated:
print '\n[+] Latest detected files that communicate with this domain/ip\n'
pretty_print(sorted(jdata['detected_communicating_samples'] , key=methodcaller('get', 'scan_date'), reverse=True), ['positives', 'total','date','sha256'], [15, 10, 20, 70], ['c', 'c', 'c', 'c'])
if jdata.get('undetected_communicating_samples') and undetected_communicated_samples:
print '\n[+] Latest undetected files that communicate with this domain/ip\n'
pretty_print(sorted(jdata['undetected_communicating_samples'], key=methodcaller('get', 'date'), reverse=True), ['positives', 'total','date','sha256'], [15, 10, 20, 70], ['c', 'c', 'c', 'c'])
if jdata.get('detected_urls') and detected_urls:
print '\n[+] Latest detected URLs\n'
pretty_print(sorted(jdata['detected_urls'], key=methodcaller('get', 'scan_date'), reverse=True), ['positives', 'total','scan_date','url'], [15, 10, 20, 100], ['c', 'c', 'c', 'l'])
def parse_report(jdata, hash_report, verbose, dump, url_report = False, not_exit = False):
if jdata['response_code'] != 1:
if not not_exit:
return False
else:
print '\n[-] Status : {info}\n'.format(info=jdata['verbose_msg'])
sys.exit()
if jdata.get('scan_date') : print '\nScanned on : \n\t{0}'.format(jdata['scan_date'])
if jdata.get('total') : print '\nDetections:\n\t {positives}/{total} Positives/Total\n'.format(positives = jdata['positives'], total = jdata['total'])
if url_report:
if jdata.get('url') : print 'Scanned url :\n\t {url}'.format(url = jdata['url'])
else:
display_engines = ['Sophos','Kaspersky', 'TrendMicro']
for engine in display_engines:
try:
if jdata['scans'][engine]['result'] : print '\n\t%s Detection :' % engine ,jdata['scans'][engine]['result']
except KeyError:
print '\n\t%s Detection : None' % engine
print '\n\tResults for MD5 : ',jdata['md5']
print '\tResults for SHA1 : ',jdata['sha1']
print '\tResults for SHA256 : ',jdata['sha256']
if verbose == True and jdata.get('scans'):
print '\nVerbose VirusTotal Information Output:'
plist = [[]]
for x in jdata['scans']:
plist.append([x, 'True' if jdata['scans'][x]['detected'] else 'False', jdata['scans'][x]['result'] if jdata['scans'][x]['result'] else ' -- '])
pretty_print_special(plist, ['Vendor name', 'Detected', 'Result'], [30, 9, 55], ['l', 'c', 'l'])
del plist
if dump == True:
jsondump(jdata, hash_report)
if jdata.get('permalink') : print "\n\tPermanent Link : {0}\n".format(jdata['permalink'])
return True
## Static variable decorator for function
def static_var(varname, value):
def decorate(func):
setattr(func, varname, value)
return func
return decorate
## Track how many times we issue a request
@static_var("counter", 0)
## Track when the first request was sent
@static_var("start_time", 0)
def get_response(url, method="get", **kwargs):
## Set on first request
if get_response.start_time == 0:
get_response.start_time = time.time()
## Increment every request
get_response.counter = 1
jdata = ''
response = ''
while True:
response = getattr(requests, method)(url, **kwargs)
if response.status_code == 403:
private_api_access_error()
if response.status_code != 204:
try:
jdata = response.json()
except:
jdata = response.json
break
## Determine minimum time we need to wait for limit to reset
wait_time = 59 - int(time.time() - get_response.start_time)
if wait_time < 0:
wait_time = 60
print "Reached per minute limit of {0:d}; waiting {1:d} seconds\n".format(get_response.counter, wait_time)
time.sleep(wait_time)
## Reset static vars
get_response.counter = 0
get_response.start_time = 0
return jdata, response
class vtAPI():
def __init__(self, apikey):
self.api = apikey
self.base = 'https://www.virustotal.com/vtapi/v2/'
def getReport(self, hash_report, allinfo = False, verbose = False, dump = False, not_exit = False):
"""
A md5/sha1/sha256 hash will retrieve the most recent report on a given sample. You may also specify a scan_id (sha256-timestamp as returned by the file upload API)
to access a specific report. You can also specify a CSV list made up of a combination of hashes and scan_ids (up to 4 items or 25 if you have private api with the
standard request rate), this allows you to perform a batch request with one single call.
"""
result, name = is_file(hash_report)
if result:
jdata = load_file(name)
dump = False
else:
if isinstance(hash_report, list) and len(hash_report) == 1:
hash_report = hash_report[0]
elif isinstance(hash_report, basestring):
hash_report = hash_report
elif len(hash_report) > 25 and not isinstance(hash_report, basestring):
print '[-] To many urls for scanning, MAX 25'
sys.exit()
else:
hash_report = ', '.join(map(lambda hash_part: hash_part, hash_report))
print hash_report,4
params = {'resource':hash_report,'apikey':self.api}
if allinfo:
params.setdefault('allinfo', allinfo)
url = self.base + 'file/report'
jdata, response = get_response(url, params=params)
if jdata['response_code'] == 0 or jdata['response_code'] == -1:
if not_exit:
return False
else:
if jdata.get('verbose_msg') : print '\n[!] Status : {verb_msg}\n'.format(verb_msg = jdata['verbose_msg'])
sys.exit()
if allinfo == '1':
if dump:
jsondump(jdata, name)
if jdata.get('md5') : print '\nMD5 : {md5}'.format( md5 = jdata['md5'])
if jdata.get('sha1') : print 'SHA1 : {sha1}'.format( sha1 = jdata['sha1'])
if jdata.get('sha256') : print 'SHA256 : {sha256}'.format(sha256 = jdata['sha256'])
if jdata.get('ssdeep') : print 'SSDEEP : {ssdeep}'.format(ssdeep = jdata['ssdeep'])
if jdata.get('scan_date') : print '\nScan Date : {scan_date}'.format( scan_date = jdata['scan_date'])
if jdata.get('first_seen'): print 'First Submission : {first_seen}'.format(first_seen = jdata['first_seen'])
if jdata.get('last_seen') : print 'Last Submission : {last_seen}'.format( last_seen = jdata['last_seen'])
if jdata.get('submission_names'):
print '\nSubmission names:'
for name in jdata['submission_names']:
print '\t{name}'.format(name = name)
if jdata.get('type') : print '\nFile Type : {type_f}'.format(type_f = jdata['type'])
if jdata.get('size') : print 'File Size : {size}'.format( size = jdata['size'])
if jdata.get('tags') : print 'Tags : {tags}'.format(tags = ', '.join(map(lambda tag: tag, jdata['tags'])))
if jdata.get('additional_info'):
#[u'pe-resource-list', u'pe-resource-langs', u'pe-timestamp', u'imports', u'pe-entry-point', u'pe-resource-types', u'sections', u'pe-machine-type']
if jdata['additional_info']['magic'] : print 'Magic : {magic}'.format(magic = jdata['additional_info']['magic'])
if jdata['additional_info'].get('referers'):
print '\nReferers:'
for referer in jdata['additional_info']['referers']:
print '\t{referer}'.format(referer = referer)
if jdata['additional_info'].get('sigcheck'):
print '\nPE signature block:'
plist = [[]]
for sig in jdata['additional_info']['sigcheck']:
plist.append([sig, jdata['additional_info']['sigcheck'][sig]])
pretty_print_special(plist, ['Name','Value'])
del plist
if jdata['additional_info'].get('exiftool'):
print '\nExifTool file metadata:'
plist = [[]]
for exiftool in jdata['additional_info']['exiftool']:
plist.append([exiftool, jdata['additional_info']['exiftool'][exiftool]])
pretty_print_special(plist, ['Name','Value'])
del plist
if jdata['additional_info'].get('sections'):
pretty_print_special(jdata['additional_info']['sections'], ['Name', 'Virtual address', 'Virtual size', 'Raw size', 'Entropy', 'MD5'], [10,10,10,10,10, 35], ['c', 'c', 'c', 'c', 'c', 'c'])
if jdata['additional_info'].get('imports'):
print '\nImports:'
for imported in jdata['additional_info']['imports']:
print '\t{0}'.format(imported)
for valor in jdata['additional_info']['imports'][imported]:
print '\t\t{0}'.format(valor)
if jdata['additional_info'].get('trid'):
print '\nTrID:'
print '\t{trid}'.format(trid = jdata['additional_info']['trid'].replace('\n', '\n\t'))
if jdata.get('total') : print '\nDetections:\n\t{positives}/{total} Positives/Total\n'.format(positives = jdata['positives'], total = jdata['total'])
if jdata.get('scans'):
plist = [[]]
for x in jdata['scans']:
plist.append([x, 'True' if jdata['scans'][x]['detected'] else 'False', jdata['scans'][x]['result']])
pretty_print_special(plist, ['Name', 'Detected', 'Result'], [30, 9, 55], ['l', 'c', 'l'])
del plist
if jdata.get('permalink') : print '\nPermanent link : {permalink}\n'.format(permalink = jdata['permalink'])
return True
else:
result = parse_report(jdata, hash_report, verbose, dump, False, not_exit)
return result
def rescan(self, hash_rescan, date = False, period = False, repeat = False, notify_url = False, notify_changes_only = False, delete = False):
"""
This API allows you to rescan files in VirusTotal's file store without having to resubmit them, thus saving bandwidth.
"""
if len(hash_rescan) == 1:
hash_rescan = hash_rescan
elif isinstance(hash_re, basestring):
hash_rescan = [hash_rescan]
elif len(hash_rescan) > 25 and not isinstance(hash_rescan, basestring):
print '[-] To many urls for scanning, MAX 25'
sys.exit()
else:
hash_rescan = ', '.join(map(lambda hash_part: hash_part, hash_rescan))
url = self.base + 'file/rescan'
for hash_part in hash_rescan:
params = {'resource':hash_part,'apikey':self.api}
if delete:
url = url + '/delete'
else:
if date:
params.setdefault('date', date)
if period:
params.setdefault('period', period)
if repeat:
params.setdefault('repeat', repeat)
if notify_url:
params.setdefault('notify_url', notify_url)
if notify_changes_only:
params.setdefault('notify_changes_only',notify_changes_only)
jdata, response = get_response(url, params=params, method='post')
if jdata['response_code'] == 0 or jdata['response_code'] == -1:
if jdata.get('verbose_msg') : print '\n[!] Status : {verb_msg}\n'.format(verb_msg = jdata['verbose_msg'])
sys.exit()
if isinstance(jdata, list):
for jdata_part in jdata:
if jdata_part.get('sha256') : print '[+] Check rescan result with sha256 in few minuts : \n\tSHA256 : {sha256}'.format(sha256 = jdata_part['sha256'])
if jdata.get('permalink') : print '\tPermanent link : {permalink}\n'.format(permalink = jdata['permalink'])
else:
if jdata.get('sha256') : print '[+] Check rescan result with sha256 in few minuts : \n\tSHA256 : {sha256}'.format(sha256 = jdata_part['sha256'])
if jdata.get('permalink') : print '\tPermanent link : {permalink}\n'.format(permalink = jdata['permalink'])
def fileScan(self, files, verbose = False, notify_url = False, notify_changes_only = False, dump = False):
"""
Allows to send a file to be analysed by VirusTotal.
Before performing your submissions we encourage you to retrieve the latest report on the files,
if it is recent enough you might want to save time and bandwidth by making use of it. File size limit is 32MB,
in order to submmit files up to 200MB you must request an special upload URL.
Before send to scan, file will be checked if not scanned before, for save bandwich and VT resources :)
"""
if len(files) == 1 and isinstance(files, list):
files = glob.glob('{files}'.format(files=files[0]))
elif isinstance(files, basestring):
files = glob.glob('{files}'.format(files=files))
params = {'apikey':self.api}
if notify_url:
params.setdefault('notify_url', notify_url)
if notify_changes_only:
params.setdefault('notify_changes_only',notify_changes_only)
url = self.base+'file/scan'
for submit_file in files:
readed = open(submit_file, 'rb').read()
md5 = hashlib.md5(readed).hexdigest()
not_exit = True
result = self.getReport(md5, False, verbose, dump, not_exit)
if not result:
if (os.path.getsize(submit_file) / 1048576) <= 32:
if os.path.isfile(submit_file):
print 'Submiting file: {filename}'.format(filename = submit_file)
file_name = os.path.split(submit_file)[-1]
files = {"file": (file_name, open(submit_file, 'rb'))}
try:
jdata, response = get_response(url, files=files, params=params, method="post")
if jdata['response_code'] == 0 or jdata['response_code'] == -1:
if jdata.get('verbose_msg') : print '\n[!] Status : {verb_msg}\n'.format(verb_msg = jdata['verbose_msg'])
sys.exit()
if jdata.get('md5') : print '\n\tResults for MD5 : {md5}'.format(md5 = jdata['md5'])
if jdata.get('sha1') : print '\tResults for SHA1 : {sha1}'.format(sha1 = jdata['sha1'])
if jdata.get('sha256') : print '\tResults for SHA256 : {sha256}'.format(sha256 = jdata['sha256'])
if jdata.get('verbose_msg') : print '\n\tStatus : {verb_msg}'.format(verb_msg = jdata['verbose_msg'])
if jdata.get('permalink') : print '\tPermanent link : {permalink}\n'.format(permalink = jdata['permalink'])
except UnicodeDecodeError:
print '\n[!] Sorry filaname is not utf-8 format, other format not suported at the moment'
print '[!] Ignored file: {file}\n'.format(file = submit_file)
else:
print '[!] Ignored file: {file}'.format(file = submit_file)
def url_scan_and_report(self, urls, key, verbose, dump=False, add_to_scan='0'):
"""
Url scan:
URLs can also be submitted for scanning. Once again, before performing your submission we encourage you to retrieve the latest report on the URL,
if it is recent enough you might want to save time and bandwidth by making use of it.
Url report:
A URL will retrieve the most recent report on the given URL. You may also specify a scan_id (sha256-timestamp as returned by the URL submission API)
to access a specific report. At the same time, you can specify a space separated list made up of a combination of hashes and scan_ids so as to perform a batch
request with one single call (up to 4 resources or 25 if you have private api, per call with the standard request rate).
"""
result, name = is_file(urls)
md5 = ''
if result:
jdata = load_file(name)
dump = False
else:
if isinstance(urls, list) and len(urls) == 1:
url_upload = urls[0]
elif isinstance(urls, basestring):
url_upload = urls
elif len(urls) > 4 and not isinstance(urls, basestring):
print '[-] To many urls for scanning, MAX 4'
sys.exit()
else:
if key == 'scan':
url_upload = '\n'.join(map(lambda url: url, urls))
elif key == 'report':
url_upload = ', '.join(map(lambda url: url, urls))
if key == 'scan':
print 'Submitting url(s) for analysis: \n\t{url}'.format(url = url_upload.replace('\n','\n\t'))
params = {'url':url_upload,'apikey':self.api}
url = self.base + 'url/scan'
elif key == 'report':
print '\nSearching for url(s) report: \n\t{url}'.format(url = url_upload.replace(', ','\n\t'))
params = {'resource':url_upload,'apikey':self.api, 'scan':add_to_scan}
url = self.base + 'url/report'
jdata, response = get_response(url, params=params, method="post")
if isinstance(jdata, list):
for jdata_part in jdata:
if jdata_part['response_code'] == 0 or jdata_part['response_code'] == -1:
if jdata_part.get('verbose_msg') : print '\n[!] Status : {verb_msg}\n'.format(verb_msg = jdata_part['verbose_msg'])
break
if dump:
md5 = hashlib.md5(jdata_part['url']).hexdigest()
if key == 'report':
url_report = True
parse_report(jdata_part, md5, verbose, dump, url_report)
elif key == 'scan':
if jdata_part.get('verbose_msg') : print '\n\tStatus : {verb_msg}\t{url}'.format(verb_msg = jdata_part['verbose_msg'], url = jdata_part['url'])
if jdata_part.get('permalink') : print '\tPermanent link : {permalink}'.format(permalink = jdata_part['permalink'])
else:
if jdata['response_code'] == 0 or jdata['response_code'] == -1:
if jdata.get('verbose_msg') : print '\n[!] Status : {verb_msg}\n'.format(verb_msg = jdata['verbose_msg'])
sys.exit()
if dump:
md5 = hashlib.md5(jdata['url']).hexdigest()
if key == 'report':
url_report = True
parse_report(jdata, md5, verbose, dump, url_report)
elif key == 'scan':
if jdata.get('verbose_msg') : print '\n\tStatus : {verb_msg}\t{url}'.format(verb_msg = jdata['verbose_msg'], url = jdata['url'])
if jdata.get('permalink') : print '\tPermanent link : {permalink}'.format(permalink = jdata['permalink'])
def getIP(self, ip, dump=False, detected_urls=False, detected_downloaded_samples=False, undetected_downloaded_samples=False,\
detected_communicated=False, undetected_communicated=False):
"""
A valid IPv4 address in dotted quad notation, for the time being only IPv4 addresses are supported.
"""
result, name = is_file(ip)
if result:
jdata = load_file(name)
dump = False
md5 = ''
else:
params = {'ip':ip,'apikey':self.api}
url = self.base + 'ip-address/report'
jdata, response = get_response(url, params=params)
if jdata['response_code'] == 0 or jdata['response_code'] == -1:
if jdata.get('verbose_msg') : print '\n[!] Status : {verb_msg}\n'.format(verb_msg = jdata['verbose_msg'])
sys.exit()
if jdata['response_code'] == 1:
if jdata.get('verbose_msg') : print '\nStatus: {verb_msg}'.format(verb_msg = jdata['verbose_msg'])
if jdata.get('detected_downloaded_samples') and detected_downloaded_samples:
print '\n[+] Latest detected files that were downloaded from this domain/ip\n'
pretty_print(sorted(jdata['detected_downloaded_samples'], key=methodcaller('get', 'date'), reverse=True), ['positives', 'total','date','sha256'], [15, 10, 20, 70], ['c', 'c', 'c', 'c'])
print_results(jdata, undetected_downloaded_samples, detected_communicated, undetected_communicated, detected_urls)
if jdata.get('resolutions'):
print '\n[+] Lastest domain resolved\n'
pretty_print(sorted(jdata['resolutions'], key=methodcaller('get', 'last_resolved'), reverse=True), ['last_resolved', 'hostname'])
if dump == True:
md5 = hashlib.md5(name).hexdigest()
jsondump(jdata, md5)
else:
if jdata.get('verbose_msg') : print '\n[-] Status: {info}\n'.format(info=jdata['verbose_msg'])
sys.exit()
def getDomain(self, domain, dump=False, trendmicro=False, detected_urls=False, undetected_downloaded_samples=False, alexa_domain_info=False,\
wot_domain_info=False, websense_threatseeker=False, bitdefender=False, webutation_domain=False,\
detected_communicated=False, undetected_communicated=False, pcaps=False):
"""
Get domain last scan, detected urls and resolved IPs
"""
result, name = is_file(domain)
if result:
jdata = load_file(name)
dump = False
md5 = ''
else:
params = {'domain':domain,'apikey':self.api}
url = self.base + "domain/report"
jdata, response = get_response(url, params=params)
if jdata['response_code'] == 0 or jdata['response_code'] == -1:
if jdata.get('verbose_msg') : print '\n[!] Status : {verb_msg}\n'.format(verb_msg = jdata['verbose_msg'])
sys.exit()
if jdata.get('response_code') and jdata['response_code'] == 1:
if jdata.get('verbose_msg') : print '\nStatus : {verb_msg}'.format(verb_msg = jdata['verbose_msg'])
if jdata.get('TrendMicro category') and trendmicro:
print '\n[+] TrendMicro category'
print '\t',jdata['TrendMicro category']
if jdata.get('Websense ThreatSeeker category') and websense_threatseeker:
print '\n[+] Websense ThreatSeeker category'
print '\t', jdata['Websense ThreatSeeker category']
if jdata.get('BitDefender category') and bitdefender:
print '\n[+] BitDefender category'
print '\t', jdata['BitDefender category']
if jdata.get('Alexa domain info') and alexa_domain_info:
print '\n[+] Alexa domain info'
print '\t', jdata['Alexa domain info']
if jdata.get('WOT domain info') and wot_domain_info:
print '\n[+] WOT domain info'
plist = [[]]
for jdata_part in jdata['WOT domain info']:
plist.append([jdata_part, jdata['WOT domain info'][jdata_part]])
pretty_print_special(plist, ['Name', 'Value'], [25, 20], ['c','c'])
del plist
if jdata.get('Webutation domain info') and webutation_domain:
print "\n[+] Webutation"
plist = [[]]
for jdata_part in jdata['Webutation domain info']:
plist.append([jdata_part, jdata['Webutation domain info'][jdata_part]])
pretty_print_special(plist, ['Name', 'Value'], [25, 20], ['c','c'])
del plist
print_results(jdata, undetected_downloaded_samples, detected_communicated, undetected_communicated, detected_urls)
if jdata.get('pcaps') and pcaps:
print '\n'
pretty_print(jdata['pcaps'], ['pcaps'], [70], ['c'])
if jdata.get('resolutions'):
print '\n[+] Passive DNS replication\n'
pretty_print(sorted(jdata['resolutions'], key=methodcaller('get', 'last_resolved'), reverse=True), ['last_resolved', 'ip_address'], [25, 20], ['c', 'c'])
if dump == True:
md5 = hashlib.md5(name).hexdigest()
jsondump(jdata, md5)
else:
print '\n[-] {info}\n'.format(info=jdata['verbose_msg'])
def clusters(self, value, dump = False, by_id = False):
"""
VirusTotal has built its own in-house file similarity clustering functionality. At present,
this clustering works only on PE files and is based on a very basic PE feature hash, which
can be very often confused by certain compression and packing strategies. In other words,
this clustering logic is no holly grail.
This API offers a programmatic access to the clustering section of VirusTotal Intelligence:
https://www.virustotal.com/intelligence/clustering/
Please note that you must be logged in with a valid VirusTotal Community user with access
to VirusTotal Intelligence in order to be able to view the clustering listing.
All of the API responses are JSON objects, if no clusters were identified for the given
time frame, this JSON will have a response_code property equal to 0, if there was some
sort of error with your query this code will be set to -1, if your query succeded and
file similarity clusters were found it will have a value of 1 and the rest of the JSON
properties will contain the clustering information.
"""
result, name = is_file(value)
if result:
jdata = load_file(name)
dump = False
md5 = ''
else:
url = self.base + 'file/clusters'
params = {'apikey': self.api}
if by_id:
params.setdefault('query', 'cluster:{0}'.format(value))
else:
params.setdefault('date', name)
jdata, response = get_response(url, params=params)
if jdata['response_code'] == 0 or jdata['response_code'] == -1:
if jdata.get('verbose_msg') : print '\n[!] Status : {verb_msg}\n'.format(verb_msg = jdata['verbose_msg'])
sys.exit()
if jdata.get('verbose_msg') : print '\nStatus : {verb_msg}'.format(verb_msg = jdata['verbose_msg'])
if jdata.get('size_top200') : print '\n\tSize top 200 : {size_top200}'.format(size_top200 = jdata['size_top200'])
if jdata.get('num_clusters') : print '\tNum Clusters : {num_clusters}'.format(num_clusters = jdata['num_clusters'])
if jdata.get('num_candidates') : print '\tNum Candidates : {num_candidates}'.format(num_candidates = jdata['num_candidates'])
if jdata.get('clusters'):
plist = [[]]
for line in jdata['clusters']:
plist.append([line['label'], line['avg_positives'], line['id'], line['size']])
pretty_print_special(plist, ['Label', 'AV Detections', 'Id', 'Size'], [40, 15, 80, 8], ['l','c','l','c'])
if dump:
jsondump(jdata, 'clusters_{0}'.format(name))
def comment(self, hash_co, action, dump = False, before_or_comment = False):
"""
Add comment:
The actual review, you can tag it using the "#" twitter-like syntax (e.g. #disinfection #zbot) and reference users using the "@" syntax (e.g. @VirusTotalTeam).
Get comments:
The application answers with the comments sorted in descending order according to their date. Please note that, for timeout reasons, the application will only
answer back with at most 25 comments. If the answer contains less than 25 comments it means that there are no more comments for that item. On the other hand,
if 25 comments were returned you should keep issuing further calls making use of the optional before parameter, this parameter should be fixed to the oldest
(last in the list) comment's date token, exactly in the same way as returned by your previous API call (e.g. 20120404132340).
"""
result, name = is_file(hash_co)
if result:
jdata = load_file(name)
dump = False
md5 = ''
else:
params = {'resource':hash_co,'apikey':self.api}
if action == 'add':
url = self.base + 'comments/put'
params.setdefault('comment',before_or_commentz)
jdata, response = get_response(url, params=params, method="post")
elif action == 'get':
url = self.base + 'comments/get'
if before_or_comment:
params.setdefault('before', before_or_comment)
jdata, response = get_response(url, params=params)
else:
print '\n[!] Support only get/add comments action \n'
sys.exit()
if jdata['response_code'] == 0 or jdata['response_code'] == -1:
if jdata.get('verbose_msg') : print '\n[!] Status : {verb_msg}\n'.format(verb_msg = jdata['verbose_msg'])
sys.exit()
if action == 'add':
if jdata.get('verbose_msg') : print '\nStatus : {0}\n'.format(jdata['verbose_msg'])
else:
if jdata['response_code'] == 0:
print '\n[!] This analysis doen\'t have any comment\n'
else:
if jdata.get('comments'):
for comment in jdata['comments']:
date_format = time.strptime(comment['date'], '%Y%m%d%H%M%S')
date_formated = '{year}:{month}:{day} {hour}:{minuts}:{seconds}'.format(year = date_format.tm_year,month = date_format.tm_mon,\
day = date_format.tm_mday, hour = date_format.tm_hour,\
minuts = date_format.tm_min, seconds = date_format.tm_sec)
if comment.get('date') : print 'Date : {0}'.format(date_formated)
if comment.get('comment') : print 'Comment : {0}\n'.format(comment['comment'])
def download(self, hash_file, file_type = False):
"""
About pcaps
VirusTotal runs a distributed setup of Cuckoo sandbox machines that execute the files we receive.
Execution is attempted only once, upon first submission to VirusTotal, and only Portable Executables
under 10MB in size are ran. The execution of files is a best effort process, hence, there are no guarantees
about a report being generated for a given file in our dataset.
Files that are successfully executed may communicate with certain network resources, all this communication
is recorded in a network traffic dump (pcap file). This API allows you to retrieve the network traffic dump
generated during the file's execution.
The md5/sha1/sha256 hash of the file whose network traffic dump you want to retrieve.
"""
result, name = is_file(hash_file)
if result:
print '\n[!]Hash cannot be file\n'
sys.exit()
else:
params = {'apikey': self.api, 'hash': hash_file}
if file_type == 'pcap':
jdata, response = get_response(self.base + 'file/network-traffic', params=params)
name = 'VTDL_{hash}.pcap'.format(hash = hash_file)
elif file_type == 'file':
jdata, response = get_response(self.base + 'file/download', params=params)
if jdata['response_code'] == 0 or jdata['response_code'] == -1:
if jdata.get('verbose_msg') : print '\n[!] Status : {verb_msg}\n'.format(verb_msg = jdata['verbose_msg'])
sys.exit()
name = 'VTDL_{hash}.dangerous'.format(hash = hash_file)
else:
print '\n[!] File_type must be pcap or file\n'
sys.exit()
file_save = response.content
if len(file_save) > 0 and '{"response_code": 0, "hash":' not in file_save :
fo = open(name,"w")
fo.write(file_save)
fo.close()
print '\n\tDownloaded to File -- {name}\n'.format(name = name)
def distribution(self, local_file, action, before = False, after = False, reports = False, limit = False, allinfo = False, dump = False):
"""
Note that scan items are not kept forever in the distribution queue, they are automatically removed after 6 hours counting from the time
they were put in the queue. You have a 6 hours time frame to get an item from the queue. The timestamp property value is what you need to
iterate through your queue using the before and after call parameters.
"""
result, name = is_file(local_file)
if result:
jdata = load_file(name)
dump = False
else:
params = {'apikey':self.api}
if before:
params.setdefault('before', before)
if after:
params.setdefault('after', after)
if limit:
params.setdefault('limit', limit)
if action == 'file':
if reports:
params.setdefault('reports', str(reports).lower())
url = self.base + 'file/distribution'
elif action == 'url':
if allinfo:
params.setdefault('allinfo', '1')
url = self.base + 'url/distribution'
jdata, response = get_response(url, params=params)
if jdata['response_code'] == 0 or jdata['response_code'] == -1:
if jdata.get('verbose_msg') : print '\n[!] Status : {verb_msg}\n'.format(verb_msg = jdata['verbose_msg'])
sys.exit()
for vt_file in jdata:
if action == 'file':
try:
if vt_file.get('name') : print '\n\nName : {name}'.format(name = vt_file['name'])