1
2
3
4
5
6 import logging
7 import re
8
9 try:
10 import requests
11 HAVE_REQUESTS = True
12
13
14 logging.getLogger("requests").setLevel(logging.WARNING)
15 logging.getLogger("urllib3").setLevel(logging.WARNING)
16 except ImportError:
17 HAVE_REQUESTS = False
18
19 from lib.cuckoo.common.exceptions import CuckooOperationalError
20 from lib.cuckoo.common.objects import File
21
23 """This resource has not been scanned yet."""
24
26 """Wrapper to VirusTotal API."""
27
28 FILE_REPORT = "https://www.virustotal.com/vtapi/v2/file/report"
29 URL_REPORT = "https://www.virustotal.com/vtapi/v2/url/report"
30 FILE_SCAN = "https://www.virustotal.com/vtapi/v2/file/scan"
31 URL_SCAN = "https://www.virustotal.com/vtapi/v2/url/scan"
32
33 VARIANT_BLACKLIST = [
34 "generic", "malware", "trojan", "agent", "win32", "multi", "w32",
35 "trojanclicker", "trojware", "win", "a variant of win32", "trj",
36 "susp", "dangerousobject", "backdoor", "clicker", "variant", "heur",
37 "gen", "virus", "dropper", "generic suspicious", "spyware", "program",
38 "suspectcrc", "corrupt", "behaveslike", "crypt", "adclicker",
39 "troj", "injector", "cryptor", "packed", "adware", "macro", "msil4",
40 "suspicious", "worm", "msil", "msword", "drop", "keygen", "office",
41 "password", "malpack", "lookslike", "banker", "riskware", "unwanted",
42 "unclassifiedmalware", "ransom", "trojan horse", "trjndwnlder",
43 "trojandwnldr", "autorun", "trojandownloader", "trojandwnldr", "text",
44 "download", "excel", "msilobfuscator", "rootkit", "application",
45 "a variant of win64", "w97m", "shellcode", "o97m", "exploit",
46 "x97m", "maliciousmacro", "downldr", "msexcel", "pp97m", "other",
47 "trojandropper", "crypter", "a variant of msil", "macrodown",
48 "trojanapt", "dwnldr", "downldexe", "dload", "trojanhorse", "toolbar",
49 "mailer", "obfus", "obfuscator", "suspicious file", "optional",
50 "suspected of trojan", "heuristic", "rogue", "virtool", "infostealer",
51 "generic downloader", "generic malware", "undef", "inject", "packer",
52 "generic backdoor", "word", "macosx", "hack", "unknown", "downloader",
53 "trojanspy", "dldr", "msoffice", "osx32", "script", "stealer",
54 "not a virus", "html", "expl", "shellkode", "downagent", "win64",
55 "applicunwnt", "heur2", "ddos", "avkill", "servstart", "normal",
56 "encoder", "w2km_dloader", "docdl", "w97m_dloadr", "mo97", "dloader",
57 "x2km_dloadr", "w2km_dload", "w2km_dloade", "x2km_droppr", "exedown",
58 "encodefeature", "docdrop", "mw97", "adload", "a variant of pp97m",
59 "a variant of w97m", "badmacro", "bkdr", "docdrp", "exedrop",
60 "generic trojan", "malcrypt", "malicious website", "ransomlock",
61 "ransomcrypt", "reputation", "trojanransom", "pepatch", "risk",
62 "adplugin", "webtoolbar", "malagent", "genmalicious", "vbinject",
63 "vbcrypt", "inject2", "mdropper", "download3", "keylogger",
64 "downloader11", "damaged", "file", "dldrop", "msil7", "injcrypt",
65 "patched", "patchfile", "downware", "dropped",
66 ]
67
68 - def __init__(self, apikey, timeout, scan=0):
69 """Initialize VirusTotal API with the API key and timeout.
70 @param api_key: virustotal api key
71 @param timeout: request and response timeout
72 @param scan: send file to scan or just get report
73 """
74 self.apikey = apikey
75 self.timeout = timeout
76 self.scan = scan
77
79 """Wrapper around doing a request and parsing its JSON output."""
80 if not HAVE_REQUESTS:
81 raise CuckooOperationalError(
82 "The VirusTotal processing module requires the requests "
83 "library (install with `pip install requests`)")
84
85 try:
86 r = requests.post(url, timeout=self.timeout, **kwargs)
87 return r.json() if r.status_code == 200 else {}
88 except (requests.ConnectionError, ValueError) as e:
89 raise CuckooOperationalError("Unable to fetch VirusTotal "
90 "results: %r" % e.message)
91
93 """Fetch the report of a file or URL."""
94 data = dict(resource=resource, apikey=self.apikey)
95
96 r = self._request_json(url, data=data)
97
98
99
100 if not r.get("response_code"):
101 if self.scan:
102 raise VirusTotalResourceNotScanned
103 else:
104 return {
105 "summary": {
106 "error": "resource has not been scanned yet",
107 }
108 }
109
110 results = {
111 "summary": {
112 "positives": r.get("positives", 0),
113 "permalink": r.get("permalink"),
114 "scan_date": r.get("scan_date"),
115 },
116 }
117
118
119 results.update(r)
120
121 if not summary:
122 results["scans"] = {}
123 results["normalized"] = []
124
125
126 for engine, signature in r.get("scans", {}).items():
127 signature["normalized"] = self.normalize(signature["result"])
128 results["scans"][engine.replace(".", "_")] = signature
129
130
131
132 norm_lower = []
133 for signature in results["scans"].values():
134 for normalized in signature["normalized"]:
135 if normalized.lower() not in norm_lower:
136 results["normalized"].append(normalized)
137 norm_lower.append(normalized.lower())
138
139 return results
140
142 """Get the report of an existing URL scan.
143 @param url: URL
144 @param summary: if you want a summary report"""
145 return self._get_report(self.URL_REPORT, url, summary)
146
148 """Get the report of an existing file scan.
149 @param filepath: file path
150 @param summary: if you want a summary report"""
151 resource = File(filepath).get_md5()
152 return self._get_report(self.FILE_REPORT, resource, summary)
153
155 """Submit a URL to be scanned.
156 @param url: URL
157 """
158 data = dict(apikey=self.apikey, url=url)
159 r = self._request_json(self.URL_SCAN, data=data)
160 return dict(summary=dict(permalink=r.get("permalink")))
161
163 """Submit a file to be scanned.
164 @param filepath: file path
165 """
166 data = dict(apikey=self.apikey)
167 files = {"file": open(filepath, "rb")}
168 r = self._request_json(self.FILE_SCAN, data=data, files=files)
169 return dict(summary=dict(permalink=r.get("permalink")))
170
172 """Normalize the variant name provided by an Anti Virus engine. This
173 attempts to extract the useful parts of a variant name by stripping
174 all the boilerplate stuff from it."""
175 if not variant:
176 return []
177
178 ret = []
179
180
181 cve = re.search("CVE[-_]?(\\d{4})[-_](\\d{4})", variant)
182 if cve:
183 ret.append("CVE-%s-%s" % (cve.group(1), cve.group(2)))
184
185
186 cve = re.search("CVE(\\d{2})(\\d{4})", variant)
187 if cve:
188 ret.append("CVE-20%s-%s" % (cve.group(1), cve.group(2)))
189
190 for word in re.split("[\\.\\,\\-\\(\\)\\[\\]/!:_]", variant):
191 word = word.strip()
192 if len(word) < 4:
193 continue
194
195 if word.lower() in self.VARIANT_BLACKLIST:
196 continue
197
198
199 if re.match("[a-fA-F0-9]+$", word):
200 continue
201
202
203 if re.match("[a-zA-Z]{1,2} potentially unwanted", word.lower()):
204 continue
205
206 ret.append(word)
207 return ret
208