Package modules :: Package reporting :: Module maec40
[hide private]
[frames] | no frames]

Source Code for Module modules.reporting.maec40

  1  # Copyright (c) 2013, The MITRE Corporation 
  2  # Copyright (c) 2010-2014, Cuckoo Developers 
  3  # All rights reserved. 
  4   
  5  # This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org 
  6  # See the file "docs/LICENSE" for copying permission. 
  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   
42 -class MAEC40Report(Report):
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 # We put the raise here and not at the import because it would 66 # otherwise trigger even if the module is not enabled in the config. 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 # Map of PIDs to the Actions that they spawned. 72 self.pidActionMap = {} 73 # Windows Handle map. 74 self.handleMap = {} 75 # Save results. 76 self.results = results 77 # Setup MAEC document structure. 78 self.setupMAEC() 79 # Build MAEC doc. 80 self.addSubjectAttributes() 81 self.addDroppedFiles() 82 self.addAnalyses() 83 self.addActions() 84 self.addProcessTree() 85 # Write XML report. 86 self.output()
87
88 - def setupMAEC(self):
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 # Generate Package. 98 self.package = Package(self.id_generator.generate_package_id()) 99 # Generate Malware Subject. 100 self.subject = MalwareSubject(self.id_generator.generate_malware_subject_id()) 101 # Add the Subject to the Package. 102 self.package.add_malware_subject(self.subject) 103 # Generate dynamic analysis bundle. 104 self.dynamic_bundle = Bundle(self.id_generator.generate_bundle_id(), False, "4.0.1", "dynamic analysis tool output") 105 # Add the Bundle to the Subject. 106 self.subject.add_findings_bundle(self.dynamic_bundle) 107 # Generate Static Analysis Bundles, if static results exist. 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
118 - def addActions(self):
119 """Add Actions section.""" 120 # Process-initiated Actions. 121 for process in self.results["behavior"]["processes"]: 122 self.createProcessActions(process) 123 # Network actions. 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 # Construct the various dictionaries. 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 # General network connection properties. 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 # Layer 7-specific object properties. 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 # Add the Action to the dynamic analysis bundle. 194 self.dynamic_bundle.add_action(MalwareAction.from_dict(action_dict), "Network Actions")
195
196 - def addProcessTree(self):
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 # Process Tree TypedField Fix. 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
219 - def createProcessTreeNode(self, process):
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
230 - def apiCallToAction(self, call, pos):
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 # Setup the action/action implementation dictionaries and lists. 236 action_dict = {} 237 parameter_list = [] 238 # Add the action parameter arguments. 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 # Try to add the mapped Action Name. 247 if call["api"] in api_call_mappings: 248 mapping_dict = api_call_mappings[call["api"]] 249 # Handle the Action Name. 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 # Try to add the mapped Action Arguments and Associated Objects. 255 # Only output in "overview" or "full" modes. 256 if self.options["mode"].lower() == "overview" or self.options["mode"].lower() == "full": 257 # Check to make sure we have a mapping for this API call. 258 if call["api"] in api_call_mappings: 259 mapping_dict = api_call_mappings[call["api"]] 260 # Handle the Action Name. 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 # Handle any Parameters. 266 if "parameter_associated_arguments" in mapping_dict: 267 action_dict["action_arguments"] = self.processActionArguments(mapping_dict["parameter_associated_arguments"], parameter_list) 268 # Handle any Associated Objects. 269 if "parameter_associated_objects" in mapping_dict: 270 action_dict["associated_objects"] = self.processActionAssociatedObjects(mapping_dict["parameter_associated_objects"], parameter_list) 271 272 # Only output Implementation in "api" or "full" modes. 273 if self.options["mode"].lower() == "api" or self.options["mode"].lower() == "full": 274 action_dict["implementation"] = self.processActionImplementation(call, parameter_list) 275 276 # Add the common Action properties. 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
284 - def processActionImplementation(self, call, parameter_list):
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 # Generate the API Call dictionary. 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 # Generate the action implementation dictionary. 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
302 - def processActionArguments(self, parameter_mappings_dict, parameter_list):
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 # Make sure the argument value is set, otherwise skip this parameter. 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
326 - def processActionAssociatedObjects(self, associated_objects_dict, parameter_list):
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 # First, handle any parameters that need to be grouped together into a single Object. 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 # Make sure the parameter value is set. 342 if parameter_value: 343 self.processAssociatedObject(associated_objects_dict[parameter_name], parameter_value, associated_object_dict) 344 # Add the parameter to the list of those that have already been processed. 345 processed_parameters.append(parameter_name) 346 associated_objects_list.append(associated_object_dict) 347 # Handle grouped nested parameters (corner case). 348 if "group_together_nested" in associated_objects_dict: 349 nested_group_dict = associated_objects_dict["group_together_nested"] 350 # Construct the values dictionary. 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 # Handle any values that require post-processing (via external functions). 355 if "post_processing" in parameter_mapping: 356 parameter_value = globals()[parameter_mapping["post_processing"]](parameter_value) 357 # Make sure the parameter value is set. 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 # Make sure we have data in the values dictionary. 364 if values_dict: 365 associated_objects_list.append(self.processAssociatedObject(nested_group_dict, values_dict)) 366 # Handle non-grouped, normal parameters. 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 # Make sure the parameter value is set. 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 # Process any RegKeys to account for the Hive == Handle corner case. 375 self.processRegKeys(associated_objects_list) 376 # Perform Windows Handle Update/Replacement Processing. 377 return self.processWinHandles(associated_objects_list) 378 else: 379 return []
380
381 - def processWinHandles(self, associated_objects_list):
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 # Add the named object collections if they do not exist. 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 # Determine the types of objects we're dealing with. 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 # Check for handle objects. 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 # Check for non-handle objects. 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 # Handle the different cases. 413 # If no input/output handle, then just return the list unchanged. 414 if not input_handles and not output_handles: 415 return associated_objects_list 416 # Handle the case where there is an input object and output handle. 417 # Also handle the case where there is an output handle and output object. 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 # Add the handle to the mapping and get the substituted object. 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 # Handle the corner case for certain calls with two output handles and input objects or output objects. 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 # Handle the case where there is an . 459 # Lookup the handle and replace it with the appropriate object if we've seen it before. 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 # If the input object is of the same type, then "merge" them into a new object. 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 # Assign the merged object a new ID. 482 merged_dict["id"] = self.id_generator.generate_object_id() 483 # Set the association type to that of the input object. 484 merged_dict["association_type"] = input_object["association_type"] 485 # Add the new object to the list of associated objects. 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 # Otherwise, add the existing object via a reference. 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
498 - def addHandleToMap(self, handle_dict, object_dict):
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 # Add the Handle to the Mapped Object as a related object. 513 # This is optional, as the handles themselves may not be very useful. 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 # Add the Objects to their corresponding Collections. 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
525 - def processRegKeys(self, associated_objects_list):
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
534 - def processRegKeyHandle(self, handle_id, current_dict):
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 # If we find the "HKEY_" then we assume we're done. 547 if "HKEY_" in handle_mapped_key["properties"]["hive"]: 548 current_dict["properties"]["hive"] = handle_mapped_key["properties"]["hive"] 549 return current_dict 550 # If not, then we assume the hive refers to a Handle so we recurse. 551 else: 552 self.processRegKeyHandle(handle_mapped_key["properties"]["hive"], current_dict) 553 else: 554 return current_dict
555
556 - def processAssociatedObject(self, parameter_mapping_dict, parameter_value, associated_object_dict = None):
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 # Set the Association Type if it has not been set already. 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 # Handle any values that require post-processing (via external functions). 570 if "post_processing" in parameter_mapping_dict: 571 parameter_value = globals()[parameter_mapping_dict["post_processing"]](parameter_value) 572 573 # Handle the actual element value 574 if "associated_object_element" in parameter_mapping_dict and parameter_mapping_dict["associated_object_element"]: 575 # Handle simple (non-nested) elements 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 # Handle complex (nested) elements. 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 # Corner case for some Registry Keys 586 else: 587 associated_object_dict["properties"] = parameter_value 588 # Set any "forced" properties that should be set alongside the current 589 if "forced" in parameter_mapping_dict: 590 self.processAssociatedObject(parameter_mapping_dict["forced"], parameter_mapping_dict["forced"]["value"], associated_object_dict) 591 # Finally, set the XSI type if it has not been set already. 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
597 - def createNestedDict(self, list, value):
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
625 - def getParameterValue(self, parameter_list, parameter_name):
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
634 - def createProcessActions(self, process):
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 # Generate the action collection name and create a new named action collection if one does not exist. 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 # Generate the Action dictionary. 648 action_dict = self.apiCallToAction(call, pos) 649 650 # Add the action ID to the list of Actions spawned by the process. 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 # Add the action to the dynamic analysis Bundle. 657 self.dynamic_bundle.add_action(MalwareAction.from_dict(action_dict), action_collection_name) 658 # Update the action position 659 pos = pos + 1
660 661 # Map the Cuckoo status to that used in the MAEC/CybOX action_status field.
662 - def mapActionStatus(self, status):
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
670 - def createWinExecFileObj(self):
671 """Creates a Windows Executable File (PE) object for capturing static analysis output. 672 """ 673 674 # A mapping of Cuckoo resource type names to their name in MAEC 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 # PE exports. 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 # PE Imports. 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 # Imported functions. 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 # Resources. 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 # Sections. 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 # Version info. 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
799 - def createFileStringsObj(self):
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
813 - def createFileObj(self, file):
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
840 - def addSubjectAttributes(self):
841 """Add Malware Instance Object Attributes to the Malware Subject.""" 842 # File Object. 843 if self.results["target"]["category"] == "file": 844 self.subject.set_malware_instance_object_attributes(self.createFileObj(self.results["target"]["file"])) 845 # URL Object. 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
850 - def addAnalyses(self):
851 """Adds analysis header.""" 852 # Add the dynamic analysis. 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 # Add the static analysis. 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 # Add the static file results. 875 self.static_bundle.add_object(self.createWinExecFileObj()) 876 # Add the strings analysis. 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 # Add the strings results. 888 self.strings_bundle.add_object(self.createFileStringsObj()) 889 # Add the VirusTotal analysis. 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 # Add the VirusTotal results. 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
907 - def addDroppedFiles(self):
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 # Add the named object collection. 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
917 - def output(self):
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 '&#44;'>]>\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