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.
- misp_modules/__init__.py +379 -0
- misp_modules/helpers/__init__.py +1 -0
- misp_modules/helpers/cache.py +84 -0
- misp_modules/lib/__init__.py +4 -0
- misp_modules/lib/_vmray/__init__.py +0 -0
- misp_modules/lib/_vmray/parser.py +1417 -0
- misp_modules/lib/_vmray/rest_api.py +148 -0
- misp_modules/lib/cof2misp/LICENSE-2.0.txt +202 -0
- misp_modules/lib/cof2misp/__init__.py +0 -0
- misp_modules/lib/cof2misp/cof.py +165 -0
- misp_modules/lib/joe_mapping.py +114 -0
- misp_modules/lib/joe_parser.py +573 -0
- misp_modules/lib/lastline_api.py +841 -0
- misp_modules/lib/qintel_helper.py +263 -0
- misp_modules/lib/stix2misp.py +2080 -0
- misp_modules/lib/stix2misp_mapping.py +460 -0
- misp_modules/lib/synonymsToTagNames.json +1 -0
- misp_modules/lib/vt_graph_parser/__init__.py +8 -0
- misp_modules/lib/vt_graph_parser/errors.py +20 -0
- misp_modules/lib/vt_graph_parser/helpers/__init__.py +7 -0
- misp_modules/lib/vt_graph_parser/helpers/parsers.py +88 -0
- misp_modules/lib/vt_graph_parser/helpers/rules.py +304 -0
- misp_modules/lib/vt_graph_parser/helpers/wrappers.py +58 -0
- misp_modules/lib/vt_graph_parser/importers/__init__.py +7 -0
- misp_modules/lib/vt_graph_parser/importers/base.py +98 -0
- misp_modules/lib/vt_graph_parser/importers/pymisp_response.py +73 -0
- misp_modules/modules/__init__.py +4 -0
- misp_modules/modules/action_mod/__init__.py +1 -0
- misp_modules/modules/action_mod/_utils/__init__.py +1 -0
- misp_modules/modules/action_mod/_utils/utils.py +70 -0
- misp_modules/modules/action_mod/mattermost.py +113 -0
- misp_modules/modules/action_mod/slack.py +96 -0
- misp_modules/modules/action_mod/testaction.py +68 -0
- misp_modules/modules/expansion/__init__.py +36 -0
- misp_modules/modules/expansion/_dnsdb_query/COPYRIGHT +27 -0
- misp_modules/modules/expansion/_dnsdb_query/LICENSE +202 -0
- misp_modules/modules/expansion/_dnsdb_query/README.md +162 -0
- misp_modules/modules/expansion/_dnsdb_query/__init__.py +0 -0
- misp_modules/modules/expansion/_dnsdb_query/dnsdb_query.py +327 -0
- misp_modules/modules/expansion/_ransomcoindb/__init__.py +0 -0
- misp_modules/modules/expansion/_ransomcoindb/ransomcoindb.py +96 -0
- misp_modules/modules/expansion/_vulnerability_parser/__init__.py +0 -0
- misp_modules/modules/expansion/_vulnerability_parser/vulnerability_parser.py +118 -0
- misp_modules/modules/expansion/abuseipdb.py +146 -0
- misp_modules/modules/expansion/apiosintds.py +365 -0
- misp_modules/modules/expansion/apivoid.py +133 -0
- misp_modules/modules/expansion/assemblyline_query.py +179 -0
- misp_modules/modules/expansion/assemblyline_submit.py +100 -0
- misp_modules/modules/expansion/backscatter_io.py +84 -0
- misp_modules/modules/expansion/btc_scam_check.py +54 -0
- misp_modules/modules/expansion/btc_steroids.py +238 -0
- misp_modules/modules/expansion/censys_enrich.py +287 -0
- misp_modules/modules/expansion/circl_passivedns.py +85 -0
- misp_modules/modules/expansion/circl_passivessl.py +113 -0
- misp_modules/modules/expansion/clamav.py +129 -0
- misp_modules/modules/expansion/cluster25_expand.py +239 -0
- misp_modules/modules/expansion/countrycode.py +69 -0
- misp_modules/modules/expansion/cpe.py +140 -0
- misp_modules/modules/expansion/crowdsec.py +235 -0
- misp_modules/modules/expansion/crowdstrike_falcon.py +148 -0
- misp_modules/modules/expansion/cuckoo_submit.py +161 -0
- misp_modules/modules/expansion/cve.py +57 -0
- misp_modules/modules/expansion/cve_advanced.py +185 -0
- misp_modules/modules/expansion/cytomic_orion.py +196 -0
- misp_modules/modules/expansion/dbl_spamhaus.py +78 -0
- misp_modules/modules/expansion/dns.py +71 -0
- misp_modules/modules/expansion/docx_enrich.py +71 -0
- misp_modules/modules/expansion/domaintools.py +290 -0
- misp_modules/modules/expansion/eql.py +91 -0
- misp_modules/modules/expansion/eupi.py +86 -0
- misp_modules/modules/expansion/extract_url_components.py +83 -0
- misp_modules/modules/expansion/farsight_passivedns.py +243 -0
- misp_modules/modules/expansion/geoip_asn.py +75 -0
- misp_modules/modules/expansion/geoip_city.py +75 -0
- misp_modules/modules/expansion/geoip_country.py +73 -0
- misp_modules/modules/expansion/google_safe_browsing.py +86 -0
- misp_modules/modules/expansion/google_search.py +57 -0
- misp_modules/modules/expansion/google_threat_intelligence.py +453 -0
- misp_modules/modules/expansion/greynoise.py +346 -0
- misp_modules/modules/expansion/hashdd.py +53 -0
- misp_modules/modules/expansion/hashlookup.py +118 -0
- misp_modules/modules/expansion/hibp.py +62 -0
- misp_modules/modules/expansion/html_to_markdown.py +63 -0
- misp_modules/modules/expansion/hyasinsight.py +881 -0
- misp_modules/modules/expansion/intel471.py +73 -0
- misp_modules/modules/expansion/intelmq_eventdb.py.experimental +67 -0
- misp_modules/modules/expansion/ip2locationio.py +90 -0
- misp_modules/modules/expansion/ipasn.py +70 -0
- misp_modules/modules/expansion/ipinfo.py +112 -0
- misp_modules/modules/expansion/ipqs_fraud_and_risk_scoring.py +633 -0
- misp_modules/modules/expansion/iprep.py +98 -0
- misp_modules/modules/expansion/jinja_template_rendering.py +54 -0
- misp_modules/modules/expansion/joesandbox_query.py +91 -0
- misp_modules/modules/expansion/joesandbox_submit.py +147 -0
- misp_modules/modules/expansion/lastline_query.py +148 -0
- misp_modules/modules/expansion/lastline_submit.py +180 -0
- misp_modules/modules/expansion/macaddress_io.py +130 -0
- misp_modules/modules/expansion/macvendors.py +55 -0
- misp_modules/modules/expansion/malshare_upload.py +107 -0
- misp_modules/modules/expansion/malwarebazaar.py +67 -0
- misp_modules/modules/expansion/mcafee_insights_enrich.py +249 -0
- misp_modules/modules/expansion/mmdb_lookup.py +138 -0
- misp_modules/modules/expansion/module.py.skeleton +44 -0
- misp_modules/modules/expansion/mwdb.py +152 -0
- misp_modules/modules/expansion/ocr_enrich.py +74 -0
- misp_modules/modules/expansion/ods_enrich.py +71 -0
- misp_modules/modules/expansion/odt_enrich.py +61 -0
- misp_modules/modules/expansion/onyphe.py +243 -0
- misp_modules/modules/expansion/onyphe_full.py +387 -0
- misp_modules/modules/expansion/otx.py +167 -0
- misp_modules/modules/expansion/passive_ssh.py +150 -0
- misp_modules/modules/expansion/passivetotal.py +358 -0
- misp_modules/modules/expansion/pdf_enrich.py +58 -0
- misp_modules/modules/expansion/pptx_enrich.py +65 -0
- misp_modules/modules/expansion/qintel_qsentry.py +229 -0
- misp_modules/modules/expansion/qrcode.py +99 -0
- misp_modules/modules/expansion/ransomcoindb.py +82 -0
- misp_modules/modules/expansion/rbl.py +126 -0
- misp_modules/modules/expansion/recordedfuture.py +544 -0
- misp_modules/modules/expansion/reversedns.py +77 -0
- misp_modules/modules/expansion/securitytrails.py +571 -0
- misp_modules/modules/expansion/shodan.py +244 -0
- misp_modules/modules/expansion/sigma_queries.py +61 -0
- misp_modules/modules/expansion/sigma_syntax_validator.py +49 -0
- misp_modules/modules/expansion/sigmf_expand.py +303 -0
- misp_modules/modules/expansion/socialscan.py +108 -0
- misp_modules/modules/expansion/sophoslabs_intelix.py +146 -0
- misp_modules/modules/expansion/sourcecache.py +57 -0
- misp_modules/modules/expansion/stairwell.py +156 -0
- misp_modules/modules/expansion/stix2_pattern_syntax_validator.py +56 -0
- misp_modules/modules/expansion/threatcrowd.py +170 -0
- misp_modules/modules/expansion/threatfox.py +75 -0
- misp_modules/modules/expansion/threatminer.py +152 -0
- misp_modules/modules/expansion/triage_submit.py +119 -0
- misp_modules/modules/expansion/trustar_enrich.py +235 -0
- misp_modules/modules/expansion/urlhaus.py +164 -0
- misp_modules/modules/expansion/urlscan.py +269 -0
- misp_modules/modules/expansion/variotdbs.py +133 -0
- misp_modules/modules/expansion/virustotal.py +311 -0
- misp_modules/modules/expansion/virustotal_public.py +272 -0
- misp_modules/modules/expansion/virustotal_upload.py +91 -0
- misp_modules/modules/expansion/vmray_submit.py +161 -0
- misp_modules/modules/expansion/vmware_nsx.py +628 -0
- misp_modules/modules/expansion/vulndb.py +292 -0
- misp_modules/modules/expansion/vulnerability_lookup.py +324 -0
- misp_modules/modules/expansion/vulners.py +81 -0
- misp_modules/modules/expansion/vysion.py +221 -0
- misp_modules/modules/expansion/whois.py +72 -0
- misp_modules/modules/expansion/whoisfreaks.py +232 -0
- misp_modules/modules/expansion/wiki.py +58 -0
- misp_modules/modules/expansion/xforceexchange.py +189 -0
- misp_modules/modules/expansion/xlsx_enrich.py +63 -0
- misp_modules/modules/expansion/yara_query.py +70 -0
- misp_modules/modules/expansion/yara_syntax_validator.py +49 -0
- misp_modules/modules/expansion/yeti.py +196 -0
- misp_modules/modules/export_mod/__init__.py +3 -0
- misp_modules/modules/export_mod/cef_export.py +93 -0
- misp_modules/modules/export_mod/cisco_firesight_manager_ACL_rule_export.py +150 -0
- misp_modules/modules/export_mod/defender_endpoint_export.py +141 -0
- misp_modules/modules/export_mod/goamlexport.py +255 -0
- misp_modules/modules/export_mod/liteexport.py +97 -0
- misp_modules/modules/export_mod/mass_eql_export.py +106 -0
- misp_modules/modules/export_mod/nexthinkexport.py +131 -0
- misp_modules/modules/export_mod/osqueryexport.py +125 -0
- misp_modules/modules/export_mod/pdfexport.py +107 -0
- misp_modules/modules/export_mod/testexport.py +73 -0
- misp_modules/modules/export_mod/threatStream_misp_export.py +114 -0
- misp_modules/modules/export_mod/threat_connect_export.py +125 -0
- misp_modules/modules/export_mod/virustotal_collections.py +141 -0
- misp_modules/modules/export_mod/vt_graph.py +120 -0
- misp_modules/modules/export_mod/yara_export.py +290 -0
- misp_modules/modules/import_mod/__init__.py +22 -0
- misp_modules/modules/import_mod/cof2misp.py +264 -0
- misp_modules/modules/import_mod/csvimport.py +319 -0
- misp_modules/modules/import_mod/cuckooimport.py +749 -0
- misp_modules/modules/import_mod/email_import.py +294 -0
- misp_modules/modules/import_mod/goamlimport.py +188 -0
- misp_modules/modules/import_mod/import_blueprint.py +93 -0
- misp_modules/modules/import_mod/joe_import.py +74 -0
- misp_modules/modules/import_mod/lastline_import.py +160 -0
- misp_modules/modules/import_mod/mispjson.py +73 -0
- misp_modules/modules/import_mod/ocr.py +122 -0
- misp_modules/modules/import_mod/openiocimport.py +99 -0
- misp_modules/modules/import_mod/taxii21.py +383 -0
- misp_modules/modules/import_mod/testimport.py +73 -0
- misp_modules/modules/import_mod/threatanalyzer_import.py +552 -0
- misp_modules/modules/import_mod/url_import.py +97 -0
- misp_modules/modules/import_mod/vmray_import.py +96 -0
- misp_modules/modules/import_mod/vmray_summary_json_import.py +87 -0
- misp_modules-2.4.196.dist-info/LICENSE +661 -0
- misp_modules-2.4.196.dist-info/METADATA +281 -0
- misp_modules-2.4.196.dist-info/RECORD +194 -0
- misp_modules-2.4.196.dist-info/WHEEL +4 -0
- 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
|