Skip to content

Commit ec405a3

Browse files
author
netevert
committed
added virustotal integration
1 parent 0f77dd4 commit ec405a3

File tree

2 files changed

+215
-5
lines changed

2 files changed

+215
-5
lines changed

pockint.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ def getID(self, item):
245245

246246
def selectItem(self, event=None):
247247
"""Selects item in treeview and inserts in search box"""
248+
# todo: select node element in treeview
248249
curItem = self.treeview.focus()
249250
self.entry.delete(0, 'end')
250251
try:

utils.py

Lines changed: 214 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,28 +81,177 @@ def close_connection(self):
8181
"""Closes the connection to the local database file"""
8282
self.db.close()
8383

84+
class Sha256Hash(object):
85+
"""Md5 hash handler class"""
86+
def __init__(self):
87+
self.osint_options = {}
88+
self.api_db = Database()
89+
self.virustotal_api_key = self.api_db.get_api_key("virustotal")
90+
if self.virustotal_api_key:
91+
self.osint_options.update({
92+
"virustotal: malicious check": self.virustotal_is_malicious,
93+
"virustotal: malware type": self.virustotal_malware_type})
94+
95+
def is_sha256(self, _input: str):
96+
"""Validates if _input is an md5 hash"""
97+
if validators.hashes.sha256(_input):
98+
return True
99+
return False
100+
101+
def virustotal_is_malicious(self, _hash:str):
102+
"""Checks virustotal to see if sha256 has positive detections"""
103+
try:
104+
data = make_vt_api_request(
105+
"https://www.virustotal.com/vtapi/v2/file/report",
106+
self.virustotal_api_key,
107+
{"resource": _hash}
108+
)
109+
if data:
110+
if data.json().get("response_code") == 0:
111+
return ["no report available"]
112+
return ["hash malicious: {} detections".format(data.json().get("positives"))]
113+
else:
114+
return ["no data available"]
115+
except Exception as e:
116+
return ["virustotal api error: ", e]
117+
118+
def virustotal_malware_type(self, _hash:str):
119+
"""Checks virustotal to return malware types detected by scans"""
120+
try:
121+
data = make_vt_api_request(
122+
"https://www.virustotal.com/vtapi/v2/file/report",
123+
self.virustotal_api_key,
124+
{"resource": _hash}
125+
)
126+
if data:
127+
if data.json().get("response_code") == 1:
128+
return ["{}: {}".format(i, data.json().get("scans").get(i).get("result"))
129+
for i in data.json().get("scans")
130+
if data.json().get("scans").get(i).get("result")]
131+
return ["no report available"]
132+
else:
133+
return ["no data available"]
134+
except Exception as e:
135+
return ["virustotal api error: ", e]
136+
84137
class Md5Hash(object):
85138
"""Md5 hash handler class"""
86139
def __init__(self):
87140
self.osint_options = {}
141+
self.api_db = Database()
142+
self.virustotal_api_key = self.api_db.get_api_key("virustotal")
143+
if self.virustotal_api_key:
144+
self.osint_options.update({
145+
"virustotal: malicious check": self.virustotal_is_malicious,
146+
"virustotal: malware type": self.virustotal_malware_type})
88147

89148
def is_md5(self, _input: str):
90149
"""Validates if _input is an md5 hash"""
91150
if validators.hashes.md5(_input):
92151
return True
93152
return False
94153

154+
def virustotal_is_malicious(self, _hash:str):
155+
"""Checks virustotal to see if MD5 has positive detections"""
156+
try:
157+
data = make_vt_api_request(
158+
"https://www.virustotal.com/vtapi/v2/file/report",
159+
self.virustotal_api_key,
160+
{"resource": _hash}
161+
)
162+
if data:
163+
if data.json().get("response_code") == 0:
164+
return ["no report available"]
165+
return ["hash malicious: {} detections".format(data.json().get("positives"))]
166+
else:
167+
return ["no data available"]
168+
except Exception as e:
169+
return ["virustotal api error: ", e]
170+
171+
def virustotal_malware_type(self, _hash:str):
172+
"""Checks virustotal to return malware types detected by scans"""
173+
try:
174+
data = make_vt_api_request(
175+
"https://www.virustotal.com/vtapi/v2/file/report",
176+
self.virustotal_api_key,
177+
{"resource": _hash}
178+
)
179+
if data:
180+
if data.json().get("response_code") == 1:
181+
return ["{}: {}".format(i, data.json().get("scans").get(i).get("result"))
182+
for i in data.json().get("scans")
183+
if data.json().get("scans").get(i).get("result")]
184+
return ["no report available"]
185+
else:
186+
return ["no data available"]
187+
except Exception as e:
188+
return ["virustotal api error: ", e]
189+
190+
95191
class Url(object):
96192
"""Url handler class"""
97193
def __init__(self):
98-
self.osint_options = {}
194+
self.osint_options = {
195+
"dns: extract hostname": self.url_to_hostname
196+
}
197+
self.api_db = Database()
198+
self.virustotal_api_key = self.api_db.get_api_key("virustotal")
199+
if self.virustotal_api_key:
200+
self.osint_options.update({
201+
"virustotal: malicious check": self.is_malicious,
202+
"virustotal: reported detections": self.reported_detections})
99203

100204
def is_url(self, _input: str):
101205
"""Validates if _input is a url"""
102206
if validators.url(_input):
103207
return True
104208
return False
105209

210+
def is_malicious(self, url: str):
211+
"""Checks if url is malicious"""
212+
try:
213+
data = make_vt_api_request(
214+
"https://www.virustotal.com/vtapi/v2/url/report",
215+
self.virustotal_api_key,
216+
{"resource": url}
217+
)
218+
if data:
219+
if data.json().get("response_code") == 1:
220+
return ["url malicious: {} detections".format(data.json().get("positives"))]
221+
return ["no report available"]
222+
else:
223+
return ["no data available"]
224+
except Exception as e:
225+
return ["virustotal api error: ", e]
226+
227+
def reported_detections(self, url: str):
228+
"""Checks virustotal to determine which sites are reporting the url"""
229+
try:
230+
data = make_vt_api_request(
231+
"https://www.virustotal.com/vtapi/v2/url/report",
232+
self.virustotal_api_key,
233+
{"resource": url}
234+
)
235+
if data:
236+
if data.json().get("response_code") == 1:
237+
return ["{}: {}".format(i, data.json().get("scans").get(i).get("result"))
238+
for i in data.json().get("scans")
239+
if (data.json().get("scans").get(i).get("result") == "malicious site") or
240+
(data.json().get("scans").get(i).get("result") == "malware site")]
241+
return ["no report available"]
242+
else:
243+
return ["no data available"]
244+
except Exception as e:
245+
return ["virustotal api error: ", e] # todo: change to return ["error: " + e]
246+
247+
def url_to_hostname(self, url: str):
248+
"""Extracts hostname from url"""
249+
try:
250+
return [urlparse(url).netloc]
251+
except Exception as e:
252+
return ["error: " + e]
253+
254+
106255
class IPAdress(object):
107256
"""Ip address handler class"""
108257
def __init__(self):
@@ -288,13 +437,13 @@ def ip_to_vt_detected_urls(self, ip:str):
288437
else:
289438
return ["no data available"]
290439
except Exception as e:
291-
return ["virustotal api error: ", e]
440+
return ["virustotal api error: ", e] # todo: return ["virustotal api error: " + e]
441+
292442

293443
class EmailAddress(object):
294444
"""Email address handler class"""
295445
def __init__(self):
296446
self.osint_options = {
297-
"haveibeenpwnd": self.hibp_lookup,
298447
"extract domain": self.domain_extract
299448
}
300449

@@ -311,6 +460,7 @@ def domain_extract(self, email: str):
311460
"""Returns domain from supplied email"""
312461
return [email.split("@")[1]]
313462

463+
314464
class Domain(object):
315465
"""Domain handler class"""
316466
def __init__(self):
@@ -325,6 +475,13 @@ def __init__(self):
325475
if shodan_api_key:
326476
self.shodan_api = shodan.Shodan(shodan_api_key)
327477
self.osint_options.update({"shodan: hostnames": self.to_shodan_hostnames})
478+
self.virustotal_api_key = self.api_db.get_api_key("virustotal")
479+
if self.virustotal_api_key:
480+
self.osint_options.update({
481+
"virustotal: downloaded samples": self.domain_to_vt_downloaded_samples,
482+
"virustotal: detected urls": self.domain_to_vt_detected_urls,
483+
"virustotal: subdomains": self.domain_to_vt_subdomains
484+
})
328485

329486
def is_valid_domain(self, _input: str):
330487
"""Checks if _input is a domain"""
@@ -344,7 +501,7 @@ def to_mx_records(self, domain: str):
344501
try:
345502
return [x.exchange for x in dns.resolver.query(domain, 'MX')]
346503
except Exception as e:
347-
raise e
504+
raise e # todo: return ["virustotal api error: " + e]
348505

349506
def to_txt_records(self, domain: str):
350507
"""Returns dns txt record for domain"""
@@ -373,7 +530,50 @@ def to_shodan_hostnames(self, domain: str):
373530
else:
374531
return ["no data available"]
375532
except Exception as e:
376-
return ["shodan api error: ", e]
533+
return ["shodan api error: " + e]
534+
535+
def domain_to_vt_detected_urls(self, domain:str):
536+
"""Searches virustotal to search for detected communicating samples"""
537+
try:
538+
data = make_vt_api_request(
539+
"https://www.virustotal.com/vtapi/v2/domain/report",
540+
self.virustotal_api_key,
541+
{"domain":domain})
542+
if data:
543+
return [record.get("url") for record in data.json()["detected_urls"]]
544+
else:
545+
return ["no data available"]
546+
except Exception as e:
547+
return ["virustotal api error: " + e]
548+
549+
def domain_to_vt_downloaded_samples(self, domain:str):
550+
"""Searches virustotal to search for detected communicating samples"""
551+
try:
552+
data = make_vt_api_request(
553+
"https://www.virustotal.com/vtapi/v2/domain/report",
554+
self.virustotal_api_key,
555+
{"domain":domain})
556+
if data:
557+
return [record.get("sha256") for record in data.json()["detected_downloaded_samples"]]
558+
else:
559+
return ["no data available"]
560+
except Exception as e:
561+
return ["virustotal api error: " + e]
562+
563+
def domain_to_vt_subdomains(self, domain: str):
564+
"""Searches virustotal for subdomains"""
565+
try:
566+
data = make_vt_api_request(
567+
"https://www.virustotal.com/vtapi/v2/domain/report",
568+
self.virustotal_api_key,
569+
{"domain":domain})
570+
if data:
571+
return data.json().get("subdomains")
572+
else:
573+
return ["no data available"]
574+
except Exception as e:
575+
return ["virustotal api error: " + e]
576+
377577

378578
class InputValidator(object):
379579
"""Handler to validate user inputs"""
@@ -383,6 +583,7 @@ def __init__(self):
383583
self.domain = Domain()
384584
self.url = Url()
385585
self.md5 = Md5Hash()
586+
self.sha256 = Sha256Hash()
386587

387588
def run(self, _function, **kwargs):
388589
"""Runs function and associated keyword arguments"""
@@ -403,6 +604,8 @@ def validate(self, _input: str):
403604
return [True, "input: url", [option for option in self.url.osint_options.keys()]]
404605
elif self.md5.is_md5(_input):
405606
return [True, "input: md5", [option for option in self.md5.osint_options.keys()]]
607+
elif self.sha256.is_sha256(_input):
608+
return [True, "input: sha256", [option for option in self.sha256.osint_options.keys()]]
406609
return [False, []]
407610

408611
def execute_transform(self, _input: str, transform: str):
@@ -413,6 +616,12 @@ def execute_transform(self, _input: str, transform: str):
413616
return self.run(self.email.osint_options.get(transform), email=_input)
414617
elif self.domain.is_valid_domain(_input):
415618
return self.run(self.domain.osint_options.get(transform), domain=_input)
619+
elif self.md5.is_md5(_input):
620+
return self.run(self.md5.osint_options.get(transform), _hash=_input)
621+
elif self.url.is_url(_input):
622+
return self.run(self.url.osint_options.get(transform), url=_input)
623+
elif self.sha256.is_sha256(_input):
624+
return self.run(self.sha256.osint_options.get(transform), _hash=_input)
416625

417626
def load_icon():
418627
"""loads and returns program icon from base64 string"""

0 commit comments

Comments
 (0)