|
1 | 1 | # Copyright (c) 2017, The MITRE Corporation. All rights reserved. |
2 | 2 | # See LICENSE.txt for complete terms. |
3 | 3 |
|
4 | | -# stdlib |
5 | | -from distutils.version import LooseVersion |
6 | | - |
7 | 4 | # external |
8 | | -from lxml import etree |
9 | | -import mixbox.xml |
10 | | -from mixbox.vendor.six import BytesIO, iteritems, binary_type |
| 5 | +from mixbox import fields |
| 6 | + |
11 | 7 |
|
12 | 8 | # internal |
13 | 9 | import stix |
14 | | -import stix.utils as utils |
15 | | -import stix.ttp.malware_instance |
| 10 | +from stix.bindings.extensions.malware import maec_4_1 as maec_instance_binding |
16 | 11 | from stix.ttp.malware_instance import MalwareInstance |
17 | | -import stix.bindings.extensions.malware.maec_4_1 as ext_binding |
18 | | -from mixbox import fields |
19 | | -from stix.bindings.extensions.malware.maec_4_1 import maec_installed |
20 | | -from lxml.etree import _ElementTree |
21 | | - |
22 | | -_MIN_PYTHON_MAEC_VERSION = '4.1.0.12' |
23 | | - |
24 | | - |
25 | | -class UnsupportedVersion(Exception): |
26 | | - def __init__(self, message, expected, found): |
27 | | - super(UnsupportedVersion, self).__init__(message) |
28 | | - self.expected = expected |
29 | | - self.found = found |
30 | 12 |
|
31 | 13 |
|
32 | | -def _check_maec_version(): |
33 | | - """Checks that the installed python-maec has a version greater than or |
34 | | - equal to the minimum supported version. |
35 | | -
|
36 | | - Note: |
37 | | - We do this rather than having a python-maec dependency requirement |
38 | | - listed in setup.py because MAEC is used as an extension to STIX and |
39 | | - not a core component to STIX (like CybOX). |
40 | | -
|
41 | | - Raises: |
42 | | - ImportError: If python-maec is not installed. |
43 | | - UnsupportedVersion: If the python-maec installation does not satisfy |
44 | | - the version requirements. |
45 | | -
|
| 14 | +@stix.register_extension |
| 15 | +class MAECInstance(MalwareInstance): |
46 | 16 | """ |
47 | | - import maec |
48 | | - |
49 | | - found = maec.__version__ |
50 | | - expected = _MIN_PYTHON_MAEC_VERSION |
51 | | - |
52 | | - if LooseVersion(found) >= LooseVersion(expected): |
53 | | - return |
| 17 | + The MAECInstance object provides an extension to the MalwareInstanceType |
| 18 | + which imports and leverages the MAEC 4.1 schema for structured |
| 19 | + characterization of Malware. |
54 | 20 |
|
55 | | - fmt = ("Unsupported python-maec version installed: '%s'. Minimum version " |
56 | | - "is '%s'.") |
57 | | - error = fmt % (found, expected) |
58 | | - raise UnsupportedVersion(error, expected=expected, found=found) |
59 | | - |
60 | | - |
61 | | -try: |
62 | | - # Check that the correct version of python-maec is installed. |
63 | | - _check_maec_version() |
64 | | - |
65 | | - # Import maecPackage into global space |
66 | | - from maec.package.package import Package as maecPackage |
67 | | - |
68 | | - _MAEC_INSTALLED = True |
69 | | -except ImportError: |
70 | | - maecPackage, Package = None, None |
71 | | - _MAEC_INSTALLED = False |
72 | | - |
73 | | - |
74 | | -def is_maec(obj): |
75 | | - """Checks if the input object is python-maec object. |
76 | | -
|
77 | | - Returns: |
78 | | - True if python-maec is ins |
| 21 | + This class extension is automatically registered by the |
| 22 | + MalwareInstanceFactory. |
79 | 23 |
|
| 24 | + Warnings: |
| 25 | + Interacting with the ``maec`` field will fail if the maec library is |
| 26 | + not installed in your Python environment. |
80 | 27 | """ |
81 | | - if not _MAEC_INSTALLED: |
82 | | - return False |
83 | | - |
84 | | - return isinstance(obj, maecPackage) |
85 | | - |
86 | | -def validate_maec_input(instance, value): |
87 | | - if value is None: |
88 | | - return |
89 | | - elif _MAEC_INSTALLED and is_maec(value): |
90 | | - return |
91 | | - elif mixbox.xml.is_element(value) or mixbox.xml.is_etree(value): |
92 | | - return |
93 | | - else: |
94 | | - error = ( |
95 | | - "Cannot set maec to '{0}'. Expected 'lxml.etree._Element' or " |
96 | | - "'maec.package.package.Package'." |
97 | | - ) |
98 | | - error = error.format(type(value)) |
99 | | - raise ValueError(error) |
100 | | - |
101 | | -@stix.register_extension |
102 | | -class MAECInstance(MalwareInstance): |
103 | | - _binding = ext_binding |
| 28 | + _binding = maec_instance_binding |
104 | 29 | _binding_class = _binding.MAEC4_1InstanceType |
105 | | - _namespace = 'http://stix.mitre.org/extensions/Malware#MAEC4.1-1' |
106 | | - _xml_ns_prefix = "stix-maec" |
| 30 | + _namespace = "http://stix.mitre.org/extensions/Malware#MAEC4.1-1" |
107 | 31 | _XSI_TYPE = "stix-maec:MAEC4.1InstanceType" |
108 | | - _TAG_MAEC = "{%s}MAEC" % _namespace |
109 | 32 |
|
110 | | - maec = fields.TypedField("MAEC", preset_hook=validate_maec_input) |
| 33 | + maec = fields.TypedField("MAEC", type_="maec.package.package.Package") |
111 | 34 |
|
112 | 35 | def __init__(self, maec=None): |
113 | 36 | super(MAECInstance, self).__init__() |
114 | | - self.__input_namespaces__ = {} |
115 | | - self.__input_schemalocations__ = {} |
116 | 37 | self.maec = maec |
117 | | - |
118 | | - def _parse_etree(self, root): |
119 | | - node_tag = root.tag |
120 | | - |
121 | | - if node_tag != self._TAG_MAEC: |
122 | | - self._cast_maec(root) |
123 | | - |
124 | | - self._collect_namespaces(root) |
125 | | - self._collect_schemalocs(root) |
126 | | - |
127 | | - def _cast_maec(self, node): |
128 | | - ns_maec = "http://maec.mitre.org/XMLSchema/maec-package-2" |
129 | | - node_ns = etree.QName(node).namespace |
130 | | - |
131 | | - if node_ns == ns_maec: |
132 | | - etree.register_namespace(self._xml_ns_prefix, self._namespace) |
133 | | - node.tag = self._TAG_MAEC |
134 | | - else: |
135 | | - error = "Cannot set maec. Expected tag '{0}' found '{1}'." |
136 | | - error = error.format(self._TAG_MAEC, node.tag) |
137 | | - raise ValueError(error) |
138 | | - |
139 | | - def _collect_schemalocs(self, node): |
140 | | - try: |
141 | | - schemaloc = mixbox.xml.get_schemaloc_pairs(node) |
142 | | - self.__input_schemalocations__ = dict(schemaloc) |
143 | | - except KeyError: |
144 | | - self.__input_schemalocations__ = {} |
145 | | - |
146 | | - def _collect_namespaces(self, node): |
147 | | - self.__input_namespaces__ = dict(iteritems(node.nsmap)) |
148 | | - |
149 | | - @classmethod |
150 | | - def from_obj(cls, obj): |
151 | | - if not obj: |
152 | | - return None |
153 | | - |
154 | | - return_obj = cls() |
155 | | - |
156 | | - if _MAEC_INSTALLED: |
157 | | - obj.MAEC = maecPackage.from_obj(obj.MAEC) |
158 | | - else: |
159 | | - obj.MAEC = obj.MAEC |
160 | | - |
161 | | - return_obj = super(MAECInstance, cls).from_obj(obj) |
162 | | - |
163 | | - return return_obj |
164 | | - |
165 | | - def to_obj(self, ns_info=None): |
166 | | - return_obj = super(MAECInstance, self).to_obj(ns_info=ns_info) |
167 | | - |
168 | | - if mixbox.xml.is_element(self.maec) or mixbox.xml.is_etree(self.maec): |
169 | | - tree = mixbox.xml.get_etree(self.maec) |
170 | | - root = mixbox.xml.get_etree_root(tree) |
171 | | - self._parse_etree(root) |
172 | | - self.maec = root |
173 | | - |
174 | | - if _MAEC_INSTALLED and isinstance(self.maec, maecPackage): |
175 | | - return_obj.MAEC = self.maec.to_obj(ns_info=ns_info) |
176 | | - else: |
177 | | - return_obj.MAEC = self.maec |
178 | | - |
179 | | - return return_obj |
180 | | - |
181 | | - @classmethod |
182 | | - def _maec_from_dict(cls, d): |
183 | | - if _MAEC_INSTALLED: |
184 | | - return maecPackage.from_dict(d) |
185 | | - |
186 | | - raise ValueError( |
187 | | - "Unable to parse 'maec' value in dictionary. Please " |
188 | | - "install python-maec to parse dictionary value." |
189 | | - ) |
190 | | - |
191 | | - @classmethod |
192 | | - def from_dict(cls, d, return_obj=None): |
193 | | - if not d: |
194 | | - return None |
195 | | - |
196 | | - d = d.copy() |
197 | | - |
198 | | - maec = d.get('maec') |
199 | | - |
200 | | - if maec is None: |
201 | | - pass |
202 | | - elif isinstance(maec, dict): |
203 | | - d['maec'] = cls._maec_from_dict(maec) |
204 | | - elif isinstance(maec, binary_type): |
205 | | - d['maec'] = mixbox.xml.get_etree_root(BytesIO(maec)) |
206 | | - else: |
207 | | - raise TypeError("Unknown type for 'maec' entry.") |
208 | | - |
209 | | - return_obj = super(MAECInstance, cls).from_dict(d) |
210 | | - |
211 | | - return return_obj |
212 | | - |
213 | | - def to_dict(self): |
214 | | - d = super(MAECInstance, self).to_dict() |
215 | | - |
216 | | - if self.maec is not None: |
217 | | - if mixbox.xml.is_element(self.maec) or mixbox.xml.is_etree(self.maec): |
218 | | - tree = mixbox.xml.get_etree(self.maec) |
219 | | - root = mixbox.xml.get_etree_root(tree) |
220 | | - self._parse_etree(root) |
221 | | - self.maec = root |
222 | | - |
223 | | - if _MAEC_INSTALLED and isinstance(self.maec, maecPackage): |
224 | | - d['maec'] = self.maec.to_dict() |
225 | | - else: |
226 | | - d['maec'] = etree.tostring(self.maec) |
227 | | - |
228 | | - if self._XSI_TYPE: |
229 | | - d['xsi:type'] = self._XSI_TYPE |
230 | | - |
231 | | - return d |
0 commit comments