misp-modules 2.4.196__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. misp_modules/__init__.py +379 -0
  2. misp_modules/helpers/__init__.py +1 -0
  3. misp_modules/helpers/cache.py +84 -0
  4. misp_modules/lib/__init__.py +4 -0
  5. misp_modules/lib/_vmray/__init__.py +0 -0
  6. misp_modules/lib/_vmray/parser.py +1417 -0
  7. misp_modules/lib/_vmray/rest_api.py +148 -0
  8. misp_modules/lib/cof2misp/LICENSE-2.0.txt +202 -0
  9. misp_modules/lib/cof2misp/__init__.py +0 -0
  10. misp_modules/lib/cof2misp/cof.py +165 -0
  11. misp_modules/lib/joe_mapping.py +114 -0
  12. misp_modules/lib/joe_parser.py +573 -0
  13. misp_modules/lib/lastline_api.py +841 -0
  14. misp_modules/lib/qintel_helper.py +263 -0
  15. misp_modules/lib/stix2misp.py +2080 -0
  16. misp_modules/lib/stix2misp_mapping.py +460 -0
  17. misp_modules/lib/synonymsToTagNames.json +1 -0
  18. misp_modules/lib/vt_graph_parser/__init__.py +8 -0
  19. misp_modules/lib/vt_graph_parser/errors.py +20 -0
  20. misp_modules/lib/vt_graph_parser/helpers/__init__.py +7 -0
  21. misp_modules/lib/vt_graph_parser/helpers/parsers.py +88 -0
  22. misp_modules/lib/vt_graph_parser/helpers/rules.py +304 -0
  23. misp_modules/lib/vt_graph_parser/helpers/wrappers.py +58 -0
  24. misp_modules/lib/vt_graph_parser/importers/__init__.py +7 -0
  25. misp_modules/lib/vt_graph_parser/importers/base.py +98 -0
  26. misp_modules/lib/vt_graph_parser/importers/pymisp_response.py +73 -0
  27. misp_modules/modules/__init__.py +4 -0
  28. misp_modules/modules/action_mod/__init__.py +1 -0
  29. misp_modules/modules/action_mod/_utils/__init__.py +1 -0
  30. misp_modules/modules/action_mod/_utils/utils.py +70 -0
  31. misp_modules/modules/action_mod/mattermost.py +113 -0
  32. misp_modules/modules/action_mod/slack.py +96 -0
  33. misp_modules/modules/action_mod/testaction.py +68 -0
  34. misp_modules/modules/expansion/__init__.py +36 -0
  35. misp_modules/modules/expansion/_dnsdb_query/COPYRIGHT +27 -0
  36. misp_modules/modules/expansion/_dnsdb_query/LICENSE +202 -0
  37. misp_modules/modules/expansion/_dnsdb_query/README.md +162 -0
  38. misp_modules/modules/expansion/_dnsdb_query/__init__.py +0 -0
  39. misp_modules/modules/expansion/_dnsdb_query/dnsdb_query.py +327 -0
  40. misp_modules/modules/expansion/_ransomcoindb/__init__.py +0 -0
  41. misp_modules/modules/expansion/_ransomcoindb/ransomcoindb.py +96 -0
  42. misp_modules/modules/expansion/_vulnerability_parser/__init__.py +0 -0
  43. misp_modules/modules/expansion/_vulnerability_parser/vulnerability_parser.py +118 -0
  44. misp_modules/modules/expansion/abuseipdb.py +146 -0
  45. misp_modules/modules/expansion/apiosintds.py +365 -0
  46. misp_modules/modules/expansion/apivoid.py +133 -0
  47. misp_modules/modules/expansion/assemblyline_query.py +179 -0
  48. misp_modules/modules/expansion/assemblyline_submit.py +100 -0
  49. misp_modules/modules/expansion/backscatter_io.py +84 -0
  50. misp_modules/modules/expansion/btc_scam_check.py +54 -0
  51. misp_modules/modules/expansion/btc_steroids.py +238 -0
  52. misp_modules/modules/expansion/censys_enrich.py +287 -0
  53. misp_modules/modules/expansion/circl_passivedns.py +85 -0
  54. misp_modules/modules/expansion/circl_passivessl.py +113 -0
  55. misp_modules/modules/expansion/clamav.py +129 -0
  56. misp_modules/modules/expansion/cluster25_expand.py +239 -0
  57. misp_modules/modules/expansion/countrycode.py +69 -0
  58. misp_modules/modules/expansion/cpe.py +140 -0
  59. misp_modules/modules/expansion/crowdsec.py +235 -0
  60. misp_modules/modules/expansion/crowdstrike_falcon.py +148 -0
  61. misp_modules/modules/expansion/cuckoo_submit.py +161 -0
  62. misp_modules/modules/expansion/cve.py +57 -0
  63. misp_modules/modules/expansion/cve_advanced.py +185 -0
  64. misp_modules/modules/expansion/cytomic_orion.py +196 -0
  65. misp_modules/modules/expansion/dbl_spamhaus.py +78 -0
  66. misp_modules/modules/expansion/dns.py +71 -0
  67. misp_modules/modules/expansion/docx_enrich.py +71 -0
  68. misp_modules/modules/expansion/domaintools.py +290 -0
  69. misp_modules/modules/expansion/eql.py +91 -0
  70. misp_modules/modules/expansion/eupi.py +86 -0
  71. misp_modules/modules/expansion/extract_url_components.py +83 -0
  72. misp_modules/modules/expansion/farsight_passivedns.py +243 -0
  73. misp_modules/modules/expansion/geoip_asn.py +75 -0
  74. misp_modules/modules/expansion/geoip_city.py +75 -0
  75. misp_modules/modules/expansion/geoip_country.py +73 -0
  76. misp_modules/modules/expansion/google_safe_browsing.py +86 -0
  77. misp_modules/modules/expansion/google_search.py +57 -0
  78. misp_modules/modules/expansion/google_threat_intelligence.py +453 -0
  79. misp_modules/modules/expansion/greynoise.py +346 -0
  80. misp_modules/modules/expansion/hashdd.py +53 -0
  81. misp_modules/modules/expansion/hashlookup.py +118 -0
  82. misp_modules/modules/expansion/hibp.py +62 -0
  83. misp_modules/modules/expansion/html_to_markdown.py +63 -0
  84. misp_modules/modules/expansion/hyasinsight.py +881 -0
  85. misp_modules/modules/expansion/intel471.py +73 -0
  86. misp_modules/modules/expansion/intelmq_eventdb.py.experimental +67 -0
  87. misp_modules/modules/expansion/ip2locationio.py +90 -0
  88. misp_modules/modules/expansion/ipasn.py +70 -0
  89. misp_modules/modules/expansion/ipinfo.py +112 -0
  90. misp_modules/modules/expansion/ipqs_fraud_and_risk_scoring.py +633 -0
  91. misp_modules/modules/expansion/iprep.py +98 -0
  92. misp_modules/modules/expansion/jinja_template_rendering.py +54 -0
  93. misp_modules/modules/expansion/joesandbox_query.py +91 -0
  94. misp_modules/modules/expansion/joesandbox_submit.py +147 -0
  95. misp_modules/modules/expansion/lastline_query.py +148 -0
  96. misp_modules/modules/expansion/lastline_submit.py +180 -0
  97. misp_modules/modules/expansion/macaddress_io.py +130 -0
  98. misp_modules/modules/expansion/macvendors.py +55 -0
  99. misp_modules/modules/expansion/malshare_upload.py +107 -0
  100. misp_modules/modules/expansion/malwarebazaar.py +67 -0
  101. misp_modules/modules/expansion/mcafee_insights_enrich.py +249 -0
  102. misp_modules/modules/expansion/mmdb_lookup.py +138 -0
  103. misp_modules/modules/expansion/module.py.skeleton +44 -0
  104. misp_modules/modules/expansion/mwdb.py +152 -0
  105. misp_modules/modules/expansion/ocr_enrich.py +74 -0
  106. misp_modules/modules/expansion/ods_enrich.py +71 -0
  107. misp_modules/modules/expansion/odt_enrich.py +61 -0
  108. misp_modules/modules/expansion/onyphe.py +243 -0
  109. misp_modules/modules/expansion/onyphe_full.py +387 -0
  110. misp_modules/modules/expansion/otx.py +167 -0
  111. misp_modules/modules/expansion/passive_ssh.py +150 -0
  112. misp_modules/modules/expansion/passivetotal.py +358 -0
  113. misp_modules/modules/expansion/pdf_enrich.py +58 -0
  114. misp_modules/modules/expansion/pptx_enrich.py +65 -0
  115. misp_modules/modules/expansion/qintel_qsentry.py +229 -0
  116. misp_modules/modules/expansion/qrcode.py +99 -0
  117. misp_modules/modules/expansion/ransomcoindb.py +82 -0
  118. misp_modules/modules/expansion/rbl.py +126 -0
  119. misp_modules/modules/expansion/recordedfuture.py +544 -0
  120. misp_modules/modules/expansion/reversedns.py +77 -0
  121. misp_modules/modules/expansion/securitytrails.py +571 -0
  122. misp_modules/modules/expansion/shodan.py +244 -0
  123. misp_modules/modules/expansion/sigma_queries.py +61 -0
  124. misp_modules/modules/expansion/sigma_syntax_validator.py +49 -0
  125. misp_modules/modules/expansion/sigmf_expand.py +303 -0
  126. misp_modules/modules/expansion/socialscan.py +108 -0
  127. misp_modules/modules/expansion/sophoslabs_intelix.py +146 -0
  128. misp_modules/modules/expansion/sourcecache.py +57 -0
  129. misp_modules/modules/expansion/stairwell.py +156 -0
  130. misp_modules/modules/expansion/stix2_pattern_syntax_validator.py +56 -0
  131. misp_modules/modules/expansion/threatcrowd.py +170 -0
  132. misp_modules/modules/expansion/threatfox.py +75 -0
  133. misp_modules/modules/expansion/threatminer.py +152 -0
  134. misp_modules/modules/expansion/triage_submit.py +119 -0
  135. misp_modules/modules/expansion/trustar_enrich.py +235 -0
  136. misp_modules/modules/expansion/urlhaus.py +164 -0
  137. misp_modules/modules/expansion/urlscan.py +269 -0
  138. misp_modules/modules/expansion/variotdbs.py +133 -0
  139. misp_modules/modules/expansion/virustotal.py +311 -0
  140. misp_modules/modules/expansion/virustotal_public.py +272 -0
  141. misp_modules/modules/expansion/virustotal_upload.py +91 -0
  142. misp_modules/modules/expansion/vmray_submit.py +161 -0
  143. misp_modules/modules/expansion/vmware_nsx.py +628 -0
  144. misp_modules/modules/expansion/vulndb.py +292 -0
  145. misp_modules/modules/expansion/vulnerability_lookup.py +324 -0
  146. misp_modules/modules/expansion/vulners.py +81 -0
  147. misp_modules/modules/expansion/vysion.py +221 -0
  148. misp_modules/modules/expansion/whois.py +72 -0
  149. misp_modules/modules/expansion/whoisfreaks.py +232 -0
  150. misp_modules/modules/expansion/wiki.py +58 -0
  151. misp_modules/modules/expansion/xforceexchange.py +189 -0
  152. misp_modules/modules/expansion/xlsx_enrich.py +63 -0
  153. misp_modules/modules/expansion/yara_query.py +70 -0
  154. misp_modules/modules/expansion/yara_syntax_validator.py +49 -0
  155. misp_modules/modules/expansion/yeti.py +196 -0
  156. misp_modules/modules/export_mod/__init__.py +3 -0
  157. misp_modules/modules/export_mod/cef_export.py +93 -0
  158. misp_modules/modules/export_mod/cisco_firesight_manager_ACL_rule_export.py +150 -0
  159. misp_modules/modules/export_mod/defender_endpoint_export.py +141 -0
  160. misp_modules/modules/export_mod/goamlexport.py +255 -0
  161. misp_modules/modules/export_mod/liteexport.py +97 -0
  162. misp_modules/modules/export_mod/mass_eql_export.py +106 -0
  163. misp_modules/modules/export_mod/nexthinkexport.py +131 -0
  164. misp_modules/modules/export_mod/osqueryexport.py +125 -0
  165. misp_modules/modules/export_mod/pdfexport.py +107 -0
  166. misp_modules/modules/export_mod/testexport.py +73 -0
  167. misp_modules/modules/export_mod/threatStream_misp_export.py +114 -0
  168. misp_modules/modules/export_mod/threat_connect_export.py +125 -0
  169. misp_modules/modules/export_mod/virustotal_collections.py +141 -0
  170. misp_modules/modules/export_mod/vt_graph.py +120 -0
  171. misp_modules/modules/export_mod/yara_export.py +290 -0
  172. misp_modules/modules/import_mod/__init__.py +22 -0
  173. misp_modules/modules/import_mod/cof2misp.py +264 -0
  174. misp_modules/modules/import_mod/csvimport.py +319 -0
  175. misp_modules/modules/import_mod/cuckooimport.py +749 -0
  176. misp_modules/modules/import_mod/email_import.py +294 -0
  177. misp_modules/modules/import_mod/goamlimport.py +188 -0
  178. misp_modules/modules/import_mod/import_blueprint.py +93 -0
  179. misp_modules/modules/import_mod/joe_import.py +74 -0
  180. misp_modules/modules/import_mod/lastline_import.py +160 -0
  181. misp_modules/modules/import_mod/mispjson.py +73 -0
  182. misp_modules/modules/import_mod/ocr.py +122 -0
  183. misp_modules/modules/import_mod/openiocimport.py +99 -0
  184. misp_modules/modules/import_mod/taxii21.py +383 -0
  185. misp_modules/modules/import_mod/testimport.py +73 -0
  186. misp_modules/modules/import_mod/threatanalyzer_import.py +552 -0
  187. misp_modules/modules/import_mod/url_import.py +97 -0
  188. misp_modules/modules/import_mod/vmray_import.py +96 -0
  189. misp_modules/modules/import_mod/vmray_summary_json_import.py +87 -0
  190. misp_modules-2.4.196.dist-info/LICENSE +661 -0
  191. misp_modules-2.4.196.dist-info/METADATA +281 -0
  192. misp_modules-2.4.196.dist-info/RECORD +194 -0
  193. misp_modules-2.4.196.dist-info/WHEEL +4 -0
  194. misp_modules-2.4.196.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,749 @@
1
+ import json
2
+ import base64
3
+ import io
4
+ import logging
5
+ import posixpath
6
+ import stat
7
+ import tarfile
8
+ import zipfile
9
+ from pymisp import MISPEvent, MISPObject, MISPAttribute
10
+ from pymisp.tools import make_binary_objects
11
+ from collections import OrderedDict
12
+
13
+ log = logging.getLogger(__name__)
14
+
15
+ misperrors = {'error': 'Error'}
16
+
17
+ moduleinfo = {
18
+ 'version': '1.1',
19
+ 'author': 'Pierre-Jean Grenier',
20
+ 'module-type': ['import'],
21
+ 'name': 'Cuckoo Sandbox Import',
22
+ 'description': 'Module to import Cuckoo JSON.',
23
+ 'logo': 'cuckoo.png',
24
+ 'requirements': [],
25
+ 'features': 'Import a Cuckoo archive (zipfile or bzip2 tarball), either downloaded manually or exported from the API (/tasks/report/<task_id>/all).',
26
+ 'references': ['https://cuckoosandbox.org/', 'https://github.com/cuckoosandbox/cuckoo'],
27
+ 'input': 'Cuckoo JSON file',
28
+ 'output': 'MISP Event attributes',
29
+ }
30
+
31
+ moduleconfig = []
32
+
33
+ mispattributes = {
34
+ 'inputSource': ['file'],
35
+ 'output': ['MISP objects', 'malware-sample'],
36
+ 'format': 'misp_standard',
37
+ }
38
+
39
+ # Attributes for which we can set the "Artifacts dropped"
40
+ # category if we want to
41
+ ARTIFACTS_DROPPED = (
42
+ "filename",
43
+ "md5",
44
+ "sha1",
45
+ "sha256",
46
+ "sha512",
47
+ "malware-sample",
48
+ "mimetype",
49
+ "ssdeep",
50
+ )
51
+
52
+ # Same for the category "Payload delivery"
53
+ PAYLOAD_DELIVERY = ARTIFACTS_DROPPED
54
+
55
+
56
+ class PrettyDict(OrderedDict):
57
+ """
58
+ This class is just intended for a pretty print
59
+ of its keys and values.
60
+ """
61
+ MAX_SIZE = 30
62
+
63
+ def __str__(self):
64
+ tmp = []
65
+ for k, v in self.items():
66
+ v = str(v)
67
+ if len(v) > self.MAX_SIZE:
68
+ k += ',cut'
69
+ v = v[:self.MAX_SIZE]
70
+ v.replace('\n', ' ')
71
+ tmp.append((k, v))
72
+ return "; ".join(f"({k}) {v}" for k, v in tmp)
73
+
74
+
75
+ def search_objects(event, name, attributes=[]):
76
+ """
77
+ Search for objects in event, which name is `name` and
78
+ contain at least the attributes given.
79
+ Return a generator.
80
+ @ param attributes: a list of (object_relation, value)
81
+ """
82
+ match = filter(
83
+ lambda obj: all(
84
+ obj.name == name
85
+ and (obj_relation, str(attr_value)) in map(
86
+ lambda attr: (attr.object_relation, str(attr.value)),
87
+ obj.attributes
88
+ )
89
+ for obj_relation, attr_value in attributes
90
+ ), event.objects
91
+ )
92
+ return match
93
+
94
+
95
+ def find_process_by_pid(event, pid):
96
+ """
97
+ Find a 'process' MISPObject by its PID. If multiple objects are found,
98
+ only return the first one.
99
+ @ param pid: integer or str
100
+ """
101
+ generator = search_objects(
102
+ event,
103
+ "process",
104
+ (('pid', pid),)
105
+ )
106
+ return next(generator, None)
107
+
108
+
109
+ class CuckooParser():
110
+ # This dict is used to generate the userConfig and link the different
111
+ # options to the corresponding method of the parser. This way, we avoid
112
+ # redundancy and make future changes easier (instead of for instance
113
+ # defining all the options in userConfig directly, and then making a
114
+ # switch when running the parser).
115
+ # Careful about the order here, as we create references between
116
+ # MISPObjects/MISPAttributes at the same time we generate them.
117
+ # Hence when we create object B, which we want to reference to
118
+ # object A, we should already have created object A.
119
+ # TODO create references only after all parsing is done
120
+ options = {
121
+ "Sandbox info": {
122
+ "method": lambda self: self.add_sandbox_info(),
123
+ "userConfig": {
124
+ 'type': 'Boolean',
125
+ 'message': "Add info related to the sandbox",
126
+ 'checked': 'true',
127
+ },
128
+ },
129
+ "Upload sample": {
130
+ "method": lambda self: self.add_sample(),
131
+ "userConfig": {
132
+ 'type': 'Boolean',
133
+ 'message': "Upload the sample",
134
+ 'checked': 'true',
135
+ },
136
+ },
137
+ "Processes": {
138
+ "method": lambda self: self.add_process_tree(),
139
+ "userConfig": {
140
+ 'type': 'Boolean',
141
+ 'message': "Add info related to the processes",
142
+ 'checked': 'true',
143
+ },
144
+ },
145
+ "DNS": {
146
+ "method": lambda self: self.add_dns(),
147
+ "userConfig": {
148
+ 'type': 'Boolean',
149
+ 'message': "Add DNS queries/answers",
150
+ 'checked': 'true',
151
+ },
152
+ },
153
+ "TCP": {
154
+ "method": lambda self: self.add_network("tcp"),
155
+ "userConfig": {
156
+ 'type': 'Boolean',
157
+ 'message': "Add TCP connections",
158
+ 'checked': 'true',
159
+ },
160
+ },
161
+ "UDP": {
162
+ "method": lambda self: self.add_network("udp"),
163
+ "userConfig": {
164
+ 'type': 'Boolean',
165
+ 'message': "Add UDP connections",
166
+ 'checked': 'true',
167
+ },
168
+ },
169
+ "HTTP": {
170
+ "method": lambda self: self.add_http(),
171
+ "userConfig": {
172
+ 'type': 'Boolean',
173
+ 'message': "Add HTTP requests",
174
+ 'checked': 'true',
175
+ },
176
+ },
177
+ "Signatures": {
178
+ "method": lambda self: self.add_signatures(),
179
+ "userConfig": {
180
+ 'type': 'Boolean',
181
+ 'message': "Add Cuckoo's triggered signatures",
182
+ 'checked': 'true',
183
+ },
184
+ },
185
+ "Screenshots": {
186
+ "method": lambda self: self.add_screenshots(),
187
+ "userConfig": {
188
+ 'type': 'Boolean',
189
+ 'message': "Upload the screenshots",
190
+ 'checked': 'true',
191
+ },
192
+ },
193
+ "Dropped files": {
194
+ "method": lambda self: self.add_dropped_files(),
195
+ "userConfig": {
196
+ 'type': 'Boolean',
197
+ 'message': "Upload the dropped files",
198
+ 'checked': 'true',
199
+ },
200
+ },
201
+ "Dropped buffers": {
202
+ "method": lambda self: self.add_dropped_buffers(),
203
+ "userConfig": {
204
+ 'type': 'Boolean',
205
+ 'message': "Upload the dropped buffers",
206
+ 'checked': 'true',
207
+ },
208
+ },
209
+ }
210
+
211
+ def __init__(self, config):
212
+ self.event = MISPEvent()
213
+ self.files = None
214
+ self.malware_binary = None
215
+ self.report = None
216
+ self.config = {
217
+ # if an option is missing (we receive None as a value),
218
+ # fall back to the default specified in the options
219
+ key: int(
220
+ on if on is not None
221
+ else self.options[key]["userConfig"]["checked"] == 'true'
222
+ )
223
+ for key, on in config.items()
224
+ }
225
+
226
+ def get_file(self, relative_filepath):
227
+ """Return an io.BufferedIOBase for the corresponding relative_filepath
228
+ in the Cuckoo archive. If not found, return an empty io.BufferedReader
229
+ to avoid fatal errors."""
230
+ blackhole = io.BufferedReader(open('/dev/null', 'rb'))
231
+ res = self.files.get(relative_filepath, blackhole)
232
+ if res == blackhole:
233
+ log.debug(f"Did not find file {relative_filepath}, "
234
+ f"returned an empty file instead")
235
+ return res
236
+
237
+ def read_archive(self, archive_encoded):
238
+ """Read the archive exported from Cuckoo and initialize the class"""
239
+ # archive_encoded is base 64 encoded content
240
+ # we extract the info about each file but do not retrieve
241
+ # it automatically, as it may take too much space in memory
242
+ buf_io = io.BytesIO(base64.b64decode(archive_encoded))
243
+ if zipfile.is_zipfile(buf_io):
244
+ # the archive was probably downloaded from the WebUI
245
+ buf_io.seek(0) # don't forget this not to read an empty buffer
246
+ z = zipfile.ZipFile(buf_io, 'r')
247
+ self.files = {
248
+ info.filename: z.open(info)
249
+ for info in z.filelist
250
+ # only extract the regular files and dirs, we don't
251
+ # want any symbolic link
252
+ if stat.S_ISREG(info.external_attr >> 16)
253
+ or stat.S_ISDIR(info.external_attr >> 16)
254
+ }
255
+ else:
256
+ # the archive was probably downloaded from the API
257
+ buf_io.seek(0) # don't forget this not to read an empty buffer
258
+ f = tarfile.open(fileobj=buf_io, mode='r:bz2')
259
+ self.files = {
260
+ info.name: f.extractfile(info)
261
+ for info in f.getmembers()
262
+ # only extract the regular files and dirs, we don't
263
+ # want any symbolic link
264
+ if info.isreg() or info.isdir()
265
+ }
266
+
267
+ # We want to keep the order of the keys of sub-dicts in the report,
268
+ # eg. the signatures have marks with unknown keys such as
269
+ # {'marks': [
270
+ # {"suspicious_features": "Connection to IP address",
271
+ # "suspicious_request": "OPTIONS http://85.20.18.18/doc"}
272
+ # ]}
273
+ # To render those marks properly, we can only hope the developpers
274
+ # thought about the order in which they put the keys, and keep this
275
+ # order so that the signature makes sense to the reader.
276
+ # We use PrettyDict, a customization of OrderedDict to do so.
277
+ # It will be instanced iteratively when parsing the json (ie. subdicts
278
+ # will also be instanced as PrettyDict)
279
+ self.report = json.load(
280
+ self.get_file("reports/report.json"),
281
+ object_pairs_hook=PrettyDict,
282
+ )
283
+
284
+ def read_malware(self):
285
+ self.malware_binary = self.get_file("binary").read()
286
+ if not self.malware_binary:
287
+ log.warn("No malware binary found")
288
+
289
+ def add_sandbox_info(self):
290
+ info = self.report.get("info", {})
291
+ if not info:
292
+ log.warning("The 'info' field was not found "
293
+ "in the report, skipping")
294
+ return False
295
+
296
+ o = MISPObject(name='sandbox-report')
297
+ o.add_attribute('score', info['score'])
298
+ o.add_attribute('sandbox-type', 'on-premise')
299
+ o.add_attribute('on-premise-sandbox', 'cuckoo')
300
+ o.add_attribute('raw-report',
301
+ f'started on:{info["machine"]["started_on"]} '
302
+ f'duration:{info["duration"]}s '
303
+ f'vm:{info["machine"]["name"]}/'
304
+ f'{info["machine"]["label"]}')
305
+ self.event.add_object(o)
306
+
307
+ def add_sample(self):
308
+ """Add the sample/target of the analysis"""
309
+ target = self.report.get("target", {})
310
+ category = target.get("category", "")
311
+ if not category:
312
+ log.warning("Could not find info about the sample "
313
+ "in the report, skipping")
314
+ return False
315
+
316
+ if category == "file":
317
+ log.debug("Sample is a file, uploading it")
318
+ self.read_malware()
319
+ file_o, bin_type_o, bin_section_li = make_binary_objects(
320
+ pseudofile=io.BytesIO(self.malware_binary),
321
+ filename=target["file"]["name"],
322
+ )
323
+
324
+ file_o.comment = "Submitted sample"
325
+ # fix categories
326
+ for obj in filter(None, (file_o, bin_type_o, *bin_section_li,)):
327
+ for attr in obj.attributes:
328
+ if attr.type in PAYLOAD_DELIVERY:
329
+ attr.category = "Payload delivery"
330
+ self.event.add_object(obj)
331
+
332
+ elif category == "url":
333
+ log.debug("Sample is a URL")
334
+ o = MISPObject(name='url')
335
+ o.add_attribute('url', target['url'])
336
+ o.add_attribute('text', "Submitted URL")
337
+ self.event.add_object(o)
338
+
339
+ def add_http(self):
340
+ """Add the HTTP requests"""
341
+ network = self.report.get("network", [])
342
+ http = network.get("http", [])
343
+ if not http:
344
+ log.info("No HTTP connection found in the report, skipping")
345
+ return False
346
+
347
+ for request in http:
348
+ o = MISPObject(name='http-request')
349
+ o.add_attribute('host', request['host'])
350
+ o.add_attribute('method', request['method'])
351
+ o.add_attribute('uri', request['uri'])
352
+ o.add_attribute('user-agent', request['user-agent'])
353
+ o.add_attribute('text', f"count:{request['count']} "
354
+ f"port:{request['port']}")
355
+ self.event.add_object(o)
356
+
357
+ def add_network(self, proto=None):
358
+ """
359
+ Add UDP/TCP traffic
360
+ proto must be one of "tcp", "udp"
361
+ """
362
+ network = self.report.get("network", [])
363
+ li_conn = network.get(proto, [])
364
+ if not li_conn:
365
+ log.info(f"No {proto} connection found in the report, skipping")
366
+ return False
367
+
368
+ from_to = []
369
+ # sort by time to get the "first packet seen" right
370
+ li_conn.sort(key=lambda x: x["time"])
371
+ for conn in li_conn:
372
+ src = conn['src']
373
+ dst = conn['dst']
374
+ sport = conn['sport']
375
+ dport = conn['dport']
376
+ if (src, sport, dst, dport) in from_to:
377
+ continue
378
+
379
+ from_to.append((src, sport, dst, dport))
380
+
381
+ o = MISPObject(name='network-connection')
382
+ o.add_attribute('ip-src', src)
383
+ o.add_attribute('ip-dst', dst)
384
+ o.add_attribute('src-port', sport)
385
+ o.add_attribute('dst-port', dport)
386
+ o.add_attribute('layer3-protocol', "IP")
387
+ o.add_attribute('layer4-protocol', proto.upper())
388
+ o.add_attribute('first-packet-seen', conn['time'])
389
+ self.event.add_object(o)
390
+
391
+ def add_dns(self):
392
+ """Add DNS records"""
393
+ network = self.report.get("network", [])
394
+ dns = network.get("dns", [])
395
+ if not dns:
396
+ log.info("No DNS connection found in the report, skipping")
397
+ return False
398
+
399
+ for record in dns:
400
+ o = MISPObject(name='dns-record')
401
+ o.add_attribute('text', f"request type:{record['type']}")
402
+ o.add_attribute('queried-domain', record['request'])
403
+ for answer in record.get("answers", []):
404
+ if answer["type"] in ("A", "AAAA"):
405
+ o.add_attribute('a-record', answer['data'])
406
+ # TODO implement MX/NS
407
+
408
+ self.event.add_object(o)
409
+
410
+ def _get_marks_str(self, marks):
411
+ marks_strings = []
412
+ for m in marks:
413
+ m_type = m.pop("type") # temporarily remove the type
414
+
415
+ if m_type == "generic":
416
+ marks_strings.append(str(m))
417
+
418
+ elif m_type == "ioc":
419
+ marks_strings.append(m['ioc'])
420
+
421
+ elif m_type == "call":
422
+ call = m["call"]
423
+ arguments = call.get("arguments", {})
424
+ flags = call.get("flags", {})
425
+ info = ""
426
+ for details in (arguments, flags):
427
+ info += f" {details}"
428
+ marks_strings.append(f"Call API '{call['api']}'%s" % info)
429
+
430
+ else:
431
+ logging.debug(f"Unknown mark type '{m_type}', skipping")
432
+
433
+ m["type"] = m_type # restore key 'type'
434
+ # TODO implemented marks 'config' and 'volatility'
435
+ return marks_strings
436
+
437
+ def _add_ttp(self, attribute, ttp_short, ttp_num):
438
+ """
439
+ Internal wrapper to add the TTP tag from the MITRE galaxy.
440
+ @ params
441
+ - attribute: MISPAttribute
442
+ - ttp_short: short description of the TTP
443
+ (eg. "Credential Dumping")
444
+ - ttp_num: formatted as "T"+int
445
+ (eg. T1003)
446
+ """
447
+ attribute.add_tag(f'misp-galaxy:mitre-attack-pattern='
448
+ f'"{ttp_short} - {ttp_num}"')
449
+
450
+ def add_signatures(self):
451
+ """Add the Cuckoo signatures, with as many details as possible
452
+ regarding the marks"""
453
+ signatures = self.report.get("signatures", [])
454
+ if not signatures:
455
+ log.info("No signature found in the report")
456
+ return False
457
+
458
+ o = MISPObject(name='sb-signature')
459
+ o.add_attribute('software', "Cuckoo")
460
+
461
+ for sign in signatures:
462
+ marks = sign["marks"]
463
+ marks_strings = self._get_marks_str(marks)
464
+ summary = sign['description']
465
+ if marks_strings:
466
+ summary += "\n---\n"
467
+
468
+ marks_strings = set(marks_strings)
469
+ description = summary + "\n".join(marks_strings)
470
+
471
+ a = MISPAttribute()
472
+ a.from_dict(type='text', value=description)
473
+ for ttp_num, desc in sign.get("ttp", {}).items():
474
+ ttp_short = desc["short"]
475
+ self._add_ttp(a, ttp_short, ttp_num)
476
+
477
+ # this signature was triggered by the processes with the following
478
+ # PIDs, we can create references
479
+ triggered_by_pids = filter(
480
+ None,
481
+ (m.get("pid", None) for m in marks)
482
+ )
483
+ # remove redundancy
484
+ triggered_by_pids = set(triggered_by_pids)
485
+ for pid in triggered_by_pids:
486
+ process_o = find_process_by_pid(self.event, pid)
487
+ if process_o:
488
+ process_o.add_reference(a, "triggers")
489
+
490
+ o.add_attribute('signature', **a)
491
+
492
+ self.event.add_object(o)
493
+
494
+ def _handle_process(self, proc, accu):
495
+ """
496
+ This is an internal recursive function to handle one process
497
+ from a process tree and then iterate on its children.
498
+ List the objects to be added, based on the tree, into the `accu` list.
499
+ The `accu` list uses a DFS-like order.
500
+ """
501
+ o = MISPObject(name='process')
502
+ accu.append(o)
503
+ o.add_attribute('pid', proc['pid'])
504
+ o.add_attribute('command-line', proc['command_line'])
505
+ o.add_attribute('name', proc['process_name'])
506
+ o.add_attribute('parent-pid', proc['ppid'])
507
+ for child in proc.get('children', []):
508
+ pos_child = len(accu)
509
+ o.add_attribute('child-pid', child['pid'])
510
+ self._handle_process(child, accu)
511
+ child_obj = accu[pos_child]
512
+ child_obj.add_reference(o, 'child-of')
513
+
514
+ return o
515
+
516
+ def add_process_tree(self):
517
+ """Add process tree from the report, as separated process objects"""
518
+ behavior = self.report.get("behavior", {})
519
+ tree = behavior.get("processtree", [])
520
+ if not tree:
521
+ log.warning("No process tree found in the report, skipping")
522
+ return False
523
+
524
+ for proc in tree:
525
+ objs = []
526
+ self._handle_process(proc, objs)
527
+ for o in objs:
528
+ self.event.add_object(o)
529
+
530
+ def get_relpath(self, path):
531
+ """
532
+ Transform an absolute or relative path into a path relative to the
533
+ correct cuckoo analysis directory, without knowing the cuckoo
534
+ working directory.
535
+ Return an empty string if the path given does not refer to a
536
+ file from the analysis directory.
537
+ """
538
+ head, tail = posixpath.split(path)
539
+ if not tail:
540
+ return ""
541
+ prev = self.get_relpath(head)
542
+ longer = posixpath.join(prev, tail)
543
+ if longer in self.files:
544
+ return longer
545
+ elif tail in self.files:
546
+ return tail
547
+ else:
548
+ return ""
549
+
550
+ def add_screenshots(self):
551
+ """Add the screenshots taken by Cuckoo in a sandbox-report object"""
552
+ screenshots = self.report.get('screenshots', [])
553
+ if not screenshots:
554
+ log.info("No screenshot found in the report, skipping")
555
+ return False
556
+
557
+ o = MISPObject(name='sandbox-report')
558
+ o.add_attribute('sandbox-type', 'on-premise')
559
+ o.add_attribute('on-premise-sandbox', "cuckoo")
560
+ for shot in screenshots:
561
+ # The path given by Cuckoo is an absolute path, but we need a path
562
+ # relative to the analysis folder.
563
+ path = self.get_relpath(shot['path'])
564
+ img = self.get_file(path)
565
+ # .decode('utf-8') in order to avoid the b'' format
566
+ img_data = base64.b64encode(img.read()).decode('utf-8')
567
+ filename = posixpath.basename(path)
568
+
569
+ o.add_attribute(
570
+ "sandbox-file", value=filename,
571
+ data=img_data, type='attachment',
572
+ category="External analysis",
573
+ )
574
+
575
+ self.event.add_object(o)
576
+
577
+ def _get_dropped_objs(self, path, filename=None, comment=None):
578
+ """
579
+ Internal wrapper to get dropped files/buffers as file objects
580
+ @ params
581
+ - path: relative to the cuckoo analysis directory
582
+ - filename: if not specified, deduced from the path
583
+ """
584
+ if not filename:
585
+ filename = posixpath.basename(path)
586
+
587
+ dropped_file = self.get_file(path)
588
+ dropped_binary = io.BytesIO(dropped_file.read())
589
+ # create ad hoc objects
590
+ file_o, bin_type_o, bin_section_li = make_binary_objects(
591
+ pseudofile=dropped_binary, filename=filename,
592
+ )
593
+
594
+ if comment:
595
+ file_o.comment = comment
596
+ # fix categories
597
+ for obj in filter(None, (file_o, bin_type_o, *bin_section_li,)):
598
+ for attr in obj.attributes:
599
+ if attr.type in ARTIFACTS_DROPPED:
600
+ attr.category = "Artifacts dropped"
601
+
602
+ return file_o, bin_type_o, bin_section_li
603
+
604
+ def _add_yara(self, obj, yara_dict):
605
+ """Internal wrapper to add Yara matches to an MISPObject"""
606
+ for yara in yara_dict:
607
+ description = yara.get("meta", {}).get("description", "")
608
+ name = yara.get("name", "")
609
+ obj.add_attribute(
610
+ "text",
611
+ f"Yara match\n(name) {name}\n(description) {description}",
612
+ comment="Yara match"
613
+ )
614
+
615
+ def add_dropped_files(self):
616
+ """Upload the dropped files as file objects"""
617
+ dropped = self.report.get("dropped", [])
618
+ if not dropped:
619
+ log.info("No dropped file found, skipping")
620
+ return False
621
+
622
+ for d in dropped:
623
+ # Cuckoo logs three things that are of interest for us:
624
+ # - 'filename' which is not the original name of the file
625
+ # but is formatted as follow:
626
+ # 8 first bytes of SHA265 + _ + original name in lower case
627
+ # - 'filepath' which is the original filepath on the VM,
628
+ # where the file was dropped
629
+ # - 'path' which is the local path of the stored file,
630
+ # in the cuckoo archive
631
+ filename = d.get("name", "")
632
+ original_path = d.get("filepath", "")
633
+ sha256 = d.get("sha256", "")
634
+ if original_path and sha256:
635
+ log.debug(f"Will now try to restore original filename from "
636
+ f"path {original_path}")
637
+ try:
638
+ s = filename.split("_")
639
+ if not s:
640
+ raise Exception("unexpected filename read "
641
+ "in the report")
642
+ sha256_first_8_bytes = s[0]
643
+ original_name = s[1]
644
+ # check our assumptions are valid, if so we can safely
645
+ # restore the filename, if not the format may have changed
646
+ # so we'll keep the filename of the report
647
+ if sha256.startswith(sha256_first_8_bytes) and \
648
+ original_path.lower().endswith(original_name) and \
649
+ filename not in original_path.lower():
650
+ # we can restore the original case of the filename
651
+ position = original_path.lower().rindex(original_name)
652
+ filename = original_path[position:]
653
+ log.debug(f"Successfully restored original filename: "
654
+ f"{filename}")
655
+ else:
656
+ raise Exception("our assumptions were wrong, "
657
+ "filename format may have changed")
658
+ except Exception as e:
659
+ log.debug(f"Cannot restore filename: {e}")
660
+
661
+ if not filename:
662
+ filename = "NO NAME FOUND IN THE REPORT"
663
+ log.warning(f'No filename found for dropped file! '
664
+ f'Will use "{filename}"')
665
+
666
+ file_o, bin_type_o, bin_section_o = self._get_dropped_objs(
667
+ self.get_relpath(d['path']),
668
+ filename=filename,
669
+ comment="Dropped file"
670
+ )
671
+
672
+ self._add_yara(file_o, d.get("yara", []))
673
+
674
+ file_o.add_attribute("fullpath", original_path,
675
+ category="Artifacts dropped")
676
+
677
+ # why is this a list? for when various programs drop the same file?
678
+ for pid in d.get("pids", []):
679
+ # if we have an object for the process that dropped the file,
680
+ # we can link the two (we just take the first result from
681
+ # the search)
682
+ process_o = find_process_by_pid(self.event, pid)
683
+ if process_o:
684
+ file_o.add_reference(process_o, "dropped-by")
685
+
686
+ self.event.add_object(file_o)
687
+
688
+ def add_dropped_buffers(self):
689
+ """"Upload the dropped buffers as file objects"""
690
+ buffer = self.report.get("buffer", [])
691
+ if not buffer:
692
+ log.info("No dropped buffer found, skipping")
693
+ return False
694
+
695
+ for i, buf in enumerate(buffer):
696
+ file_o, bin_type_o, bin_section_o = self._get_dropped_objs(
697
+ self.get_relpath(buf['path']),
698
+ filename=f"buffer {i}",
699
+ comment="Dropped buffer"
700
+ )
701
+ self._add_yara(file_o, buf.get("yara", []))
702
+ self.event.add_object(file_o)
703
+
704
+ def parse(self):
705
+ """Run the parsing"""
706
+ for name, active in self.config.items():
707
+ if active:
708
+ self.options[name]["method"](self)
709
+
710
+ def get_misp_event(self):
711
+ log.debug("Running MISP expansions")
712
+ self.event.run_expansions()
713
+ return self.event
714
+
715
+
716
+ def handler(q=False):
717
+ # In case there's no data
718
+ if q is False:
719
+ return False
720
+
721
+ q = json.loads(q)
722
+ data = q['data']
723
+
724
+ parser = CuckooParser(q['config'])
725
+ parser.read_archive(data)
726
+ parser.parse()
727
+ event = parser.get_misp_event()
728
+
729
+ event = json.loads(event.to_json())
730
+ results = {
731
+ key: event[key]
732
+ for key in ('Attribute', 'Object')
733
+ if (key in event and event[key])
734
+ }
735
+ return {'results': results}
736
+
737
+
738
+ def introspection():
739
+ userConfig = {
740
+ key: o["userConfig"]
741
+ for key, o in CuckooParser.options.items()
742
+ }
743
+ mispattributes['userConfig'] = userConfig
744
+ return mispattributes
745
+
746
+
747
+ def version():
748
+ moduleinfo['config'] = moduleconfig
749
+ return moduleinfo