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 action_dict["action_arguments"] = self.processActionArguments(mapping_dict["parameter_associated_arguments"], parameter_list)
268
269 if "parameter_associated_objects" in mapping_dict:
270 action_dict["associated_objects"] = self.processActionAssociatedObjects(mapping_dict["parameter_associated_objects"], parameter_list)
271
272
273 if self.options["mode"].lower() == "api" or self.options["mode"].lower() == "full":
274 action_dict["implementation"] = self.processActionImplementation(call, parameter_list)
275
276
277 action_dict["id"] = self.id_generator.generate_malware_action_id()
278 action_dict["ordinal_position"] = pos
279 action_dict["action_status"] = self.mapActionStatus(call["status"])
280 action_dict["timestamp"] = str(call["timestamp"]).replace(" ", "T").replace(",", ".")
281
282 return action_dict
283
285 """Creates a MAEC Action Implementation based on API call input.
286 @param parameter_list: the input parameter list (from the API call).
287 """
288
289 if len(parameter_list) > 0:
290 api_call_dict = {"function_name": call["api"],
291 "return_value": call["return"],
292 "parameters": parameter_list}
293 else:
294 api_call_dict = {"function_name": call["api"],
295 "return_value": call["return"]}
296
297 action_implementation_dict = {"id": self.id_generator.generate_action_implementation_id(),
298 "type": "api call",
299 "api_call": api_call_dict}
300 return action_implementation_dict
301
303 """Processes a dictionary of parameters that should be mapped to Action Arguments in the Malware Action.
304 @param parameter_mappings_dict: the input parameter to Arguments mappings.
305 @param parameter_list: the input parameter list (from the API call).
306 """
307 arguments_list = []
308 for call_parameter in parameter_list:
309 parameter_name = call_parameter["name"]
310 argument_value = call_parameter["value"]
311
312 if not argument_value:
313 continue
314 if parameter_name in parameter_mappings_dict and "associated_argument_vocab" in parameter_mappings_dict[parameter_name]:
315 arguments_list.append({"argument_value": argument_value,
316 "argument_name": {"value": parameter_mappings_dict[parameter_name]["associated_argument_name"],
317 "xsi:type": parameter_mappings_dict[parameter_name]["associated_argument_vocab"]}})
318 elif parameter_name in parameter_mappings_dict and "associated_argument_vocab" not 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 if arguments_list:
322 return arguments_list
323 else:
324 return None
325
327 """Processes a dictionary of parameters that should be mapped to Associated Objects in the Action
328 @param associated_objects_dict: the input parameter to Associated_Objects mappings.
329 @param parameter_list: the input parameter list (from the API call).
330 """
331 associated_objects_list = []
332 processed_parameters = []
333
334 if "group_together" in associated_objects_dict:
335 grouped_list = associated_objects_dict["group_together"]
336 associated_object_dict = {}
337 associated_object_dict["id"] = self.id_generator.generate_object_id()
338 associated_object_dict["properties"] = {}
339 for parameter_name in grouped_list:
340 parameter_value = self.getParameterValue(parameter_list, parameter_name)
341
342 if parameter_value:
343 self.processAssociatedObject(associated_objects_dict[parameter_name], parameter_value, associated_object_dict)
344
345 processed_parameters.append(parameter_name)
346 associated_objects_list.append(associated_object_dict)
347
348 if "group_together_nested" in associated_objects_dict:
349 nested_group_dict = associated_objects_dict["group_together_nested"]
350
351 values_dict = {}
352 for parameter_mapping in nested_group_dict["parameter_mappings"]:
353 parameter_value = self.getParameterValue(parameter_list, parameter_mapping["parameter_name"])
354
355 if "post_processing" in parameter_mapping:
356 parameter_value = globals()[parameter_mapping["post_processing"]](parameter_value)
357
358 if parameter_value and "/" not in parameter_mapping["element_name"]:
359 values_dict[parameter_mapping["element_name"].lower()] = parameter_value
360 elif parameter_value and "/" in parameter_mapping["element_name"]:
361 split_element_name = parameter_mapping["element_name"].split("/")
362 values_dict[split_element_name[0].lower()] = self.createNestedDict(split_element_name[1:], parameter_value)
363
364 if values_dict:
365 associated_objects_list.append(self.processAssociatedObject(nested_group_dict, values_dict))
366
367 for call_parameter in parameter_list:
368 if call_parameter["name"] not in processed_parameters and call_parameter["name"] in associated_objects_dict:
369 parameter_value = self.getParameterValue(parameter_list, call_parameter["name"])
370
371 if parameter_value:
372 associated_objects_list.append(self.processAssociatedObject(associated_objects_dict[call_parameter["name"]], parameter_value))
373 if associated_objects_list:
374
375 self.processRegKeys(associated_objects_list)
376
377 return self.processWinHandles(associated_objects_list)
378 else:
379 return []
380
382 """Process any Windows Handles that may be associated with an Action. Replace Handle references with
383 actual Object, if possible.
384 @param associated_objects_list: the list of associated_objects processed for the Action.
385 """
386 input_handles = []
387 output_handles = []
388 input_objects = []
389 output_objects = []
390
391
392 if not self.dynamic_bundle.collections.object_collections.has_collection("Handle-mapped Objects"):
393 self.dynamic_bundle.add_named_object_collection("Handle-mapped Objects", self.id_generator.generate_object_collection_id())
394 if self.options["output_handles"] and not self.dynamic_bundle.collections.object_collections.has_collection("Windows Handles"):
395 self.dynamic_bundle.add_named_object_collection("Windows Handles", self.id_generator.generate_object_collection_id())
396
397 for associated_object_dict in associated_objects_list:
398 object_type = associated_object_dict["properties"]["xsi:type"]
399 object_association_type = associated_object_dict["association_type"]["value"]
400
401 if object_type is "WindowsHandleObjectType":
402 if object_association_type is "output":
403 output_handles.append(associated_object_dict)
404 elif object_association_type is "input":
405 input_handles.append(associated_object_dict)
406
407 elif object_type is not "WindowsHandleObjectType":
408 if object_association_type is "output":
409 output_objects.append(associated_object_dict)
410 elif object_association_type is "input":
411 input_objects.append(associated_object_dict)
412
413
414 if not input_handles and not output_handles:
415 return associated_objects_list
416
417
418 if len(output_handles) == 1:
419 mapped_object = None
420 output_handle = output_handles[0]
421 if len(input_objects) == 1:
422 mapped_object = input_objects[0]
423 elif len(output_objects) == 1:
424 mapped_object = output_objects[0]
425
426 if mapped_object:
427 substituted_object = self.addHandleToMap(output_handle, mapped_object)
428 if substituted_object:
429 associated_objects_list.remove(mapped_object)
430 associated_objects_list.remove(output_handle)
431 associated_objects_list.append(substituted_object)
432
433 elif len(output_handles) == 2:
434 object_list = []
435 if len(input_objects) == 2:
436 object_list = input_objects
437 elif len(output_objects) == 2:
438 object_list = output_objects
439
440 for object in object_list:
441 if "properties" in object and object["properties"]["xsi:type"] is "WindowsThreadObjectType":
442 for output_handle in output_handles:
443 if "type" in output_handle["properties"] and output_handle["properties"]["type"] is "Thread":
444 substituted_object = self.addHandleToMap(output_handle, object)
445 if substituted_object:
446 associated_objects_list.remove(object)
447 associated_objects_list.remove(output_handle)
448 associated_objects_list.append(substituted_object)
449 elif "properties" in object and object["properties"]["xsi:type"] is "ProcessObjectType":
450 for output_handle in output_handles:
451 if "type" in output_handle["properties"] and output_handle["properties"]["type"] is "Process":
452 substituted_object = self.addHandleToMap(output_handle, object)
453 if substituted_object:
454 associated_objects_list.remove(object)
455 associated_objects_list.remove(output_handle)
456 associated_objects_list.append(substituted_object)
457
458
459
460 for input_handle in input_handles:
461 if "type" in input_handle["properties"]:
462 handle_type = input_handle["properties"]["type"]
463 handle_id = input_handle["properties"]["id"]
464 if handle_type in self.handleMap and handle_id in self.handleMap[handle_type]:
465 merged_objects = False
466 mapped_object = self.handleMap[handle_type][handle_id]
467
468 for input_object in input_objects:
469 if input_object["properties"]["xsi:type"] == mapped_object["properties"]["xsi:type"]:
470 merged_dict = defaultdict(dict)
471 for k, v in input_object.iteritems():
472 if isinstance(v, dict):
473 merged_dict[k].update(v)
474 else:
475 merged_dict[k] = v
476 for k, v in mapped_object.iteritems():
477 if isinstance(v, dict):
478 merged_dict[k].update(v)
479 else:
480 merged_dict[k] = v
481
482 merged_dict["id"] = self.id_generator.generate_object_id()
483
484 merged_dict["association_type"] = input_object["association_type"]
485
486 associated_objects_list.remove(input_handle)
487 associated_objects_list.remove(input_object)
488 associated_objects_list.append(merged_dict)
489 merged_objects = True
490
491 if not merged_objects:
492 substituted_object = {"idref": mapped_object["id"],
493 "association_type": {"value": "input", "xsi:type": "maecVocabs:ActionObjectAssociationTypeVocab-1.0"}}
494 associated_objects_list.remove(input_handle)
495 associated_objects_list.append(substituted_object)
496 return associated_objects_list
497
499 """Add a new Handle/Object pairing to the Handle mappings dictionary.
500 @param handle_dict: the dictionary of the Handle to which the object is mapped.
501 @param object_dict: the dictionary of the object mapped to the Handle.
502 return: the substituted object dictionary
503 """
504 if "type" in handle_dict["properties"]:
505 handle_type = handle_dict["properties"]["type"]
506 handle_id = handle_dict["properties"]["id"]
507 substituted_object = {"idref": object_dict["id"],
508 "association_type": object_dict["association_type"]}
509 if handle_type not in self.handleMap:
510 self.handleMap[handle_type] = {}
511 self.handleMap[handle_type][handle_id] = object_dict
512
513
514 if self.options["output_handles"]:
515 handle_reference_dict = {}
516 handle_reference_dict["relationship"] = {"value": "Related_To", "xsi:type": "cyboxVocabs:ObjectRelationshipVocab-1.0"}
517 handle_reference_dict["idref"] = handle_dict["id"]
518 object_dict["related_objects"] = [handle_reference_dict]
519
520 self.dynamic_bundle.add_object(Object.from_dict(handle_dict), "Windows Handles")
521 self.dynamic_bundle.add_object(Object.from_dict(object_dict), "Handle-mapped Objects")
522 return substituted_object
523 return None
524
526 """Process any Registry Key associated with an action. Special case to handle registry Hives that may refer to Handles.
527 @param associated_objects_list: the list of associated_objects processed for the Action.
528 """
529 for associated_object in associated_objects_list:
530 if associated_object["properties"]["xsi:type"] is "WindowsRegistryKeyObjectType":
531 if "hive" in associated_object["properties"] and "HKEY_" not in associated_object["properties"]["hive"]:
532 associated_object = self.processRegKeyHandle(associated_object["properties"]["hive"], associated_object)
533
535 """Process a Registry Key Handle and return the full key, recursing as necessary.
536 @param handle_id: the id of the root-level handle
537 @param current_dict: the dictionary containing the properties of the current key
538 """
539 if "RegistryKey" in self.handleMap and handle_id in self.handleMap["RegistryKey"]:
540 handle_mapped_key = self.handleMap["RegistryKey"][handle_id]
541 if "key" in handle_mapped_key["properties"]:
542 if "key" not in current_dict["properties"]:
543 current_dict["properties"]["key"] = ""
544 current_dict["properties"]["key"] = (handle_mapped_key["properties"]["key"] + "\\" + current_dict["properties"]["key"])
545 if "hive" in handle_mapped_key["properties"]:
546
547 if "HKEY_" in handle_mapped_key["properties"]["hive"]:
548 current_dict["properties"]["hive"] = handle_mapped_key["properties"]["hive"]
549 return current_dict
550
551 else:
552 self.processRegKeyHandle(handle_mapped_key["properties"]["hive"], current_dict)
553 else:
554 return current_dict
555
557 """Process a single Associated Object mapping.
558 @param parameter_mapping_dict: input parameter to Associated Object mapping dictionary.
559 @param parameter_value: the input parameter value (from the API call).
560 @param associated_object_dict: optional associated object dict, for special cases.
561 """
562 if not associated_object_dict:
563 associated_object_dict = {}
564 associated_object_dict["id"] = self.id_generator.generate_object_id()
565 associated_object_dict["properties"] = {}
566
567 if "association_type" not in associated_object_dict:
568 associated_object_dict["association_type"] = {"value": parameter_mapping_dict["association_type"], "xsi:type": "maecVocabs:ActionObjectAssociationTypeVocab-1.0"}
569
570 if "post_processing" in parameter_mapping_dict:
571 parameter_value = globals()[parameter_mapping_dict["post_processing"]](parameter_value)
572
573
574 if "associated_object_element" in parameter_mapping_dict and parameter_mapping_dict["associated_object_element"]:
575
576 if "/" not in parameter_mapping_dict["associated_object_element"]:
577 associated_object_dict["properties"][parameter_mapping_dict["associated_object_element"].lower()] = parameter_value
578
579 elif "/" in parameter_mapping_dict["associated_object_element"]:
580 split_elements = parameter_mapping_dict["associated_object_element"].split("/")
581 if "list__" in split_elements[0]:
582 associated_object_dict["properties"][split_elements[0].lstrip("list__").lower()] = [self.createNestedDict(split_elements[1:], parameter_value)]
583 else:
584 associated_object_dict["properties"][split_elements[0].lower()] = self.createNestedDict(split_elements[1:], parameter_value)
585
586 else:
587 associated_object_dict["properties"] = parameter_value
588
589 if "forced" in parameter_mapping_dict:
590 self.processAssociatedObject(parameter_mapping_dict["forced"], parameter_mapping_dict["forced"]["value"], associated_object_dict)
591
592 if "associated_object_type" in parameter_mapping_dict and "xsi:type" not in associated_object_dict["properties"]:
593 associated_object_dict["properties"]["xsi:type"] = parameter_mapping_dict["associated_object_type"]
594
595 return associated_object_dict
596
598 """Helper function: returns a nested dictionary for an input list.
599 @param list: input list.
600 @param value: value to set the last embedded dictionary item to.
601 """
602 nested_dict = {}
603
604 if len(list) == 1:
605 if "list__" in list[0]:
606 if isinstance(value, dict):
607 list_element = [value]
608 else:
609 list_element = [{list[0].lstrip("list__").lower(): value}]
610 return list_element
611 else:
612 nested_dict[list[0].lower()] = value
613 return nested_dict
614
615 for list_item in list:
616 next_index = list.index(list_item) + 1
617 if "list__" in list_item:
618 nested_dict[list_item.lower().lstrip("list__")] = [self.createNestedDict(list[next_index:], value)]
619 else:
620 nested_dict[list_item.lower()] = self.createNestedDict(list[next_index:], value)
621 break
622
623 return nested_dict
624
626 """Finds and returns an API call parameter value from a list.
627 @param parameter_list: list of API call parameters.
628 @param parameter_name: name of parameter to return value for.
629 """
630 for parameter_dict in parameter_list:
631 if parameter_dict["name"] == parameter_name:
632 return parameter_dict["value"]
633
635 """Creates the Actions corresponding to the API calls initiated by a process.
636 @param process: process from cuckoo dict.
637 """
638 pos = 1
639 pid = process["process_id"]
640
641 for call in process["calls"]:
642
643 action_collection_name = str(call["category"]).capitalize() + " Actions"
644 if not self.dynamic_bundle.collections.action_collections.has_collection(action_collection_name):
645 self.dynamic_bundle.add_named_action_collection(action_collection_name, self.id_generator.generate_action_collection_id())
646
647
648 action_dict = self.apiCallToAction(call, pos)
649
650
651 if pid in self.pidActionMap:
652 action_list = self.pidActionMap[pid].append({"action_id": action_dict["id"]})
653 else:
654 self.pidActionMap[pid] = [{"action_id": action_dict["id"]}]
655
656
657 self.dynamic_bundle.add_action(MalwareAction.from_dict(action_dict), action_collection_name)
658
659 pos = pos + 1
660
661
663 if status is True or status == 1:
664 return "Success"
665 elif status is False or status == 0:
666 return "Fail"
667 else:
668 return None
669
671 """Creates a Windows Executable File (PE) object for capturing static analysis output.
672 """
673
674
675 resource_type_mappings = {"GIF": "Bitmap",
676 "RT_ACCELERATOR": "Accelerators",
677 "RT_ANICURSOR": "AniCursor",
678 "RT_ANIICON": "AniIcon",
679 "RT_BITMAP": "Bitmap",
680 "RT_CURSOR": "Cursor",
681 "RT_DIALOG": "Dialog",
682 "RT_DLGINCLUDE": "DLGInclude",
683 "RT_FONT": "Font",
684 "RT_FONTDIR": "Fontdir",
685 "RT_GROUP_CURSOR": "GroupCursor",
686 "RT_GROUP_ICON": "GroupIcon",
687 "RT_HTML": "HTML",
688 "RT_ICON": "Icon",
689 "RT_MANIFEST": "Manifest",
690 "RT_MENU": "Menu",
691 "RT_PLUGPLAY": "PlugPlay",
692 "RT_RCDATA": "RCData",
693 "RT_STRING": "String",
694 "RT_VERSION": "VersionInfo",
695 "RT_VXD": "Vxd"}
696
697 if len(self.results["static"]) > 0:
698 exports = None
699 imports = None
700 sections = None
701 resources = None
702
703
704 if len(self.results["static"]["pe_exports"]) > 0:
705 exports = {}
706 exported_function_list = []
707 for x in self.results["static"]["pe_exports"]:
708 exported_function_dict = {
709 "function_name": x["name"],
710 "ordinal": x["ordinal"],
711 "entry_point": x["address"]
712 }
713 exported_function_list.append(exported_function_dict)
714 exports["exported_functions"] = exported_function_list
715
716 if len(self.results["static"]["pe_imports"]) > 0:
717 imports = []
718 for x in self.results["static"]["pe_imports"]:
719 imported_functions = []
720 import_dict = { "file_name": x["dll"],
721 "imported_functions": imported_functions}
722
723
724 for i in x["imports"]:
725 imported_function_dict = {"function_name": i["name"],
726 "virtual_address": i["address"]}
727 imported_functions.append(imported_function_dict)
728 imports.append(import_dict)
729
730 if len(self.results["static"]["pe_resources"]) > 0:
731 resources = []
732 for r in self.results["static"]["pe_resources"]:
733 if r["name"] in resource_type_mappings:
734 resource_dict = {"type": resource_type_mappings[r["name"]]}
735 resources.append(resource_dict)
736
737 if len(self.results["static"]["pe_sections"]) > 0:
738 sections = []
739 for s in self.results["static"]["pe_sections"]:
740 section_dict = {"section_header":
741 {"virtual_size": int(s["virtual_size"], 16),
742 "virtual_address": s["virtual_address"],
743 "name": s["name"],
744 "size_of_raw_data": s["size_of_data"]
745 },
746 "entropy": {"value": s["entropy"]}
747 }
748 sections.append(section_dict)
749
750 if len(self.results["static"]["pe_versioninfo"]) > 0:
751 if not resources:
752 resources = []
753 version_info = {}
754 for k in self.results["static"]["pe_versioninfo"]:
755 if not k["value"]:
756 continue
757 if k["name"].lower() == "comments":
758 version_info["comments"] = k["value"]
759 if k["name"].lower() == "companyname":
760 version_info["companyname"] = k["value"]
761 if k["name"].lower() == "productversion":
762 version_info["productversion"] = k["value"]
763 if k["name"].lower() == "productname":
764 version_info["product_name"] = k["value"]
765 if k["name"].lower() == "filedescription":
766 version_info["filedescription"] = k["value"]
767 if k["name"].lower() == "fileversion":
768 version_info["fileversion"] = k["value"]
769 if k["name"].lower() == "internalname":
770 version_info["internalname"] = k["value"]
771 if k["name"].lower() == "langid":
772 version_info["langid"] = k["value"]
773 if k["name"].lower() == "legalcopyright":
774 version_info["legalcopyright"] = k["value"]
775 if k["name"].lower() == "legaltrademarks":
776 version_info["legaltrademarks"] = k["value"]
777 if k["name"].lower() == "originalfilename":
778 version_info["originalfilename"] = k["value"]
779 if k["name"].lower() == "privatebuild":
780 version_info["privatebuild"] = k["value"]
781 if k["name"].lower() == "productname":
782 version_info["productname"] = k["value"]
783 if k["name"].lower() == "productversion":
784 version_info["productversion"] = k["value"]
785 if k["name"].lower() == "specialbuild":
786 version_info["specialbuild"] = k["value"]
787 resources.append(version_info)
788 object_dict = {"id": self.id_generator.generate_object_id(),
789 "properties": {"xsi:type":"WindowsExecutableFileObjectType",
790 "imports": imports,
791 "exports": exports,
792 "sections": sections,
793 "resources": resources
794 }
795 }
796 win_exec_file_obj = Object.from_dict(object_dict)
797 return win_exec_file_obj
798
800 """Creates a File object for capturing strings output."""
801 extracted_string_list = []
802 for extracted_string in self.results["strings"]:
803 extracted_string_list.append({"string_value": self._illegal_xml_chars_RE.sub("?", extracted_string)})
804 extracted_features = {"strings": extracted_string_list}
805 object_dict = {"id": self.id_generator.generate_object_id(),
806 "properties": {"xsi:type":"FileObjectType",
807 "extracted_features": extracted_features
808 }
809 }
810 strings_file_obj = Object.from_dict(object_dict)
811 return strings_file_obj
812
814 """Creates a File object.
815 @param file: file dict from Cuckoo dict.
816 @requires: file object.
817 """
818 if "ssdeep" in file and file["ssdeep"] is not None:
819 hashes_list = [{"type": "MD5", "simple_hash_value": file["md5"]},
820 {"type": "SHA1", "simple_hash_value": file["sha1"]},
821 {"type": "SHA256", "simple_hash_value": file["sha256"]},
822 {"type": "SHA512", "simple_hash_value": file["sha512"]},
823 {"type": "SSDEEP", "fuzzy_hash_value": file["ssdeep"]}]
824 else:
825 hashes_list = [{"type": "MD5", "simple_hash_value": file["md5"]},
826 {"type": "SHA1", "simple_hash_value": file["sha1"]},
827 {"type": "SHA256", "simple_hash_value": file["sha256"]},
828 {"type": "SHA512", "simple_hash_value": file["sha512"]}]
829 object_dict = {"id": self.id_generator.generate_object_id(),
830 "properties": {"xsi:type":"FileObjectType",
831 "file_name": file["name"],
832 "file_path": {"value": file["path"]},
833 "file_format": file["type"],
834 "size_in_bytes": file["size"],
835 "hashes": hashes_list}
836 }
837 file_obj = Object.from_dict(object_dict)
838 return file_obj
839
841 """Add Malware Instance Object Attributes to the Malware Subject."""
842
843 if self.results["target"]["category"] == "file":
844 self.subject.set_malware_instance_object_attributes(self.createFileObj(self.results["target"]["file"]))
845
846 elif self.results["target"]["category"] == "url":
847 url_object_dict = {"id": self.id_generator.generate_object_id(), "properties": {"xsi:type": "URIObjectType", "value": self.results["target"]["url"]}}
848 self.subject.set_malware_instance_object_attributes(Object.from_dict(url_object_dict))
849
851 """Adds analysis header."""
852
853 dynamic_analysis = Analysis(self.id_generator.generate_analysis_id(), "dynamic", "triage", BundleReference.from_dict({'bundle_idref': self.dynamic_bundle.id}))
854 dynamic_analysis.start_datetime = datetime_to_iso(self.results["info"]["started"])
855 dynamic_analysis.complete_datetime = datetime_to_iso(self.results["info"]["ended"])
856 dynamic_analysis.summary = StructuredText("Cuckoo Sandbox dynamic analysis of the malware instance object.")
857 dynamic_analysis.add_tool(ToolInformation.from_dict({"id": self.id_generator.generate_tool_id(),
858 "name": "Cuckoo Sandbox",
859 "version": self.results["info"]["version"],
860 "vendor": "http://www.cuckoosandbox.org"}))
861 self.subject.add_analysis(dynamic_analysis)
862
863
864 if self.options["static"] and self.results["static"]:
865 static_analysis = Analysis(self.id_generator.generate_analysis_id(), "static", "triage", BundleReference.from_dict({"bundle_idref": self.static_bundle.id}))
866 static_analysis.start_datetime = datetime_to_iso(self.results["info"]["started"])
867 static_analysis.complete_datetime = datetime_to_iso(self.results["info"]["ended"])
868 static_analysis.summary = StructuredText("Cuckoo Sandbox static (PE) analysis of the malware instance object.")
869 static_analysis.add_tool(ToolInformation.from_dict({"id": self.id_generator.generate_tool_id(),
870 "name": "Cuckoo Sandbox Static Analysis",
871 "version": self.results["info"]["version"],
872 "vendor": "http://www.cuckoosandbox.org"}))
873 self.subject.add_analysis(static_analysis)
874
875 self.static_bundle.add_object(self.createWinExecFileObj())
876
877 if self.options["strings"] and self.results["strings"]:
878 strings_analysis = Analysis(self.id_generator.generate_analysis_id(), "static", "triage", BundleReference.from_dict({"bundle_idref": self.strings_bundle.id}))
879 strings_analysis.start_datetime = datetime_to_iso(self.results["info"]["started"])
880 strings_analysis.complete_datetime = datetime_to_iso(self.results["info"]["ended"])
881 strings_analysis.summary = StructuredText("Cuckoo Sandbox strings analysis of the malware instance object.")
882 strings_analysis.add_tool(ToolInformation.from_dict({"id": self.id_generator.generate_tool_id(),
883 "name": "Cuckoo Sandbox Strings",
884 "version": self.results["info"]["version"],
885 "vendor": "http://www.cuckoosandbox.org"}))
886 self.subject.add_analysis(strings_analysis)
887
888 self.strings_bundle.add_object(self.createFileStringsObj())
889
890 if self.options["virustotal"] and "virustotal" in self.results and self.results["virustotal"]:
891 virustotal_analysis = Analysis(self.id_generator.generate_analysis_id(), "static", "triage", BundleReference.from_dict({"bundle_idref": self.strings_bundle.id}))
892 virustotal_analysis.start_datetime = datetime_to_iso(self.results["info"]["started"])
893 virustotal_analysis.complete_datetime = datetime_to_iso(self.results["info"]["ended"])
894 virustotal_analysis.summary = StructuredText("Virustotal results for the malware instance object.")
895 virustotal_analysis.add_tool(ToolInformation.from_dict({"id": self.id_generator.generate_tool_id(),
896 "name": "VirusTotal",
897 "vendor": "https://www.virustotal.com/"}))
898 self.subject.add_analysis(virustotal_analysis)
899
900 for engine, signature in self.results["virustotal"]["scans"].items():
901 if signature["detected"]:
902 self.virustotal_bundle.add_av_classification(AVClassification.from_dict({"vendor": engine,
903 "engine_version": signature["version"],
904 "definition_version": signature["update"],
905 "classification_name": signature["result"]}))
906
908 """Adds Dropped files as Objects."""
909 objs = self.results["dropped"]
910 if self.results["target"]["category"] == "file":
911 objs.append(self.results["target"]["file"])
912
913 self.dynamic_bundle.add_named_object_collection("Dropped Files", self.id_generator.generate_object_collection_id())
914 for file in objs:
915 self.dynamic_bundle.add_object(self.createFileObj(file), "Dropped Files")
916
918 """Writes report to disk."""
919 try:
920 report = open(os.path.join(self.reports_path, "report.maec-4.0.1.xml"), "w")
921 report.write("<?xml version='1.0' encoding='UTF-8'?>\n")
922 report.write("<!DOCTYPE doc [<!ENTITY comma ','>]>\n")
923 report.write("<!--\n")
924 report.write("Cuckoo Sandbox MAEC 4.0.1 malware analysis report\n")
925 report.write("http://www.cuckoosandbox.org\n")
926 report.write("-->\n")
927 self.package.to_obj().export(report, 0, name_="MAEC_Package", namespacedef_=MAECNamespaceParser(self.package.to_obj()).get_namespace_schemalocation_str())
928 report.flush()
929 report.close()
930 except (TypeError, IOError) as e:
931 traceback.print_exc()
932 raise CuckooReportError("Failed to generate MAEC 4.0.1 report: %s" % e)
933