PageRenderTime 45ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/modules/reporting/maec41.py

https://gitlab.com/hounge.mobile/cuckoo-android
Python | 822 lines | 641 code | 47 blank | 134 comment | 179 complexity | 3f2c038215570be95d4968682a2bd1e2 MD5 | raw file
  1. # Copyright (c) 2013, The MITRE Corporation
  2. # Copyright (c) 2010-2015, Cuckoo Developers
  3. # All rights reserved.
  4. # This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org
  5. # See the file "docs/LICENSE" for copying permission.
  6. import os
  7. import hashlib
  8. import re
  9. from collections import defaultdict
  10. from lib.maec.maec41 import api_call_mappings, hiveHexToString,\
  11. socketTypeToString, socketProtoToString, socketAFToString,\
  12. regDatatypeToString, intToHex, regStringToKey, regStringToHive
  13. from lib.cuckoo.common.abstracts import Report
  14. from lib.cuckoo.common.exceptions import CuckooDependencyError, CuckooReportError
  15. from lib.cuckoo.common.utils import datetime_to_iso
  16. try:
  17. import cybox
  18. import cybox.utils.nsparser
  19. from cybox.utils import Namespace
  20. from cybox.core import Object
  21. from cybox.common import ToolInformation
  22. from cybox.common import StructuredText
  23. HAVE_CYBOX = True
  24. except ImportError as e:
  25. HAVE_CYBOX = False
  26. try:
  27. from maec.bundle import (Bundle, MalwareAction, BundleReference,
  28. ProcessTree, AVClassification)
  29. from maec.package import MalwareSubject, Package, Analysis
  30. import maec.utils
  31. HAVE_MAEC = True
  32. except ImportError as e:
  33. HAVE_MAEC = False
  34. class MAEC41Report(Report):
  35. """Generates a MAEC 4.1 report.
  36. --Output modes (set in reporting.conf):
  37. mode = "full": Output fully mapped Actions (see maec41), including Windows Handle mapped/substituted objects,
  38. along with API call/parameter capture via Action Implementations.
  39. mode = "overview": Output only fully mapped Actions, without any Action Implementations. Default mode.
  40. mode = "api": Output only Actions with Action Implementations, but no mapped components.
  41. --Other configuration parameters:
  42. processtree = "true" | "false". Output captured ProcessTree as part of dynamic analysis MAEC Bundle. Default = "true".
  43. output_handles = "true" | "false". Output the Windows Handles used to construct the Object-Handle mappings as a
  44. separate Object Collection in the dynamic analysis MAEC Bundle. Only applicable
  45. for mode = "full" or mode = "overview". Default = "false".
  46. static = "true" | "false". Output Cuckoo static analysis (PEfile) output as a separate MAEC Bundle in the document.
  47. Default = "true".
  48. strings = "true" | "false". Output Cuckoo strings output as a separate MAEC Bundle in the document. Default = "true".
  49. virustotal = "true" | "false". Output VirusTotal output as a separate MAEC Bundle in the document. Default = "true".
  50. deduplicate = "true" | "false". Deduplicate the CybOX Objects in the generated dynamic analysis MAEC Bundle. Default = "true".
  51. """
  52. def run(self, results):
  53. """Writes report.
  54. @param results: Cuckoo results dict.
  55. @raise CuckooReportError: if fails to write report.
  56. """
  57. # We put the raise here and not at the import because it would
  58. # otherwise trigger even if the module is not enabled in the config.
  59. if not HAVE_CYBOX:
  60. raise CuckooDependencyError("Unable to import cybox (install with `pip install cybox`)")
  61. elif not HAVE_MAEC:
  62. raise CuckooDependencyError("Unable to import cybox and maec (install with `pip install maec`)")
  63. self._illegal_xml_chars_RE = re.compile(u"[\x00-\x08\x0b\x0c\x0e-\x1F\uD800-\uDFFF\uFFFE\uFFFF]")
  64. # Map of PIDs to the Actions that they spawned.
  65. self.pidActionMap = {}
  66. # Windows Handle map.
  67. self.handleMap = {}
  68. # Save results.
  69. self.results = results
  70. # Setup MAEC document structure.
  71. self.setupMAEC()
  72. # Build MAEC doc.
  73. self.addSubjectAttributes()
  74. self.addDroppedFiles()
  75. self.addAnalyses()
  76. self.addActions()
  77. self.addProcessTree()
  78. # Write XML report.
  79. self.output()
  80. def setupMAEC(self):
  81. """Generates MAEC Package, Malware Subject, and Bundle structure"""
  82. # Instantiate the Namespace class for automatic ID generation.
  83. NS = Namespace("http://www.cuckoosandbox.org", "Cuckoosandbox")
  84. maec.utils.idgen.set_id_namespace(NS)
  85. # Setup the MAEC components
  86. if "target" in self.results and self.results["target"]["category"] == "file":
  87. self.tool_id = maec.utils.idgen.create_id(prefix=self.results["target"]["file"]["md5"])
  88. elif "target" in self.results and self.results["target"]["category"] == "url":
  89. self.tool_id = maec.utils.idgen.create_id(prefix=hashlib.md5(self.results["target"]["file"]).hexdigest())
  90. else:
  91. raise CuckooReportError("Unknown target type or targetinfo module disabled")
  92. # Generate Package.
  93. self.package = Package()
  94. # Generate Malware Subject.
  95. self.subject = MalwareSubject()
  96. # Add the Subject to the Package.
  97. self.package.add_malware_subject(self.subject)
  98. # Generate dynamic analysis bundle.
  99. self.dynamic_bundle = Bundle(None, False, "4.1", "dynamic analysis tool output")
  100. # Add the Bundle to the Subject.
  101. self.subject.add_findings_bundle(self.dynamic_bundle)
  102. # Generate Static Analysis Bundles, if static results exist.
  103. if self.options["static"] and "static" in self.results and self.results["static"]:
  104. self.static_bundle = Bundle(None, False, "4.1", "static analysis tool output")
  105. self.subject.add_findings_bundle(self.static_bundle)
  106. if self.options["strings"] and "strings" in self.results and self.results["strings"]:
  107. self.strings_bundle = Bundle(None, False, '4.1', "static analysis tool output")
  108. self.subject.add_findings_bundle(self.strings_bundle)
  109. if self.options["virustotal"] and "virustotal" in self.results and self.results["virustotal"]:
  110. self.virustotal_bundle = Bundle(None, False, "4.1", "static analysis tool output")
  111. self.subject.add_findings_bundle(self.virustotal_bundle)
  112. def addActions(self):
  113. """Add Actions section."""
  114. # Process-initiated Actions.
  115. if "behavior" in self.results and "processes" in self.results["behavior"]:
  116. for process in self.results["behavior"]["processes"]:
  117. self.createProcessActions(process)
  118. # Network actions.
  119. if "network" in self.results and isinstance(self.results["network"], dict) and len(self.results["network"]) > 0:
  120. if "udp" in self.results["network"] and isinstance(self.results["network"]["udp"], list) and len(self.results["network"]["udp"]) > 0:
  121. self.dynamic_bundle.add_named_action_collection("Network Actions")
  122. for network_data in self.results["network"]["udp"]:
  123. self.createActionNet(network_data, {"value": "connect to socket address", "xsi:type": "maecVocabs:NetworkActionNameVocab-1.0"}, "UDP")
  124. if "dns" in self.results["network"] and isinstance(self.results["network"]["dns"], list) and len(self.results["network"]["dns"]) > 0:
  125. self.dynamic_bundle.add_named_action_collection("Network Actions")
  126. for network_data in self.results["network"]["dns"]:
  127. self.createActionNet(network_data, {"value": "send dns query", "xsi:type": "maecVocabs:DNSActionNameVocab-1.0"}, "UDP", "DNS")
  128. if "tcp" in self.results["network"] and isinstance(self.results["network"]["tcp"], list) and len(self.results["network"]["tcp"]) > 0:
  129. self.dynamic_bundle.add_named_action_collection("Network Actions")
  130. for network_data in self.results["network"]["tcp"]:
  131. self.createActionNet(network_data, {"value": "connect to socket address", "xsi:type": "maecVocabs:NetworkActionNameVocab-1.0"}, "TCP")
  132. if "http" in self.results["network"] and isinstance(self.results["network"]["http"], list) and len(self.results["network"]["http"]) > 0:
  133. self.dynamic_bundle.add_named_action_collection("Network Actions")
  134. for network_data in self.results["network"]["http"]:
  135. self.createActionNet(network_data, {"value": "send http " + str(network_data["method"]).lower() + " request", "xsi:type": "maecVocabs:HTTPActionNameVocab-1.0"}, "TCP", "HTTP")
  136. # Deduplicate the Bundle.
  137. if self.options["deduplicate"]:
  138. self.dynamic_bundle.deduplicate()
  139. def createActionNet(self, network_data, action_name, layer4_protocol=None, layer7_protocol=None):
  140. """Create a network Action.
  141. @return: action.
  142. """
  143. src_category = "ipv4-addr"
  144. dst_category = "ipv4-addr"
  145. if ":" in network_data.get("src", ""): src_category = "ipv6-addr"
  146. if ":" in network_data.get("dst", ""): dst_category = "ipv6-addr"
  147. # Construct the various dictionaries.
  148. if layer7_protocol is not None:
  149. object_properties = {"xsi:type": "NetworkConnectionObjectType",
  150. "layer4_protocol": {"value": layer4_protocol, "force_datatype": True},
  151. "layer7_protocol": {"value": layer7_protocol, "force_datatype": True}}
  152. else:
  153. object_properties = {"xsi:type": "NetworkConnectionObjectType",
  154. "layer4_protocol": {"value": layer4_protocol, "force_datatype": True}}
  155. associated_object = {"id": maec.utils.idgen.create_id(prefix="object"), "properties": object_properties}
  156. # General network connection properties.
  157. if layer7_protocol is None:
  158. object_properties["source_socket_address"] = {"ip_address": {"category": src_category, "address_value": network_data["src"]},
  159. "port": {"port_value": network_data["sport"]}}
  160. object_properties["destination_socket_address"] = {"ip_address": {"category": dst_category, "address_value": network_data["dst"]},
  161. "port": {"port_value": network_data["dport"]}}
  162. # Layer 7-specific object properties.
  163. if layer7_protocol == "DNS":
  164. answer_resource_records = []
  165. for answer_record in network_data["answers"]:
  166. answer_resource_records.append({"entity_type": answer_record["type"],
  167. "record_data": answer_record["data"]})
  168. object_properties["layer7_connections"] = {"dns_queries": [{"question": {"qname": {"value": network_data["request"]},
  169. "qtype": network_data["type"]},
  170. "answer_resource_records": answer_resource_records}]}
  171. elif layer7_protocol == "HTTP":
  172. object_properties["layer7_connections"] = {"http_session":
  173. {"http_request_response": [{"http_client_request": {"http_request_line": {"http_method": {"value" : network_data["method"], "force_datatype": True},
  174. "value": network_data["path"],
  175. "version": network_data["version"]},
  176. "http_request_header": {"parsed_header": {"user_agent": network_data["user-agent"],
  177. "host": {"domain_name": {"value": network_data["host"]},
  178. "port": {"port_value": network_data["port"]}}}},
  179. "http_message_body": {"message_body": network_data["body"]}}
  180. }
  181. ]}
  182. }
  183. action_dict = {"id": maec.utils.idgen.create_id(prefix="action"),
  184. "name": action_name,
  185. "associated_objects": [associated_object]}
  186. # Add the Action to the dynamic analysis bundle.
  187. self.dynamic_bundle.add_action(MalwareAction.from_dict(action_dict), "Network Actions")
  188. def addProcessTree(self):
  189. """Creates the ProcessTree corresponding to that observed by Cuckoo."""
  190. if self.options["processtree"] and "behavior" in self.results and "processtree" in self.results["behavior"] and self.results["behavior"]["processtree"]:
  191. # Process Tree TypedField Fix.
  192. NS_LIST = cybox.utils.nsparser.NS_LIST + [
  193. ("http://maec.mitre.org/language/schema.html#bundle", "maecBundle", "http://maec.mitre.org/language/version4.1/maec_bundle_schema.xsd"),
  194. ]
  195. OBJ_LIST = cybox.utils.nsparser.OBJ_LIST + [
  196. ("ProcessTreeNodeType", "maec.bundle.process_tree.ProcessTreeNode", "", "http://cybox.mitre.org/objects#ProcessObject-2", ["ProcessObjectType"]),
  197. ]
  198. cybox.META = cybox.utils.nsparser.Metadata(NS_LIST, OBJ_LIST)
  199. root_node = self.results["behavior"]["processtree"][0]
  200. if root_node:
  201. root_node_dict = {"id": maec.utils.idgen.create_id(prefix="process_tree_node"),
  202. "pid": root_node["pid"],
  203. "name": root_node["name"],
  204. "initiated_actions": self.pidActionMap[root_node["pid"]],
  205. "spawned_processes": [self.createProcessTreeNode(child_process) for child_process in root_node["children"]]}
  206. self.dynamic_bundle.set_process_tree(ProcessTree.from_dict({"root_process": root_node_dict}))
  207. def createProcessTreeNode(self, process):
  208. """Creates a single ProcessTreeNode corresponding to a single node in the tree observed cuckoo.
  209. @param process: process from cuckoo dict.
  210. """
  211. process_node_dict = {"id": maec.utils.idgen.create_id(prefix="process_tree_node"),
  212. "pid": process["pid"],
  213. "name": process["name"],
  214. "initiated_actions": self.pidActionMap[process["pid"]],
  215. "spawned_processes": [self.createProcessTreeNode(child_process) for child_process in process["children"]]}
  216. return process_node_dict
  217. def apiCallToAction(self, call, pos):
  218. """Create and return a dictionary representing a MAEC Malware Action.
  219. @param call: the input API call.
  220. @param pos: position of the Action with respect to the execution of the malware.
  221. """
  222. # Setup the action/action implementation dictionaries and lists.
  223. action_dict = {}
  224. parameter_list = []
  225. # Add the action parameter arguments.
  226. apos = 1
  227. for arg in call["arguments"]:
  228. parameter_list.append({"ordinal_position": apos,
  229. "name": arg["name"],
  230. "value": self._illegal_xml_chars_RE.sub("?", arg["value"])
  231. })
  232. apos = apos + 1
  233. # Try to add the mapped Action Name.
  234. if call["api"] in api_call_mappings:
  235. mapping_dict = api_call_mappings[call["api"]]
  236. # Handle the Action Name.
  237. if "action_vocab" in mapping_dict:
  238. action_dict["name"] = {"value": mapping_dict["action_name"], "xsi:type": mapping_dict["action_vocab"]}
  239. else:
  240. action_dict["name"] = {"value": mapping_dict["action_name"], "xsi:type": None}
  241. # Try to add the mapped Action Arguments and Associated Objects.
  242. # Only output in "overview" or "full" modes.
  243. if self.options["mode"].lower() == "overview" or self.options["mode"].lower() == "full":
  244. # Check to make sure we have a mapping for this API call.
  245. if call["api"] in api_call_mappings:
  246. mapping_dict = api_call_mappings[call["api"]]
  247. # Handle the Action Name.
  248. if "action_vocab" in mapping_dict:
  249. action_dict["name"] = {"value": mapping_dict["action_name"], "xsi:type": mapping_dict["action_vocab"]}
  250. else:
  251. action_dict["name"] = {"value": mapping_dict["action_name"], "xsi:type": None}
  252. # Handle any Parameters.
  253. if "parameter_associated_arguments" in mapping_dict:
  254. action_dict["action_arguments"] = self.processActionArguments(mapping_dict["parameter_associated_arguments"], parameter_list)
  255. # Handle any Associated Objects.
  256. if "parameter_associated_objects" in mapping_dict:
  257. action_dict["associated_objects"] = self.processActionAssociatedObjects(mapping_dict["parameter_associated_objects"], parameter_list)
  258. # Only output Implementation in "api" or "full" modes.
  259. if self.options["mode"].lower() == "api" or self.options["mode"].lower() == "full":
  260. action_dict["implementation"] = self.processActionImplementation(call, parameter_list)
  261. # Add the common Action properties.
  262. action_dict["id"] = maec.utils.idgen.create_id(prefix="action")
  263. action_dict["ordinal_position"] = pos
  264. action_dict["action_status"] = self.mapActionStatus(call["status"])
  265. action_dict["timestamp"] = str(call["timestamp"]).replace(" ", "T").replace(",", ".")
  266. return action_dict
  267. def processActionImplementation(self, call, parameter_list):
  268. """Creates a MAEC Action Implementation based on API call input.
  269. @param parameter_list: the input parameter list (from the API call).
  270. """
  271. # Generate the API Call dictionary.
  272. if len(parameter_list) > 0:
  273. api_call_dict = {"function_name": call["api"],
  274. "return_value": call["return"],
  275. "parameters": parameter_list}
  276. else:
  277. api_call_dict = {"function_name": call["api"],
  278. "return_value": call["return"]}
  279. # Generate the action implementation dictionary.
  280. action_implementation_dict = {"id": maec.utils.idgen.create_id(prefix="action"),
  281. "type": "api call",
  282. "api_call": api_call_dict}
  283. return action_implementation_dict
  284. def processActionArguments(self, parameter_mappings_dict, parameter_list):
  285. """Processes a dictionary of parameters that should be mapped to Action Arguments in the Malware Action.
  286. @param parameter_mappings_dict: the input parameter to Arguments mappings.
  287. @param parameter_list: the input parameter list (from the API call).
  288. """
  289. arguments_list = []
  290. for call_parameter in parameter_list:
  291. parameter_name = call_parameter["name"]
  292. argument_value = call_parameter["value"]
  293. # Make sure the argument value is set, otherwise skip this parameter.
  294. if not argument_value:
  295. continue
  296. if parameter_name in parameter_mappings_dict and "associated_argument_vocab" in parameter_mappings_dict[parameter_name]:
  297. arguments_list.append({"argument_value": argument_value,
  298. "argument_name": {"value": parameter_mappings_dict[parameter_name]["associated_argument_name"],
  299. "xsi:type": parameter_mappings_dict[parameter_name]["associated_argument_vocab"]}})
  300. elif parameter_name in parameter_mappings_dict and "associated_argument_vocab" not in parameter_mappings_dict[parameter_name]:
  301. arguments_list.append({"argument_value": argument_value,
  302. "argument_name": {"value": parameter_mappings_dict[parameter_name]["associated_argument_name"],
  303. "xsi:type": None}})
  304. if arguments_list:
  305. return arguments_list
  306. def processActionAssociatedObjects(self, associated_objects_dict, parameter_list):
  307. """Processes a dictionary of parameters that should be mapped to Associated Objects in the Action
  308. @param associated_objects_dict: the input parameter to Associated_Objects mappings.
  309. @param parameter_list: the input parameter list (from the API call).
  310. """
  311. associated_objects_list = []
  312. processed_parameters = []
  313. # First, handle any parameters that need to be grouped together into a single Object.
  314. if "group_together" in associated_objects_dict:
  315. grouped_list = associated_objects_dict["group_together"]
  316. associated_object_dict = {}
  317. associated_object_dict["id"] = maec.utils.idgen.create_id(prefix="object")
  318. associated_object_dict["properties"] = {}
  319. for parameter_name in grouped_list:
  320. parameter_value = self.getParameterValue(parameter_list, parameter_name)
  321. # Make sure the parameter value is set.
  322. if parameter_value:
  323. self.processAssociatedObject(associated_objects_dict[parameter_name], parameter_value, associated_object_dict)
  324. # Add the parameter to the list of those that have already been processed.
  325. processed_parameters.append(parameter_name)
  326. associated_objects_list.append(associated_object_dict)
  327. # Handle grouped nested parameters (corner case).
  328. if "group_together_nested" in associated_objects_dict:
  329. nested_group_dict = associated_objects_dict["group_together_nested"]
  330. # Construct the values dictionary.
  331. values_dict = {}
  332. for parameter_mapping in nested_group_dict["parameter_mappings"]:
  333. parameter_value = self.getParameterValue(parameter_list, parameter_mapping["parameter_name"])
  334. # Handle any values that require post-processing (via external functions).
  335. if "post_processing" in parameter_mapping:
  336. parameter_value = globals()[parameter_mapping["post_processing"]](parameter_value)
  337. # Make sure the parameter value is set.
  338. if parameter_value and "/" not in parameter_mapping["element_name"]:
  339. values_dict[parameter_mapping["element_name"].lower()] = parameter_value
  340. elif parameter_value and "/" in parameter_mapping["element_name"]:
  341. split_element_name = parameter_mapping["element_name"].split("/")
  342. values_dict[split_element_name[0].lower()] = self.createNestedDict(split_element_name[1:], parameter_value)
  343. # Make sure we have data in the values dictionary.
  344. if values_dict:
  345. associated_objects_list.append(self.processAssociatedObject(nested_group_dict, values_dict))
  346. # Handle non-grouped, normal parameters.
  347. for call_parameter in parameter_list:
  348. if call_parameter["name"] not in processed_parameters and call_parameter["name"] in associated_objects_dict:
  349. parameter_value = self.getParameterValue(parameter_list, call_parameter["name"])
  350. # Make sure the parameter value is set.
  351. if parameter_value:
  352. associated_objects_list.append(self.processAssociatedObject(associated_objects_dict[call_parameter["name"]], parameter_value))
  353. if associated_objects_list:
  354. # Process any RegKeys to account for the Hive == Handle corner case.
  355. self.processRegKeys(associated_objects_list)
  356. # Perform Windows Handle Update/Replacement Processing.
  357. return self.processWinHandles(associated_objects_list)
  358. else:
  359. return None
  360. def processWinHandles(self, associated_objects_list):
  361. """Process any Windows Handles that may be associated with an Action. Replace Handle references with
  362. actual Object, if possible.
  363. @param associated_objects_list: the list of associated_objects processed for the Action.
  364. """
  365. input_handles = []
  366. output_handles = []
  367. input_objects = []
  368. output_objects = []
  369. # Add the named object collections if they do not exist.
  370. if not self.dynamic_bundle.collections.object_collections.has_collection("Handle-mapped Objects"):
  371. self.dynamic_bundle.add_named_object_collection("Handle-mapped Objects", maec.utils.idgen.create_id(prefix="object"))
  372. if self.options["output_handles"] and not self.dynamic_bundle.collections.object_collections.has_collection("Windows Handles"):
  373. self.dynamic_bundle.add_named_object_collection("Windows Handles", maec.utils.idgen.create_id(prefix="object"))
  374. # Determine the types of objects we're dealing with.
  375. for associated_object_dict in associated_objects_list:
  376. object_type = associated_object_dict["properties"]["xsi:type"]
  377. object_association_type = associated_object_dict["association_type"]["value"]
  378. # Check for handle objects.
  379. if object_type is "WindowsHandleObjectType":
  380. if object_association_type is "output":
  381. output_handles.append(associated_object_dict)
  382. elif object_association_type is "input":
  383. input_handles.append(associated_object_dict)
  384. # Check for non-handle objects.
  385. elif object_type is not "WindowsHandleObjectType":
  386. if object_association_type is "output":
  387. output_objects.append(associated_object_dict)
  388. elif object_association_type is "input":
  389. input_objects.append(associated_object_dict)
  390. # Handle the different cases.
  391. # If no input/output handle, then just return the list unchanged.
  392. if not input_handles and not output_handles:
  393. return associated_objects_list
  394. # Handle the case where there is an input object and output handle.
  395. # Also handle the case where there is an output handle and output object.
  396. if len(output_handles) == 1:
  397. mapped_object = None
  398. output_handle = output_handles[0]
  399. if len(input_objects) == 1:
  400. mapped_object = input_objects[0]
  401. elif len(output_objects) == 1:
  402. mapped_object = output_objects[0]
  403. # Add the handle to the mapping and get the substituted object.
  404. if mapped_object:
  405. substituted_object = self.addHandleToMap(output_handle, mapped_object)
  406. if substituted_object:
  407. associated_objects_list.remove(mapped_object)
  408. associated_objects_list.remove(output_handle)
  409. associated_objects_list.append(substituted_object)
  410. # Handle the corner case for certain calls with two output handles and input objects or output objects.
  411. elif len(output_handles) == 2:
  412. object_list = []
  413. if len(input_objects) == 2:
  414. object_list = input_objects
  415. elif len(output_objects) == 2:
  416. object_list = output_objects
  417. for object in object_list:
  418. if "properties" in object and object["properties"]["xsi:type"] is "WindowsThreadObjectType":
  419. for output_handle in output_handles:
  420. if "type" in output_handle["properties"] and output_handle["properties"]["type"] is "Thread":
  421. substituted_object = self.addHandleToMap(output_handle, object)
  422. if substituted_object:
  423. associated_objects_list.remove(object)
  424. associated_objects_list.remove(output_handle)
  425. associated_objects_list.append(substituted_object)
  426. elif "properties" in object and object["properties"]["xsi:type"] is "ProcessObjectType":
  427. for output_handle in output_handles:
  428. if "type" in output_handle["properties"] and output_handle["properties"]["type"] is "Process":
  429. substituted_object = self.addHandleToMap(output_handle, object)
  430. if substituted_object:
  431. associated_objects_list.remove(object)
  432. associated_objects_list.remove(output_handle)
  433. associated_objects_list.append(substituted_object)
  434. # Handle the case where there is an .
  435. # Lookup the handle and replace it with the appropriate object if we've seen it before.
  436. for input_handle in input_handles:
  437. if "type" in input_handle["properties"]:
  438. handle_type = input_handle["properties"]["type"]
  439. handle_id = input_handle["properties"]["id"]
  440. if handle_type in self.handleMap and handle_id in self.handleMap[handle_type]:
  441. merged_objects = False
  442. mapped_object = self.handleMap[handle_type][handle_id]
  443. # If the input object is of the same type, then "merge" them into a new object.
  444. for input_object in input_objects:
  445. if input_object["properties"]["xsi:type"] == mapped_object["properties"]["xsi:type"]:
  446. merged_dict = defaultdict(dict)
  447. for k, v in input_object.iteritems():
  448. if isinstance(v, dict):
  449. merged_dict[k].update(v)
  450. else:
  451. merged_dict[k] = v
  452. for k, v in mapped_object.iteritems():
  453. if isinstance(v, dict):
  454. merged_dict[k].update(v)
  455. else:
  456. merged_dict[k] = v
  457. # Assign the merged object a new ID.
  458. merged_dict["id"] = maec.utils.idgen.create_id()
  459. # Set the association type to that of the input object.
  460. merged_dict["association_type"] = input_object["association_type"]
  461. # Add the new object to the list of associated objects.
  462. associated_objects_list.remove(input_handle)
  463. associated_objects_list.remove(input_object)
  464. associated_objects_list.append(merged_dict)
  465. merged_objects = True
  466. # Otherwise, add the existing object via a reference.
  467. if not merged_objects:
  468. substituted_object = {"idref": mapped_object["id"],
  469. "association_type": {"value": "input", "xsi:type": "maecVocabs:ActionObjectAssociationTypeVocab-1.0"}}
  470. associated_objects_list.remove(input_handle)
  471. associated_objects_list.append(substituted_object)
  472. return associated_objects_list
  473. def addHandleToMap(self, handle_dict, object_dict):
  474. """Add a new Handle/Object pairing to the Handle mappings dictionary.
  475. @param handle_dict: the dictionary of the Handle to which the object is mapped.
  476. @param object_dict: the dictionary of the object mapped to the Handle.
  477. return: the substituted object dictionary
  478. """
  479. if "type" in handle_dict["properties"]:
  480. handle_type = handle_dict["properties"]["type"]
  481. handle_id = handle_dict["properties"]["id"]
  482. substituted_object = {"idref": object_dict["id"],
  483. "association_type": object_dict["association_type"]}
  484. if handle_type not in self.handleMap:
  485. self.handleMap[handle_type] = {}
  486. self.handleMap[handle_type][handle_id] = object_dict
  487. # Add the Handle to the Mapped Object as a related object.
  488. # This is optional, as the handles themselves may not be very useful.
  489. if self.options["output_handles"]:
  490. handle_reference_dict = {}
  491. handle_reference_dict["relationship"] = {"value": "Related_To", "xsi:type": "cyboxVocabs:ObjectRelationshipVocab-1.0"}
  492. handle_reference_dict["idref"] = handle_dict["id"]
  493. object_dict["related_objects"] = [handle_reference_dict]
  494. # Add the Objects to their corresponding Collections.
  495. self.dynamic_bundle.add_object(Object.from_dict(handle_dict), "Windows Handles")
  496. self.dynamic_bundle.add_object(Object.from_dict(object_dict), "Handle-mapped Objects")
  497. return substituted_object
  498. return None
  499. def processRegKeys(self, associated_objects_list):
  500. """Process any Registry Key associated with an action. Special case to handle registry Hives that may refer to Handles.
  501. @param associated_objects_list: the list of associated_objects processed for the Action.
  502. """
  503. for associated_object in associated_objects_list:
  504. if associated_object["properties"]["xsi:type"] is "WindowsRegistryKeyObjectType":
  505. if "hive" in associated_object["properties"] and "HKEY_" not in associated_object["properties"]["hive"]:
  506. associated_object = self.processRegKeyHandle(associated_object["properties"]["hive"], associated_object)
  507. def processRegKeyHandle(self, handle_id, current_dict):
  508. """Process a Registry Key Handle and return the full key, recursing as necessary.
  509. @param handle_id: the id of the root-level handle
  510. @param current_dict: the dictionary containing the properties of the current key
  511. """
  512. if "RegistryKey" in self.handleMap and handle_id in self.handleMap["RegistryKey"]:
  513. handle_mapped_key = self.handleMap["RegistryKey"][handle_id]
  514. if "key" in handle_mapped_key["properties"]:
  515. if "key" not in current_dict["properties"]:
  516. current_dict["properties"]["key"] = ""
  517. current_dict["properties"]["key"] = (handle_mapped_key["properties"]["key"] + "\\" + current_dict["properties"]["key"])
  518. if "hive" in handle_mapped_key["properties"]:
  519. # If we find the "HKEY_" then we assume we're done.
  520. if "HKEY_" in handle_mapped_key["properties"]["hive"]:
  521. current_dict["properties"]["hive"] = handle_mapped_key["properties"]["hive"]
  522. return current_dict
  523. # If not, then we assume the hive refers to a Handle so we recurse.
  524. else:
  525. self.processRegKeyHandle(handle_mapped_key["properties"]["hive"], current_dict)
  526. else:
  527. return current_dict
  528. def processAssociatedObject(self, parameter_mapping_dict, parameter_value, associated_object_dict = None):
  529. """Process a single Associated Object mapping.
  530. @param parameter_mapping_dict: input parameter to Associated Object mapping dictionary.
  531. @param parameter_value: the input parameter value (from the API call).
  532. @param associated_object_dict: optional associated object dict, for special cases.
  533. """
  534. if not associated_object_dict:
  535. associated_object_dict = {}
  536. associated_object_dict["id"] = maec.utils.idgen.create_id(prefix="object")
  537. associated_object_dict["properties"] = {}
  538. # Set the Association Type if it has not been set already.
  539. if "association_type" not in associated_object_dict:
  540. associated_object_dict["association_type"] = {"value": parameter_mapping_dict["association_type"], "xsi:type": "maecVocabs:ActionObjectAssociationTypeVocab-1.0"}
  541. # Handle any values that require post-processing (via external functions).
  542. if "post_processing" in parameter_mapping_dict:
  543. parameter_value = globals()[parameter_mapping_dict["post_processing"]](parameter_value)
  544. # Handle the actual element value
  545. if "associated_object_element" in parameter_mapping_dict and parameter_mapping_dict["associated_object_element"]:
  546. # Handle simple (non-nested) elements
  547. if "/" not in parameter_mapping_dict["associated_object_element"]:
  548. associated_object_dict["properties"][parameter_mapping_dict["associated_object_element"].lower()] = parameter_value
  549. # Handle complex (nested) elements.
  550. elif "/" in parameter_mapping_dict["associated_object_element"]:
  551. split_elements = parameter_mapping_dict["associated_object_element"].split("/")
  552. if "list__" in split_elements[0]:
  553. associated_object_dict["properties"][split_elements[0].lstrip("list__").lower()] = [self.createNestedDict(split_elements[1:], parameter_value)]
  554. else:
  555. associated_object_dict["properties"][split_elements[0].lower()] = self.createNestedDict(split_elements[1:], parameter_value)
  556. # Corner case for some Registry Keys
  557. else:
  558. associated_object_dict["properties"] = parameter_value
  559. # Set any "forced" properties that should be set alongside the current
  560. if "forced" in parameter_mapping_dict:
  561. self.processAssociatedObject(parameter_mapping_dict["forced"], parameter_mapping_dict["forced"]["value"], associated_object_dict)
  562. # Finally, set the XSI type if it has not been set already.
  563. if "associated_object_type" in parameter_mapping_dict and "xsi:type" not in associated_object_dict["properties"]:
  564. associated_object_dict["properties"]["xsi:type"] = parameter_mapping_dict["associated_object_type"]
  565. return associated_object_dict
  566. def createNestedDict(self, list, value):
  567. """Helper function: returns a nested dictionary for an input list.
  568. @param list: input list.
  569. @param value: value to set the last embedded dictionary item to.
  570. """
  571. nested_dict = {}
  572. if len(list) == 1:
  573. if "list__" in list[0]:
  574. if isinstance(value, dict):
  575. list_element = [value]
  576. else:
  577. list_element = [{list[0].lstrip("list__").lower(): value}]
  578. return list_element
  579. else:
  580. nested_dict[list[0].lower()] = value
  581. return nested_dict
  582. for list_item in list:
  583. next_index = list.index(list_item) + 1
  584. if "list__" in list_item:
  585. nested_dict[list_item.lower().lstrip("list__")] = [self.createNestedDict(list[next_index:], value)]
  586. else:
  587. nested_dict[list_item.lower()] = self.createNestedDict(list[next_index:], value)
  588. break
  589. return nested_dict
  590. def getParameterValue(self, parameter_list, parameter_name):
  591. """Finds and returns an API call parameter value from a list.
  592. @param parameter_list: list of API call parameters.
  593. @param parameter_name: name of parameter to return value for.
  594. """
  595. for parameter_dict in parameter_list:
  596. if parameter_dict["name"] == parameter_name:
  597. return parameter_dict["value"]
  598. def createProcessActions(self, process):
  599. """Creates the Actions corresponding to the API calls initiated by a process.
  600. @param process: process from cuckoo dict.
  601. """
  602. pos = 1
  603. pid = process["process_id"]
  604. for call in process["calls"]:
  605. # Generate the action collection name and create a new named action collection if one does not exist.
  606. action_collection_name = str(call["category"]).capitalize() + " Actions"
  607. self.dynamic_bundle.add_named_action_collection(action_collection_name, maec.utils.idgen.create_id(prefix="action"))
  608. # Generate the Action dictionary.
  609. action_dict = self.apiCallToAction(call, pos)
  610. # Add the action ID to the list of Actions spawned by the process.
  611. if pid in self.pidActionMap:
  612. action_list = self.pidActionMap[pid].append({"action_id": action_dict["id"]})
  613. else:
  614. self.pidActionMap[pid] = [{"action_id": action_dict["id"]}]
  615. # Add the action to the dynamic analysis Bundle.
  616. self.dynamic_bundle.add_action(MalwareAction.from_dict(action_dict), action_collection_name)
  617. # Update the action position
  618. pos = pos + 1
  619. # Map the Cuckoo status to that used in the MAEC/CybOX action_status field.
  620. def mapActionStatus(self, status):
  621. if status is True or status == 1:
  622. return "Success"
  623. elif status is False or status == 0:
  624. return "Fail"
  625. else:
  626. return None
  627. def createWinExecFileObj(self):
  628. """Creates a Windows Executable File (PE) object for capturing static analysis output.
  629. """
  630. # A mapping of Cuckoo resource type names to their name in MAEC
  631. resource_type_mappings = {"GIF": "Bitmap",
  632. "RT_ACCELERATOR": "Accelerators",
  633. "RT_ANICURSOR": "AniCursor",
  634. "RT_ANIICON": "AniIcon",
  635. "RT_BITMAP": "Bitmap",
  636. "RT_CURSOR": "Cursor",
  637. "RT_DIALOG": "Dialog",
  638. "RT_DLGINCLUDE": "DLGInclude",
  639. "RT_FONT": "Font",
  640. "RT_FONTDIR": "Fontdir",
  641. "RT_GROUP_CURSOR": "GroupCursor",
  642. "RT_GROUP_ICON": "GroupIcon",
  643. "RT_HTML": "HTML",
  644. "RT_ICON": "Icon",
  645. "RT_MANIFEST": "Manifest",
  646. "RT_MENU": "Menu",
  647. "RT_PLUGPLAY": "PlugPlay",
  648. "RT_RCDATA": "RCData",
  649. "RT_STRING": "String",
  650. "RT_VERSION": "VersionInfo",
  651. "RT_VXD": "Vxd"}
  652. if len(self.results["static"]) > 0:
  653. exports = None
  654. imports = None
  655. sections = None
  656. resources = None
  657. # PE exports.
  658. if "pe_exports" in self.results["static"] and len(self.results["static"]["pe_exports"]) > 0:
  659. exports = {}
  660. exported_function_list = []
  661. for x in self.results["static"]["pe_exports"]:
  662. exported_function_dict = {
  663. "function_name": x["name"],
  664. "ordinal": x["ordinal"],
  665. "entry_point": x["address"]
  666. }
  667. exported_function_list.append(exported_function_dict)
  668. exports["exported_functions"] = exported_function_list
  669. # PE Imports.
  670. if "pe_imports" in self.results["static"] and len(self.results["static"]["pe_imports"]) > 0:
  671. imports = []
  672. for x in self.results["static"]["pe_imports"]:
  673. imported_functions = []
  674. import_dict = { "file_name": x["dll"],
  675. "imported_functions": imported_functions}
  676. # Imported functions.
  677. for i in x["imports"]:
  678. imported_function_dict = {"function_name": i["name"],
  679. "virtual_address": i["address"]}
  680. imported_functions.append(imported_function_dict)
  681. imports.append(import_dict)
  682. # Resources.
  683. if "pe_resources" in self.results["static"] and len(self.results["static"]["pe_resources"]) > 0:
  684. resources = []
  685. for r in self.results["static"]["pe_resources"]:
  686. if r["name"] in resource_type_mappings:
  687. resource_dict = {"type": resource_type_mappings[r["name"]]}
  688. resources.append(resource_dict)
  689. # Sections.
  690. if "pe_sections" in self.results["static"] and len(self.results["static"]["pe_sections"]) > 0:
  691. sections = []
  692. for s in self.results["static"]["pe_sections"]:
  693. section_dict = {"section_header":
  694. {"virtual_size": int(s["virtual_size"], 16),
  695. "virtual_address": s["virtual_address"],
  696. "name": s["name"],
  697. "size_of_raw_data": s["size_of_data"]
  698. },
  699. "entropy": {"value": s["entropy"]}
  700. }
  701. sections.append(section_dict)
  702. # Version info.
  703. if "pe_versioninfo" in self.results["static"] and len(self.results["static"]["pe_versioninfo"]) > 0:
  704. if not resources:
  705. resources = []
  706. version_info = {}
  707. for k in self.results["static"]["pe_versioninfo"]:
  708. if not k["value"]:
  709. continue
  710. if k["name"].lower() == "comments":
  711. version_info["comments"] = k["value"]
  712. if k["name"].lower() == "companyname":
  713. version_info["companyname"] = k["value"]
  714. if k["name"].lower() == "productversion":
  715. version_info["productversion"] = k["value"]
  716. if k["name"].lower() == "productname":
  717. version_info["product_name"] = k["value"]
  718. if k["name"].lower() == "filedescription":
  719. version_info["filedescription"] = k["value"]
  720. if k["name"].lower() == "fileversion":
  721. version_info["fileversion"] = k["value"]
  722. if k["name"].lower() == "internalname":
  723. version_info["internalname"] = k["value"]
  724. if k["name"].lower() == "langid":
  725. version_info["langid"] = k["value"]
  726. if k["name"].lower() == "legalcopyright":
  727. version_info["legalcopyright"] = k["value"]
  728. if k["name"].lower() == "legaltrademarks":
  729. version_info["legaltrademarks"] = k["value"]
  730. if k["name"].lower() == "originalfilename":
  731. version_info["originalfilename"] = k["value"]
  732. if k["name"].lower() == "privatebuild":
  733. version_info["privatebuild"] = k["value"]
  734. if k["name"].lower() == "productname":
  735. version_info["productname"] = k["value"]
  736. if k["name"].lower() == "productversion":
  737. version_info["productversion"] = k["value"]
  738. if k["name"].lower() == "specialbuild":
  739. version_info["specialbuild"] = k["value"]
  740. resources.append(version_info)
  741. object_dict = {"id": maec.utils.idgen.create_id(prefix="object"),
  742. "properties": {"xsi:type":"WindowsExecutableFileObjectType",
  743. "imports": imports,
  744. "exports": exports,
  745. "sections": sections,
  746. "resources": resources
  747. }
  748. }
  749. win_exec_file_obj = Object.from_dict(object_dict)
  750. return win_exec_file_obj
  751. def createFileStringsObj(self):
  752. """Creates a File object for capturing strings output."""
  753. extracted_string_list = []
  754. for extracted_string in self.results["strings"]:
  755. extracted_string_list.append({"string_value": self._illegal_xml_chars_RE.sub("?", extracted_string)})
  756. extracted_features = {"strings": extracted_string_list}
  757. object_dict = {"id": maec.utils.idgen.create_id(prefix="object"),
  758. "properties": {"xsi:type":"FileObjectType",
  759. "extracted_features": extracted_features
  760. }
  761. }
  762. strings_file_obj = Object.from_dict(object_dict)
  763. return strings_file_obj
  764. def createFileObj(self, file):
  765. """Creates a File object.
  766. @param file: file dict from Cuckoo dict.
  767. @requires: file object.
  768. """
  769. if "ssdeep"