|
1 | 1 | #!/usr/bin/env python3
|
2 | 2 | # encoding: utf-8
|
| 3 | + |
3 | 4 | import time
|
4 | 5 | import hashlib
|
5 | 6 |
|
6 | 7 | from malwares_api import Api
|
7 | 8 | from cortexutils.analyzer import Analyzer
|
8 | 9 |
|
9 |
| -from io import StringIO |
10 |
| - |
11 | 10 |
|
12 | 11 | class MalwaresAnalyzer(Analyzer):
|
13 |
| - |
14 | 12 | def __init__(self):
|
15 | 13 | Analyzer.__init__(self)
|
16 |
| - self.service = self.get_param('config.service', None, 'Service parameter is missing') |
17 |
| - self.key = self.get_param('config.key', None, 'Missing Malware API key') |
18 |
| - self.polling_interval = self.get_param('config.polling_interval', 60) |
| 14 | + self.service = self.get_param( |
| 15 | + "config.service", None, "Service parameter is missing" |
| 16 | + ) |
| 17 | + self.key = self.get_param("config.key", None, "Missing Malware API key") |
| 18 | + self.polling_interval = self.get_param("config.polling_interval", 60) |
19 | 19 | self.m_api = Api(self.key)
|
20 | 20 |
|
| 21 | + def check_response(self, response): |
| 22 | + if type(response) is not dict: |
| 23 | + self.error("Bad response : " + str(response)) |
| 24 | + status = response.get("response_code", 500) |
| 25 | + if status == 429: |
| 26 | + self.error("Malwares api rate limit exceeded.") |
| 27 | + elif status >= 400: |
| 28 | + self.error("Bad status : " + str(status)) |
| 29 | + return response.get("results", {}) |
| 30 | + |
21 | 31 | def wait_file_report(self, id):
|
22 |
| - results = self.check_response(self.m_api.get_file_report(id)) |
23 |
| - code = results.get('result_code', None) |
24 |
| - if code == 1: |
| 32 | + response = self.m_api.get_file_report(id) |
| 33 | + results = self.check_response(response) |
| 34 | + status = response.get("response_code", 500) |
| 35 | + if status == 200: |
25 | 36 | self.report(results)
|
26 | 37 | else:
|
27 | 38 | time.sleep(self.polling_interval)
|
28 | 39 | self.wait_file_report(id)
|
29 | 40 |
|
30 |
| - def wait_url_report(self, id): |
31 |
| - results = self.check_response(self.m_api.get_url_report(id)) |
32 |
| - code = results.get('result_code', None) |
33 |
| - if code == 1: |
34 |
| - self.report(results) |
35 |
| - else: |
36 |
| - time.sleep(self.polling_interval) |
37 |
| - self.wait_url_report(id) |
38 |
| - |
39 |
| - def check_response(self, response): |
40 |
| - if type(response) is not dict: |
41 |
| - self.error('Bad response : ' + str(response)) |
42 |
| - status = response.get('response_code', -1) |
43 |
| - if status in (-14, -15): |
44 |
| - self.error('Malwares api rate limit exceeded.') |
45 |
| - elif int(status) < 1: |
46 |
| - self.error('Bad status : ' + str(status)) |
47 |
| - results = response.get('results', {}) |
48 |
| - return results |
49 |
| - |
50 |
| - # 0 => not found |
51 |
| - # -2 => in queue |
52 |
| - # 1 => ready |
53 |
| - |
54 | 41 | def read_scan_response(self, response, func):
|
55 | 42 | results = self.check_response(response)
|
56 |
| - code = results.get('result_code', None) |
57 |
| - md5 = results.get('md5', None) |
58 |
| - url = results.get('url', None) |
59 |
| - if code in (1, 2) and md5 is not None: |
60 |
| - func(md5) |
61 |
| - elif code in (1, 2) and url is not None: |
62 |
| - func(url) |
| 43 | + status = response.get("response_code", 500) |
| 44 | + sha256 = results.get("ctx_data", {}).get("sha256", None) |
| 45 | + |
| 46 | + if status in (200, 202) and sha256 is not None: |
| 47 | + func(sha256) |
63 | 48 | else:
|
64 |
| - self.error('%d %s %s - Scan not found' % (code, md5, url)) |
| 49 | + self.error("%d %s - Scan not found" % (status, sha256)) |
65 | 50 |
|
66 | 51 | def summary(self, raw):
|
67 | 52 | taxonomies = []
|
68 | 53 | level = "info"
|
69 | 54 | namespace = "Malwares"
|
70 |
| - predicate = "Score" |
71 |
| - value = "No info" |
72 |
| - score = -1 |
73 |
| - |
74 |
| - result = { |
75 |
| - "has_result": True |
76 |
| - } |
77 |
| - |
78 |
| - if "ai_score" in raw.keys(): |
79 |
| - score = raw["ai_score"] |
80 |
| - if score < 10: |
81 |
| - level = "safe" |
82 |
| - elif 10 <= score < 30: |
83 |
| - level = "suspicious" |
84 |
| - else: |
85 |
| - level = "malicious" |
86 |
| - |
87 |
| - result['score'] = score |
88 | 55 |
|
89 |
| - value = "{}/100".format(score) |
| 56 | + ctx_data = raw.get("ctx_data", {}) |
| 57 | + detect = ctx_data.get("detect", "malicious") |
| 58 | + tags = raw.get("ctx_data", {}).get("tags", []) |
90 | 59 |
|
| 60 | + if detect == "normal": |
| 61 | + level = "safe" |
91 | 62 | else:
|
92 |
| - if "detected_communicating_file" in raw.keys() or "detected_url" in raw.keys() or "detected_downloaded_file" in raw.keys(): |
93 |
| - score = max( |
94 |
| - raw.get("detected_communicating_file", {}).get("total", 0), |
95 |
| - raw.get("detected_url", {}).get("total", 0), |
96 |
| - raw.get("detected_downloaded_file", {}).get("total", 0) |
97 |
| - ) |
98 |
| - value = "{} results".format(score) |
99 |
| - |
100 |
| - elif "virustotal" in raw.keys(): |
101 |
| - score = raw.get("virustotal", {}).get("positives", 0) |
102 |
| - total = raw.get("virustotal", {}).get("total", 0) |
103 |
| - value = "{}/{} positives".format(score, total) |
104 |
| - |
105 |
| - if score == 0: |
106 |
| - level = "safe" |
107 |
| - elif 0 < score <= 5: |
108 |
| - level = "suspicious" |
109 |
| - elif score > 5: |
110 |
| - level = "malicious" |
111 |
| - |
112 |
| - taxonomies.append(self.build_taxonomy( |
113 |
| - level, namespace, predicate, value)) |
| 63 | + level = "malicious" |
| 64 | + |
| 65 | + taxonomies.append(self.build_taxonomy(level, namespace, "Detect", detect)) |
| 66 | + taxonomies.append(self.build_taxonomy(level, namespace, "Tags", tags)) |
| 67 | + |
114 | 68 | return {"taxonomies": taxonomies}
|
115 | 69 |
|
116 | 70 | def run(self):
|
117 |
| - if self.service == 'scan': |
118 |
| - if self.data_type == 'file': |
119 |
| - filename = self.get_param('filename', 'noname.ext') |
120 |
| - filepath = self.get_param('file', None, 'File is missing') |
121 |
| - self.read_scan_response(self.m_api.scan_file( |
122 |
| - open(filepath, 'rb'), filename), self.wait_file_report) |
123 |
| - elif self.data_type == 'url': |
124 |
| - data = self.get_param('data', None, 'Data is missing') |
| 71 | + if self.service == "scan": |
| 72 | + if self.data_type == "file": |
| 73 | + filename = self.get_param("filename", "noname.ext") |
| 74 | + filepath = self.get_param("file", None, "File is missing") |
125 | 75 | self.read_scan_response(
|
126 |
| - self.m_api.scan_url(data), self.wait_url_report) |
| 76 | + self.m_api.scan_file(filepath, filename), self.wait_file_report |
| 77 | + ) |
127 | 78 | else:
|
128 |
| - self.error('Invalid data type') |
129 |
| - elif self.service == 'get': |
130 |
| - if self.data_type == 'domain': |
131 |
| - data = self.get_param('data', None, 'Data is missing') |
132 |
| - self.report(self.check_response( |
133 |
| - self.m_api.get_domain_report(data))) |
134 |
| - elif self.data_type == 'ip': |
135 |
| - data = self.get_param('data', None, 'Data is missing') |
| 79 | + self.error("Invalid data type") |
| 80 | + |
| 81 | + elif self.service == "get": |
| 82 | + if self.data_type == "domain": |
| 83 | + data = self.get_param("data", None, "Data is missing") |
| 84 | + self.report(self.check_response(self.m_api.get_domain_report(data))) |
| 85 | + elif self.data_type == "ip": |
| 86 | + data = self.get_param("data", None, "Data is missing") |
136 | 87 | self.report(self.check_response(self.m_api.get_ip_report(data)))
|
137 |
| - elif self.data_type == 'file': |
138 |
| - |
139 |
| - hashes = self.get_param('attachment.hashes', |
140 |
| - None) |
| 88 | + elif self.data_type == "file": |
| 89 | + hashes = self.get_param("attachment.hashes", None) |
141 | 90 | if hashes is None:
|
142 |
| - filepath = self.get_param('file', None, 'File is missing') |
143 |
| - hash = hashlib.sha256(open(filepath, 'rb').read()).hexdigest(); |
| 91 | + filepath = self.get_param("file", None, "File is missing") |
| 92 | + hash = hashlib.sha256(open(filepath, "rb").read()).hexdigest() |
144 | 93 | else:
|
145 | 94 | # find SHA256 hash
|
146 | 95 | hash = next(h for h in hashes if len(h) == 64)
|
147 | 96 |
|
148 | 97 | self.report(self.check_response(self.m_api.get_file_report(hash)))
|
149 | 98 |
|
150 |
| - elif self.data_type == 'hash': |
151 |
| - data = self.get_param('data', None, 'Data is missing') |
| 99 | + elif self.data_type == "hash": |
| 100 | + data = self.get_param("data", None, "Data is missing") |
152 | 101 | self.report(self.check_response(self.m_api.get_file_report(data)))
|
153 | 102 | else:
|
154 |
| - self.error('Invalid data type') |
| 103 | + self.error("Invalid data type") |
155 | 104 | else:
|
156 |
| - self.error('Invalid service') |
| 105 | + self.error("Invalid service") |
157 | 106 |
|
158 | 107 |
|
159 |
| -if __name__ == '__main__': |
| 108 | +if __name__ == "__main__": |
160 | 109 | MalwaresAnalyzer().run()
|
0 commit comments