1
2
3
4
5
6
7
8 import os
9 import hashlib
10 import re
11 import traceback
12 from collections import defaultdict
13
14 from lib.maec.maec40 import api_call_mappings, hiveHexToString,\
15 socketTypeToString, socketProtoToString, socketAFToString,\
16 regDatatypeToString, intToHex, regStringToKey, regStringToHive
17
18 from lib.cuckoo.common.abstracts import Report
19 from lib.cuckoo.common.exceptions import CuckooDependencyError, CuckooReportError
20 from lib.cuckoo.common.utils import datetime_to_iso
21
22 try:
23 import cybox
24 import cybox.utils.nsparser
25 from cybox.core import Object
26 from cybox.common import ToolInformation
27 from cybox.common import StructuredText
28 from maec.bundle.bundle import Bundle
29 from maec.bundle.malware_action import MalwareAction
30 from maec.bundle.bundle_reference import BundleReference
31 from maec.bundle.process_tree import ProcessTree
32 from maec.bundle.av_classification import AVClassification
33 from maec.id_generator import Generator
34 from maec.package.malware_subject import MalwareSubject
35 from maec.package.package import Package
36 from maec.package.analysis import Analysis
37 from maec.utils import MAECNamespaceParser
38 HAVE_MAEC = True
39 except ImportError:
40 HAVE_MAEC = False
41
43 """Generates a MAEC 4.0.1 report.
44 --Output modes (set in reporting.conf):
45 mode = "full": Output fully mapped Actions (see maec40_mappings), including Windows Handle mapped/substituted objects,
46 along with API call/parameter capture via Action Implementations.
47 mode = "overview": Output only fully mapped Actions, without any Action Implementations. Default mode.
48 mode = "api": Output only Actions with Action Implementations, but no mapped components.
49 --Other configuration parameters:
50 processtree = "true" | "false". Output captured ProcessTree as part of dynamic analysis MAEC Bundle. Default = "true".
51 output_handles = "true" | "false". Output the Windows Handles used to construct the Object-Handle mappings as a
52 separate Object Collection in the dynamic analysis MAEC Bundle. Only applicable
53 for mode = "full" or mode = "overview". Default = "false".
54 static = "true" | "false". Output Cuckoo static analysis (PEfile) output as a separate MAEC Bundle in the document.
55 Default = "true".
56 strings = "true" | "false". Output Cuckoo strings output as a separate MAEC Bundle in the document. Default = "true".
57 virustotal = "true" | "false". Output VirusTotal output as a separate MAEC Bundle in the document. Default = "true".
58 """
59
60 - def run(self, results):
61 """Writes report.
62 @param results: Cuckoo results dict.
63 @raise CuckooReportError: if fails to write report.
64 """
65
66
67 if not HAVE_MAEC:
68 raise CuckooDependencyError("Unable to import cybox and maec (install with `pip install maec`)")
69
70 self._illegal_xml_chars_RE = re.compile(u"[\x00-\x08\x0b\x0c\x0e-\x1F\uD800-\uDFFF\uFFFE\uFFFF]")
71
72 self.pidActionMap = {}
73
74 self.handleMap = {}
75
76 self.results = results
77
78 self.setupMAEC()
79
80 self.addSubjectAttributes()
81 self.addDroppedFiles()
82 self.addAnalyses()
83 self.addActions()
84 self.addProcessTree()
85
86 self.output()
87
89 """Generates MAEC Package, Malware Subject, and Bundle structure"""
90 if self.results["target"]["category"] == "file":
91 self.id_generator = Generator(self.results["target"]["file"]["md5"])
92 elif self.results["target"]["category"] == "url":
93 self.id_generator = Generator(hashlib.md5(self.results["target"]["url"]).hexdigest())
94 else:
95 raise CuckooReportError("Unknown target type")
96
97
98 self.package = Package(self.id_generator.generate_package_id())
99
100 self.subject = MalwareSubject(self.id_generator.generate_malware_subject_id())
101
102 self.package.add_malware_subject(self.subject)
103
104 self.dynamic_bundle = Bundle(self.id_generator.generate_bundle_id(), False, "4.0.1", "dynamic analysis tool output")
105
106 self.subject.add_findings_bundle(self.dynamic_bundle)
107
108 if self.options["static"] and "static" in self.results and self.results["static"]:
109 self.static_bundle = Bundle(self.id_generator.generate_bundle_id(), False, "4.0.1", "static analysis tool output")
110 self.subject.add_findings_bundle(self.static_bundle)
111 if self.options["strings"] and "strings" in self.results and self.results["strings"]:
112 self.strings_bundle = Bundle(self.id_generator.generate_bundle_id(), False, "4.0.1", "static analysis tool output")
113 self.subject.add_findings_bundle(self.strings_bundle)
114 if self.options["virustotal"] and "virustotal" in self.results and self.results["virustotal"]:
115 self.virustotal_bundle = Bundle(self.id_generator.generate_bundle_id(), False, "4.0.1", "static analysis tool output")
116 self.subject.add_findings_bundle(self.virustotal_bundle)
117
119 """Add Actions section."""
120
121 for process in self.results["behavior"]["processes"]:
122 self.createProcessActions(process)
123
124 if "network" in self.results and isinstance(self.results["network"], dict) and len(self.results["network"]) > 0:
125 if "udp" in self.results["network"] and isinstance(self.results["network"]["udp"], list) and len(self.results["network"]["udp"]) > 0:
126 if not self.dynamic_bundle.collections.action_collections.has_collection("Network Actions"):
127 self.dynamic_bundle.add_named_action_collection("Network Actions", self.id_generator.generate_action_collection_id())
128 for network_data in self.results["network"]["udp"]:
129 self.createActionNet(network_data, {"value": "connect to socket address", "xsi:type": "maecVocabs:NetworkActionNameVocab-1.0"}, "UDP")
130 if "dns" in self.results["network"] and isinstance(self.results["network"]["dns"], list) and len(self.results["network"]["dns"]) > 0:
131 if not self.dynamic_bundle.collections.action_collections.has_collection("Network Actions"):
132 self.dynamic_bundle.add_named_action_collection("Network Actions", self.id_generator.generate_action_collection_id())
133 for network_data in self.results["network"]["dns"]:
134 self.createActionNet(network_data, {"value": "send dns query", "xsi:type": "maecVocabs:DNSActionNameVocab-1.0"}, "UDP", "DNS")
135 if "tcp" in self.results["network"] and isinstance(self.results["network"]["tcp"], list) and len(self.results["network"]["tcp"]) > 0:
136 if not self.dynamic_bundle.collections.action_collections.has_collection("Network Actions"):
137 self.dynamic_bundle.add_named_action_collection("Network Actions", self.id_generator.generate_action_collection_id())
138 for network_data in self.results["network"]["tcp"]:
139 self.createActionNet(network_data, {"value": "connect to socket address", "xsi:type": "maecVocabs:NetworkActionNameVocab-1.0"}, "TCP")
140 if "http" in self.results["network"] and isinstance(self.results["network"]["http"], list) and len(self.results["network"]["http"]) > 0:
141 if not self.dynamic_bundle.collections.action_collections.has_collection("Network Actions"):
142 self.dynamic_bundle.add_named_action_collection("Network Actions", self.id_generator.generate_action_collection_id())
143 for network_data in self.results["network"]["http"]:
144 self.createActionNet(network_data, {"value": "send http " + str(network_data["method"]).lower() + " request", "xsi:type": "maecVocabs:HTTPActionNameVocab-1.0"}, "TCP", "HTTP")
145
146 - def createActionNet(self, network_data, action_name, layer4_protocol=None, layer7_protocol=None):
147 """Create a network Action.
148 @return: action.
149 """
150 src_category = "ipv4-addr"
151 dst_category = "ipv4-addr"
152 if ":" in network_data.get("src", ""): src_category = "ipv6-addr"
153 if ":" in network_data.get("dst", ""): dst_category = "ipv6-addr"
154
155 if layer7_protocol is not None:
156 object_properties = {"xsi:type": "NetworkConnectionObjectType",
157 "layer4_protocol": {"value": layer4_protocol, "force_datatype": True},
158 "layer7_protocol": {"value": layer7_protocol, "force_datatype": True}}
159 else:
160 object_properties = {"xsi:type": "NetworkConnectionObjectType",
161 "layer4_protocol": {"value": layer4_protocol, "force_datatype": True}}
162 associated_object = {"id": self.id_generator.generate_object_id(), "properties": object_properties}
163
164 if layer7_protocol is None:
165 object_properties["source_socket_address"] = {"ip_address": {"category": src_category, "address_value": network_data["src"]},
166 "port": {"port_value": network_data["sport"]}}
167 object_properties["destination_socket_address"] = {"ip_address": {"category": dst_category, "address_value": network_data["dst"]},
168 "port": {"port_value": network_data["dport"]}}
169
170 if layer7_protocol == "DNS":
171 answer_resource_records = []
172 for answer_record in network_data["answers"]:
173 answer_resource_records.append({"entity_type": answer_record["type"],
174 "record_data": answer_record["data"]})
175 object_properties["layer7_connections"] = {"dns_queries": [{"question": {"qname": {"value": network_data["request"]},
176 "qtype": network_data["type"]},
177 "answer_resource_records": answer_resource_records}]}
178 elif layer7_protocol == "HTTP":
179 object_properties["layer7_connections"] = {"http_session":
180 {"http_request_response": [{"http_client_request": {"http_request_line": {"http_method": {"value" : network_data["method"], "force_datatype": True},
181 "value": network_data["path"],
182 "version": network_data["version"]},
183 "http_request_header": {"parsed_header": {"user_agent": network_data["user-agent"],
184 "host": {"domain_name": {"value": network_data["host"]},
185 "port": {"port_value": network_data["port"]}}}},
186 "http_message_body": {"message_body": network_data["body"]}}
187 }
188 ]}
189 }
190 action_dict = {"id": self.id_generator.generate_malware_action_id(),
191 "name": action_name,
192 "associated_objects": [associated_object]}
193
194 self.dynamic_bundle.add_action(MalwareAction.from_dict(action_dict), "Network Actions")
195
197 """Creates the ProcessTree corresponding to that observed by Cuckoo."""
198 if self.options["processtree"] and "behavior" in self.results and "processtree" in self.results["behavior"] and self.results["behavior"]["processtree"]:
199
200 NS_LIST = cybox.utils.nsparser.NS_LIST + [
201 ("http://maec.mitre.org/XMLSchema/maec-bundle-4", "maecBundle", "http://maec.mitre.org/language/version4.0.1/maec_bundle_schema.xsd"),
202 ]
203 OBJ_LIST = cybox.utils.nsparser.OBJ_LIST + [
204 ("ProcessTreeNodeType", "maec.bundle.process_tree.ProcessTreeNode", "", "http://cybox.mitre.org/objects#ProcessObject-2", ["ProcessObjectType"]),
205 ]
206 cybox.META = cybox.utils.nsparser.Metadata(NS_LIST, OBJ_LIST)
207
208 root_node = self.results["behavior"]["processtree"][0]
209
210 if root_node:
211 root_node_dict = {"id": self.id_generator.generate_process_tree_node_id(),
212 "pid": root_node["pid"],
213 "name": root_node["name"],
214 "initiated_actions": self.pidActionMap[root_node["pid"]],
215 "spawned_processes": [self.createProcessTreeNode(child_process) for child_process in root_node["children"]]}
216
217 self.dynamic_bundle.set_process_tree(ProcessTree.from_dict({"root_process": root_node_dict}))
218
220 """Creates a single ProcessTreeNode corresponding to a single node in the tree observed cuckoo.
221 @param process: process from cuckoo dict.
222 """
223 process_node_dict = {"id": self.id_generator.generate_process_tree_node_id(),
224 "pid": process["pid"],
225 "name": process["name"],
226 "initiated_actions": self.pidActionMap[process["pid"]],
227 "spawned_processes": [self.createProcessTreeNode(child_process) for child_process in process["children"]]}
228 return process_node_dict
229
231 """Create and return a dictionary representing a MAEC Malware Action.
232 @param call: the input API call.
233 @param pos: position of the Action with respect to the execution of the malware.
234 """
235
236 action_dict = {}
237 parameter_list = []
238
239 apos = 1
240 for arg in call["arguments"]:
241 parameter_list.append({"ordinal_position": apos,
242 "name": arg["name"],
243 "value": self._illegal_xml_chars_RE.sub("?", arg["value"])
244 })
245 apos = apos + 1
246
247 if call["api"] in api_call_mappings:
248 mapping_dict = api_call_mappings[call["api"]]
249
250 if "action_vocab" in mapping_dict:
251 action_dict["name"] = {"value": mapping_dict["action_name"], "xsi:type": mapping_dict["action_vocab"]}
252 else:
253 action_dict["name"] = {"value": mapping_dict["action_name"]}
254
255
256 if self.options["mode"].lower() == "overview" or self.options["mode"].lower() == "full":
257
258 if call["api"] in api_call_mappings:
259 mapping_dict = api_call_mappings[call["api"]]
260
261 if "action_vocab" in mapping_dict:
262 action_dict["name"] = {"value": mapping_dict["action_name"], "xsi:type": mapping_dict["action_vocab"]}
263 else:
264 action_dict["name"] = {"value": mapping_dict["action_name"]}
265
266 if "parameter_associated_arguments" in mapping_dict:
267 actions_args = self.processActionArguments(mapping_dict["parameter_associated_arguments"], parameter_list)
268 if actions_args:
269 action_dict["action_arguments"] = actions_args
270 else:
271 action_dict["action_arguments"] = []
272
273 if "parameter_associated_objects" in mapping_dict:
274 action_dict["associated_objects"] = self.processActionAssociatedObjects(mapping_dict["parameter_associated_objects"], parameter_list)
275
276
277 if self.options["mode"].lower() == "api" or self.options["mode"].lower() == "full":
278 action_dict["implementation"] = self.processActionImplementation(call, parameter_list)
279
280
281 action_dict["id"] = self.id_generator.generate_malware_action_id()
282 action_dict["ordinal_position"] = pos
283 action_dict["action_status"] = self.mapActionStatus(call["status"])
284 action_dict["timestamp"] = str(call["timestamp"]).replace(" ", "T").replace(",", ".")
285
286 return action_dict
287
289 """Creates a MAEC Action Implementation based on API call input.
290 @param parameter_list: the input parameter list (from the API call).
291 """
292
293 if len(parameter_list) > 0:
294 api_call_dict = {"function_name": call["api"],
295 "return_value": call["return"],
296 "parameters": parameter_list}
297 else:
298 api_call_dict = {"function_name": call["api"],
299 "return_value": call["return"]}
300
301 action_implementation_dict = {"id": self.id_generator.generate_action_implementation_id(),
302 "type": "api call",
303 "api_call": api_call_dict}
304 return action_implementation_dict
305
307 """Processes a dictionary of parameters that should be mapped to Action Arguments in the Malware Action.
308 @param parameter_mappings_dict: the input parameter to Arguments mappings.
309 @param parameter_list: the input parameter list (from the API call).
310 """
311 arguments_list = []
312 for call_parameter in parameter_list:
313 parameter_name = call_parameter["name"]
314 argument_value = call_parameter["value"]
315
316 if not argument_value:
317 continue
318 if parameter_name in parameter_mappings_dict and "associated_argument_vocab" in parameter_mappings_dict[parameter_name]:
319 arguments_list.append({"argument_value": argument_value,
320 "argument_name": {"value": parameter_mappings_dict[parameter_name]["associated_argument_name"],
321 "xsi:type": parameter_mappings_dict[parameter_name]["associated_argument_vocab"]}})
322 elif parameter_name in parameter_mappings_dict and "associated_argument_vocab" not in parameter_mappings_dict[parameter_name]:
323 arguments_list.append({"argument_value": argument_value,
324 "argument_name": {"value": parameter_mappings_dict[parameter_name]["associated_argument_name"]}})
325 return arguments_list
326
327
329 """Processes a dictionary of parameters that should be mapped to Associated Objects in the Action
330 @param associated_objects_dict: the input parameter to Associated_Objects mappings.
331 @param parameter_list: the input parameter list (from the API call).
332 """
333 associated_objects_list = []
334 processed_parameters = []
335
336 if "group_together" in associated_objects_dict:
337 grouped_list = associated_objects_dict["group_together"]
338 associated_object_dict = {}
339 associated_object_dict["id"] = self.id_generator.generate_object_id()
340 associated_object_dict["properties"] = {}
341 for parameter_name in grouped_list:
342 parameter_value = self.getParameterValue(parameter_list, parameter_name)
343
344 if parameter_value:
345 self.processAssociatedObject(associated_objects_dict[parameter_name], parameter_value, associated_object_dict)
346
347 processed_parameters.append(parameter_name)
348 associated_objects_list.append(associated_object_dict)
349
350 if "group_together_nested" in associated_objects_dict:
351 nested_group_dict = associated_objects_dict["group_together_nested"]
352
353 values_dict = {}
354 for parameter_mapping in nested_group_dict["parameter_mappings"]:
355 parameter_value = self.getParameterValue(parameter_list, parameter_mapping["parameter_name"])
356
357 if "post_processing" in parameter_mapping:
358 parameter_value = globals()[parameter_mapping["post_processing"]](parameter_value)
359
360 if parameter_value and "/" not in parameter_mapping["element_name"]:
361 values_dict[parameter_mapping["element_name"].lower()] = parameter_value
362 elif parameter_value and "/" in parameter_mapping["element_name"]:
363 split_element_name = parameter_mapping["element_name"].split("/")
364 values_dict[split_element_name[0].lower()] = self.createNestedDict(split_element_name[1:], parameter_value)
365
366 if values_dict:
367 associated_objects_list.append(self.processAssociatedObject(nested_group_dict, values_dict))
368
369 for call_parameter in parameter_list:
370 if call_parameter["name"] not in processed_parameters and call_parameter["name"] in associated_objects_dict:
371 parameter_value = self.getParameterValue(parameter_list, call_parameter["name"])
372
373 if parameter_value:
374 associated_objects_list.append(self.processAssociatedObject(associated_objects_dict[call_parameter["name"]], parameter_value))
375 if associated_objects_list:
376
377 self.processRegKeys(associated_objects_list)
378
379 return self.processWinHandles(associated_objects_list)
380 else:
381 return []
382
384 """Process any Windows Handles that may be associated with an Action. Replace Handle references with
385 actual Object, if possible.
386 @param associated_objects_list: the list of associated_objects processed for the Action.
387 """
388 input_handles = []
389 output_handles = []
390 input_objects = []
391 output_objects = []
392
393
394 if not self.dynamic_bundle.collections.object_collections.has_collection("Handle-mapped Objects"):
395 self.dynamic_bundle.add_named_object_collection("Handle-mapped Objects", self.id_generator.generate_object_collection_id())
396 if self.options["output_handles"] and not self.dynamic_bundle.collections.object_collections.has_collection("Windows Handles"):
397 self.dynamic_bundle.add_named_object_collection("Windows Handles", self.id_generator.generate_object_collection_id())
398
399 for associated_object_dict in associated_objects_list:
400 object_type = associated_object_dict["properties"]["xsi:type"]
401 object_association_type = associated_object_dict["association_type"]["value"]
402
403 if object_type is "WindowsHandleObjectType":
404 if object_association_type is "output":
405 output_handles.append(associated_object_dict)
406 elif object_association_type is "input":
407 input_handles.append(associated_object_dict)
408
409 elif object_type is not "WindowsHandleObjectType":
410 if object_association_type is "output":
411 output_objects.append(associated_object_dict)
412 elif object_association_type is "input":
413 input_objects.append(associated_object_dict)
414
415
416 if not input_handles and not output_handles:
417 return associated_objects_list
418
419
420 if len(output_handles) == 1:
421 mapped_object = None
422 output_handle = output_handles[0]
423 if len(input_objects) == 1:
424 mapped_object = input_objects[0]
425 elif len(output_objects) == 1:
426 mapped_object = output_objects[0]
427
428 if mapped_object:
429 substituted_object = self.addHandleToMap(output_handle, mapped_object)
430 if substituted_object:
431 associated_objects_list.remove(mapped_object)
432 associated_objects_list.remove(output_handle)
433 associated_objects_list.append(substituted_object)
434
435 elif len(output_handles) == 2:
436 object_list = []
437 if len(input_objects) == 2:
438 object_list = input_objects
439 elif len(output_objects) == 2:
440 object_list = output_objects
441
442 for object in object_list:
443 if "properties" in object and object["properties"]["xsi:type"] is "WindowsThreadObjectType":
444 for output_handle in output_handles:
445 if "type" in output_handle["properties"] and output_handle["properties"]["type"] is "Thread":
446 substituted_object = self.addHandleToMap(output_handle, object)
447 if substituted_object:
448 associated_objects_list.remove(object)
449 associated_objects_list.remove(output_handle)
450 associated_objects_list.append(substituted_object)
451 elif "properties" in object and object["properties"]["xsi:type"] is "ProcessObjectType":
452 for output_handle in output_handles:
453 if "type" in output_handle["properties"] and output_handle["properties"]["type"] is "Process":
454 substituted_object = self.addHandleToMap(output_handle, object)
455 if substituted_object:
456 associated_objects_list.remove(object)
457 associated_objects_list.remove(output_handle)
458 associated_objects_list.append(substituted_object)
459
460
461
462 for input_handle in input_handles:
463 if "type" in input_handle["properties"]:
464 handle_type = input_handle["properties"]["type"]
465 handle_id = input_handle["properties"]["id"]
466 if handle_type in self.handleMap and handle_id in self.handleMap[handle_type]:
467 merged_objects = False
468 mapped_object = self.handleMap[handle_type][handle_id]
469
470 for input_object in input_objects:
471 if input_object["properties"]["xsi:type"] == mapped_object["properties"]["xsi:type"]:
472 merged_dict = defaultdict(dict)
473 for k, v in input_object.iteritems():
474 if isinstance(v, dict):
475 merged_dict[k].update(v)
476 else:
477 merged_dict[k] = v
478 for k, v in mapped_object.iteritems():
479 if isinstance(v, dict):
480 merged_dict[k].update(v)
481 else:
482 merged_dict[k] = v
483
484 merged_dict["id"] = self.id_generator.generate_object_id()
485
486 merged_dict["association_type"] = input_object["association_type"]
487
488 associated_objects_list.remove(input_handle)
489 associated_objects_list.remove(input_object)
490 associated_objects_list.append(merged_dict)
491 merged_objects = True
492
493 if not merged_objects:
494 substituted_object = {"idref": mapped_object["id"],
495 "association_type": {"value": "input", "xsi:type": "maecVocabs:ActionObjectAssociationTypeVocab-1.0"}}
496 associated_objects_list.remove(input_handle)
497 associated_objects_list.append(substituted_object)
498 return associated_objects_list
499
501 """Add a new Handle/Object pairing to the Handle mappings dictionary.
502 @param handle_dict: the dictionary of the Handle to which the object is mapped.
503 @param object_dict: the dictionary of the object mapped to the Handle.
504 return: the substituted object dictionary
505 """
506 if "type" in handle_dict["properties"]:
507 handle_type = handle_dict["properties"]["type"]
508 handle_id = handle_dict["properties"]["id"]
509 substituted_object = {"idref": object_dict["id"],
510 "association_type": object_dict["association_type"]}
511 if handle_type not in self.handleMap:
512 self.handleMap[handle_type] = {}
513 self.handleMap[handle_type][handle_id] = object_dict
514
515
516 if self.options["output_handles"]:
517 handle_reference_dict = {}
518 handle_reference_dict["relationship"] = {"value": "Related_To", "xsi:type": "cyboxVocabs:ObjectRelationshipVocab-1.0"}
519 handle_reference_dict["idref"] = handle_dict["id"]
520 object_dict["related_objects"] = [handle_reference_dict]
521
522 self.dynamic_bundle.add_object(Object.from_dict(handle_dict), "Windows Handles")
523 self.dynamic_bundle.add_object(Object.from_dict(object_dict), "Handle-mapped Objects")
524 return substituted_object
525 return None
526
528 """Process any Registry Key associated with an action. Special case to handle registry Hives that may refer to Handles.
529 @param associated_objects_list: the list of associated_objects processed for the Action.
530 """
531 for associated_object in associated_objects_list:
532 if associated_object["properties"]["xsi:type"] is "WindowsRegistryKeyObjectType":
533 if "hive" in associated_object["properties"] and "HKEY_" not in associated_object["properties"]["hive"]:
534 associated_object = self.processRegKeyHandle(associated_object["properties"]["hive"], associated_object)
535
537 """Process a Registry Key Handle and return the full key, recursing as necessary.
538 @param handle_id: the id of the root-level handle
539 @param current_dict: the dictionary containing the properties of the current key
540 """
541 if "RegistryKey" in self.handleMap and handle_id in self.handleMap["RegistryKey"]:
542 handle_mapped_key = self.handleMap["RegistryKey"][handle_id]
543 if "key" in handle_mapped_key["properties"]:
544 if "key" not in current_dict["properties"]:
545 current_dict["properties"]["key"] = ""
546 current_dict["properties"]["key"] = (handle_mapped_key["properties"]["key"] + "\\" + current_dict["properties"]["key"])
547 if "hive" in handle_mapped_key["properties"]:
548
549 if "HKEY_" in handle_mapped_key["properties"]["hive"]:
550 current_dict["properties"]["hive"] = handle_mapped_key["properties"]["hive"]
551 return current_dict
552
553 else:
554 self.processRegKeyHandle(handle_mapped_key["properties"]["hive"], current_dict)
555 else:
556 return current_dict
557
559 """Process a single Associated Object mapping.
560 @param parameter_mapping_dict: input parameter to Associated Object mapping dictionary.
561 @param parameter_value: the input parameter value (from the API call).
562 @param associated_object_dict: optional associated object dict, for special cases.
563 """
564 if not associated_object_dict:
565 associated_object_dict = {}
566 associated_object_dict["id"] = self.id_generator.generate_object_id()
567 associated_object_dict["properties"] = {}
568
569 if "association_type" not in associated_object_dict:
570 associated_object_dict["association_type"] = {"value": parameter_mapping_dict["association_type"], "xsi:type": "maecVocabs:ActionObjectAssociationTypeVocab-1.0"}
571
572 if "post_processing" in parameter_mapping_dict:
573 parameter_value = globals()[parameter_mapping_dict["post_processing"]](parameter_value)
574
575
576 if "associated_object_element" in parameter_mapping_dict and parameter_mapping_dict["associated_object_element"]:
577
578 if "/" not in parameter_mapping_dict["associated_object_element"]:
579 associated_object_dict["properties"][parameter_mapping_dict["associated_object_element"].lower()] = parameter_value
580
581 elif "/" in parameter_mapping_dict["associated_object_element"]:
582 split_elements = parameter_mapping_dict["associated_object_element"].split("/")
583 if "list__" in split_elements[0]:
584 associated_object_dict["properties"][split_elements[0].lstrip("list__").lower()] = [self.createNestedDict(split_elements[1:], parameter_value)]
585 else:
586 associated_object_dict["properties"][split_elements[0].lower()] = self.createNestedDict(split_elements[1:], parameter_value)
587
588 else:
589 associated_object_dict["properties"] = parameter_value
590
591 if "forced" in parameter_mapping_dict:
592 self.processAssociatedObject(parameter_mapping_dict["forced"], parameter_mapping_dict["forced"]["value"], associated_object_dict)
593
594 if "associated_object_type" in parameter_mapping_dict and "xsi:type" not in associated_object_dict["properties"]:
595 associated_object_dict["properties"]["xsi:type"] = parameter_mapping_dict["associated_object_type"]
596
597 return associated_object_dict
598
600 """Helper function: returns a nested dictionary for an input list.
601 @param list: input list.
602 @param value: value to set the last embedded dictionary item to.
603 """
604 nested_dict = {}
605
606 if len(list) == 1:
607 if "list__" in list[0]:
608 if isinstance(value, dict):
609 list_element = [value]
610 else:
611 list_element = [{list[0].lstrip("list__").lower(): value}]
612 return list_element
613 else:
614 nested_dict[list[0].lower()] = value
615 return nested_dict
616
617 for list_item in list:
618 next_index = list.index(list_item) + 1
619 if "list__" in list_item:
620 nested_dict[list_item.lower().lstrip("list__")] = [self.createNestedDict(list[next_index:], value)]
621 else:
622 nested_dict[list_item.lower()] = self.createNestedDict(list[next_index:], value)
623 break
624
625 return nested_dict
626
628 """Finds and returns an API call parameter value from a list.
629 @param parameter_list: list of API call parameters.
630 @param parameter_name: name of parameter to return value for.
631 """
632 for parameter_dict in parameter_list:
633 if parameter_dict["name"] == parameter_name:
634 return parameter_dict["value"]
635
637 """Creates the Actions corresponding to the API calls initiated by a process.
638 @param process: process from cuckoo dict.
639 """
640 pos = 1
641 pid = process["process_id"]
642
643 for call in process["calls"]:
644
645 action_collection_name = str(call["category"]).capitalize() + " Actions"
646 if not self.dynamic_bundle.collections.action_collections.has_collection(action_collection_name):
647 self.dynamic_bundle.add_named_action_collection(action_collection_name, self.id_generator.generate_action_collection_id())
648
649
650 action_dict = self.apiCallToAction(call, pos)
651
652
653 if pid in self.pidActionMap:
654 action_list = self.pidActionMap[pid].append({"action_id": action_dict["id"]})
655 else:
656 self.pidActionMap[pid] = [{"action_id": action_dict["id"]}]
657
658
659 self.dynamic_bundle.add_action(MalwareAction.from_dict(action_dict), action_collection_name)
660
661 pos = pos + 1
662
663
665 if status is True or status == 1:
666 return "Success"
667 elif status is False or status == 0:
668 return "Fail"
669 else:
670 return None
671
673 """Creates a Windows Executable File (PE) object for capturing static analysis output.
674 """
675
676
677 resource_type_mappings = {"GIF": "Bitmap",
678 "RT_ACCELERATOR": "Accelerators",
679 "RT_ANICURSOR": "AniCursor",
680 "RT_ANIICON": "AniIcon",
681 "RT_BITMAP": "Bitmap",
682 "RT_CURSOR": "Cursor",
683 "RT_DIALOG": "Dialog",
684 "RT_DLGINCLUDE": "DLGInclude",
685 "RT_FONT": "Font",
686 "RT_FONTDIR": "Fontdir",
687 "RT_GROUP_CURSOR": "GroupCursor",
688 "RT_GROUP_ICON": "GroupIcon",
689 "RT_HTML": "HTML",
690 "RT_ICON": "Icon",
691 "RT_MANIFEST": "Manifest",
692 "RT_MENU": "Menu",
693 "RT_PLUGPLAY": "PlugPlay",
694 "RT_RCDATA": "RCData",
695 "RT_STRING": "String",
696 "RT_VERSION": "VersionInfo",
697 "RT_VXD": "Vxd"}
698
699 if len(self.results["static"]) > 0:
700 exports = None
701 imports = None
702 sections = None
703 resources = None
704
705
706 if "pe_exports" in self.results["static"] and len(self.results["static"]["pe_exports"]) > 0:
707 exports = {}
708 exported_function_list = []
709 for x in self.results["static"]["pe_exports"]:
710 exported_function_dict = {
711 "function_name": x["name"],
712 "ordinal": x["ordinal"],
713 "entry_point": x["address"]
714 }
715 exported_function_list.append(exported_function_dict)
716 exports["exported_functions"] = exported_function_list
717
718 if "pe_imports" in self.results["static"] and len(self.results["static"]["pe_imports"]) > 0:
719 imports = []
720 for x in self.results["static"]["pe_imports"]:
721 imported_functions = []
722 import_dict = { "file_name": x["dll"],
723 "imported_functions": imported_functions}
724
725
726 for i in x["imports"]:
727 imported_function_dict = {"function_name": i["name"],
728 "virtual_address": i["address"]}
729 imported_functions.append(imported_function_dict)
730 imports.append(import_dict)
731
732 if "pe_resources" in self.results["static"] and len(self.results["static"]["pe_resources"]) > 0:
733 resources = []
734 for r in self.results["static"]["pe_resources"]:
735 if r["name"] in resource_type_mappings:
736 resource_dict = {"type": resource_type_mappings[r["name"]]}
737 resources.append(resource_dict)
738
739 if "pe_sections" in self.results["static"] and len(self.results["static"]["pe_sections"]) > 0:
740 sections = []
741 for s in self.results["static"]["pe_sections"]:
742 section_dict = {"section_header":
743 {"virtual_size": int(s["virtual_size"], 16),
744 "virtual_address": s["virtual_address"],
745 "name": s["name"],
746 "size_of_raw_data": s["size_of_data"]
747 },
748 "entropy": {"value": s["entropy"]}
749 }
750 sections.append(section_dict)
751
752 if "pe_versioninfo" in self.results["static"] and len(self.results["static"]["pe_versioninfo"]) > 0:
753 if not resources:
754 resources = []
755 version_info = {}
756 for k in self.results["static"]["pe_versioninfo"]:
757 if not k["value"]:
758 continue
759 if k["name"].lower() == "comments":
760 version_info["comments"] = k["value"]
761 if k["name"].lower() == "companyname":
762 version_info["companyname"] = k["value"]
763 if k["name"].lower() == "productversion":
764 version_info["productversion"] = k["value"]
765 if k["name"].lower() == "productname":
766 version_info["product_name"] = k["value"]
767 if k["name"].lower() == "filedescription":
768 version_info["filedescription"] = k["value"]
769 if k["name"].lower() == "fileversion":
770 version_info["fileversion"] = k["value"]
771 if k["name"].lower() == "internalname":
772 version_info["internalname"] = k["value"]
773 if k["name"].lower() == "langid":
774 version_info["langid"] = k["value"]
775 if k["name"].lower() == "legalcopyright":
776 version_info["legalcopyright"] = k["value"]
777 if k["name"].lower() == "legaltrademarks":
778 version_info["legaltrademarks"] = k["value"]
779 if k["name"].lower() == "originalfilename":
780 version_info["originalfilename"] = k["value"]
781 if k["name"].lower() == "privatebuild":
782 version_info["privatebuild"] = k["value"]
783 if k["name"].lower() == "productname":
784 version_info["productname"] = k["value"]
785 if k["name"].lower() == "productversion":
786 version_info["productversion"] = k["value"]
787 if k["name"].lower() == "specialbuild":
788 version_info["specialbuild"] = k["value"]
789 resources.append(version_info)
790 object_dict = {"id": self.id_generator.generate_object_id(),
791 "properties": {"xsi:type":"WindowsExecutableFileObjectType",
792 "imports": imports,
793 "exports": exports,
794 "sections": sections,
795 "resources": resources
796 }
797 }
798 win_exec_file_obj = Object.from_dict(object_dict)
799 return win_exec_file_obj
800
802 """Creates a File object for capturing strings output."""
803 extracted_string_list = []
804 for extracted_string in self.results["strings"]:
805 extracted_string_list.append({"string_value": self._illegal_xml_chars_RE.sub("?", extracted_string)})
806 extracted_features = {"strings": extracted_string_list}
807 object_dict = {"id": self.id_generator.generate_object_id(),
808 "properties": {"xsi:type":"FileObjectType",
809 "extracted_features": extracted_features
810 }
811 }
812 strings_file_obj = Object.from_dict(object_dict)
813 return strings_file_obj
814
816 """Creates a File object.
817 @param file: file dict from Cuckoo dict.
818 @requires: file object.
819 """
820 if "ssdeep" in file and file["ssdeep"] is not None:
821 hashes_list = [{"type": "MD5", "simple_hash_value": file["md5"]},
822 {"type": "SHA1", "simple_hash_value": file["sha1"]},
823 {"type": "SHA256", "simple_hash_value": file["sha256"]},
824 {"type": "SHA512", "simple_hash_value": file["sha512"]},
825 {"type": "SSDEEP", "fuzzy_hash_value": file["ssdeep"]}]
826 else:
827 hashes_list = [{"type": "MD5", "simple_hash_value": file["md5"]},
828 {"type": "SHA1", "simple_hash_value": file["sha1"]},
829 {"type": "SHA256", "simple_hash_value": file["sha256"]},
830 {"type": "SHA512", "simple_hash_value": file["sha512"]}]
831 object_dict = {"id": self.id_generator.generate_object_id(),
832 "properties": {"xsi:type":"FileObjectType",
833 "file_name": file["name"],
834 "file_path": {"value": file["path"]},
835 "file_format": file["type"],
836 "size_in_bytes": file["size"],
837 "hashes": hashes_list}
838 }
839 file_obj = Object.from_dict(object_dict)
840 return file_obj
841
843 """Add Malware Instance Object Attributes to the Malware Subject."""
844
845 if self.results["target"]["category"] == "file":
846 self.subject.set_malware_instance_object_attributes(self.createFileObj(self.results["target"]["file"]))
847
848 elif self.results["target"]["category"] == "url":
849 url_object_dict = {"id": self.id_generator.generate_object_id(), "properties": {"xsi:type": "URIObjectType", "value": self.results["target"]["url"]}}
850 self.subject.set_malware_instance_object_attributes(Object.from_dict(url_object_dict))
851
853 """Adds analysis header."""
854
855 dynamic_analysis = Analysis(self.id_generator.generate_analysis_id(), "dynamic", "triage", BundleReference.from_dict({'bundle_idref': self.dynamic_bundle.id}))
856 dynamic_analysis.start_datetime = datetime_to_iso(self.results["info"]["started"])
857 dynamic_analysis.complete_datetime = datetime_to_iso(self.results["info"]["ended"])
858 dynamic_analysis.summary = StructuredText("Cuckoo Sandbox dynamic analysis of the malware instance object.")
859 dynamic_analysis.add_tool(ToolInformation.from_dict({"id": self.id_generator.generate_tool_id(),
860 "name": "Cuckoo Sandbox",
861 "version": self.results["info"]["version"],
862 "vendor": "http://www.cuckoosandbox.org"}))
863 self.subject.add_analysis(dynamic_analysis)
864
865
866 if self.options["static"] and self.results["static"]:
867 static_analysis = Analysis(self.id_generator.generate_analysis_id(), "static", "triage", BundleReference.from_dict({"bundle_idref": self.static_bundle.id}))
868 static_analysis.start_datetime = datetime_to_iso(self.results["info"]["started"])
869 static_analysis.complete_datetime = datetime_to_iso(self.results["info"]["ended"])
870 static_analysis.summary = StructuredText("Cuckoo Sandbox static (PE) analysis of the malware instance object.")
871 static_analysis.add_tool(ToolInformation.from_dict({"id": self.id_generator.generate_tool_id(),
872 "name": "Cuckoo Sandbox Static Analysis",
873 "version": self.results["info"]["version"],
874 "vendor": "http://www.cuckoosandbox.org"}))
875 self.subject.add_analysis(static_analysis)
876
877 self.static_bundle.add_object(self.createWinExecFileObj())
878
879 if self.options["strings"] and self.results["strings"]:
880 strings_analysis = Analysis(self.id_generator.generate_analysis_id(), "static", "triage", BundleReference.from_dict({"bundle_idref": self.strings_bundle.id}))
881 strings_analysis.start_datetime = datetime_to_iso(self.results["info"]["started"])
882 strings_analysis.complete_datetime = datetime_to_iso(self.results["info"]["ended"])
883 strings_analysis.summary = StructuredText("Cuckoo Sandbox strings analysis of the malware instance object.")
884 strings_analysis.add_tool(ToolInformation.from_dict({"id": self.id_generator.generate_tool_id(),
885 "name": "Cuckoo Sandbox Strings",
886 "version": self.results["info"]["version"],
887 "vendor": "http://www.cuckoosandbox.org"}))
888 self.subject.add_analysis(strings_analysis)
889
890 self.strings_bundle.add_object(self.createFileStringsObj())
891
892 if self.options["virustotal"] and "virustotal" in self.results and self.results["virustotal"]:
893 virustotal_analysis = Analysis(self.id_generator.generate_analysis_id(), "static", "triage", BundleReference.from_dict({"bundle_idref": self.virustotal_bundle.id}))
894 virustotal_analysis.start_datetime = datetime_to_iso(self.results["info"]["started"])
895 virustotal_analysis.complete_datetime = datetime_to_iso(self.results["info"]["ended"])
896 virustotal_analysis.summary = StructuredText("Virustotal results for the malware instance object.")
897 virustotal_analysis.add_tool(ToolInformation.from_dict({"id": self.id_generator.generate_tool_id(),
898 "name": "VirusTotal",
899 "vendor": "https://www.virustotal.com/"}))
900 self.subject.add_analysis(virustotal_analysis)
901
902 if "scans" in self.results["virustotal"]:
903 for engine, signature in self.results["virustotal"]["scans"].items():
904 if signature["detected"]:
905 self.virustotal_bundle.add_av_classification(AVClassification.from_dict({"vendor": engine,
906 "engine_version": signature["version"],
907 "definition_version": signature["update"],
908 "classification_name": signature["result"]}))
909
911 """Adds Dropped files as Objects."""
912 objs = self.results["dropped"]
913 if self.results["target"]["category"] == "file":
914 objs.append(self.results["target"]["file"])
915
916 self.dynamic_bundle.add_named_object_collection("Dropped Files", self.id_generator.generate_object_collection_id())
917 for file in objs:
918 self.dynamic_bundle.add_object(self.createFileObj(file), "Dropped Files")
919
921 """Writes report to disk."""
922 try:
923 report = open(os.path.join(self.reports_path, "report.maec-4.0.1.xml"), "w")
924 report.write("<?xml version='1.0' encoding='UTF-8'?>\n")
925 report.write("<!DOCTYPE doc [<!ENTITY comma ','>]>\n")
926 report.write("<!--\n")
927 report.write("Cuckoo Sandbox MAEC 4.0.1 malware analysis report\n")
928 report.write("http://www.cuckoosandbox.org\n")
929 report.write("-->\n")
930 self.package.to_obj().export(report, 0, name_="MAEC_Package", namespacedef_=MAECNamespaceParser(self.package.to_obj()).get_namespace_schemalocation_str())
931 report.flush()
932 report.close()
933 except (TypeError, IOError) as e:
934 traceback.print_exc()
935 raise CuckooReportError("Failed to generate MAEC 4.0.1 report: %s" % e)
936