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