diff --git a/unswf_service/DEPENDENCIES b/unswf_service/DEPENDENCIES index 01053696..681d1dba 100644 --- a/unswf_service/DEPENDENCIES +++ b/unswf_service/DEPENDENCIES @@ -1,3 +1,4 @@ Dependencies for unswf (is/are): pylzma (https://pypi.python.org/pypi/pylzma) +Flare (http://www.nowrap.de/flare.html) - you need to put the flare binary in /usr/local/bin/ diff --git a/unswf_service/README b/unswf_service/README index 95974ed5..cebe0669 100644 --- a/unswf_service/README +++ b/unswf_service/README @@ -1,7 +1,7 @@ -unswf_service 0.0.2 +unswf_service 0.0.5 ------------- -Decompress Flash files. +Decompress Flash files and estract some ActionScript with Flare. diff --git a/unswf_service/__init__.py b/unswf_service/__init__.py index 47180209..7b8e0640 100644 --- a/unswf_service/__init__.py +++ b/unswf_service/__init__.py @@ -1,22 +1,31 @@ -# (c) 2015, Adam Polkosnik +# (c) 2015, Adam Polkosnik # import logging import os import io +import tempfile +import shutil import zlib import pylzma +from datetime import datetime + +import subprocess # for computing the MD5 from hashlib import md5 # for adding the extracted files from crits.samples.handlers import handle_file +# for adding the actionscript +from crits.raw_data.handlers import handle_raw_data_file + +from crits.core.class_mapper import class_from_id from django.conf import settings from django.template.loader import render_to_string from crits.services.core import Service, ServiceConfigError -#from . import forms +from . import forms logger = logging.getLogger(__name__) class unswfService(Service): @@ -26,10 +35,57 @@ class unswfService(Service): """ name = "unswf" - version = '0.0.2' + version = '0.0.6' supported_types = ['Sample'] description = "Uncompress flash files." + + + @staticmethod + def parse_config(config): + flare_path = config.get("flare_path", "") + if not flare_path: + raise ServiceConfigError("Must specify Flare path.") + + if not os.path.isfile(flare_path): + raise ServiceConfigError("Flare path does not exist.") + + if not os.access(flare_path, os.X_OK): + raise ServiceConfigError("Flare path is not executable.") + + if not 'flare' in flare_path.lower(): + raise ServiceConfigError("Executable does not appear to be Flare.") + + @staticmethod + def get_config(existing_config): + # Generate default config from form and initial values. + config = {} + fields = forms.UnswfConfigForm().fields + for name, field in fields.iteritems(): + config[name] = field.initial + + # If there is a config in the database, use values from that. + if existing_config: + for key, value in existing_config.iteritems(): + config[key] = value + return config + + @staticmethod + def get_config_details(config): + return {'flare_path': config['flare_path']} + + @classmethod + def generate_config_form(self, config): + html = render_to_string('services_config_form.html', + {'name': self.name, + 'form': forms.UnswfConfigForm(initial=config), + 'config_error': None}) + form = forms.UnswfConfigForm + return form, html + + + + @staticmethod def valid_for(obj): if obj.filedata.grid_id == None: @@ -39,9 +95,9 @@ def valid_for(obj): raise ServiceConfigError("Need at least 4 bytes.") # Reset the read pointer. obj.filedata.seek(0) - 'We only care about the compressed flash files' - if not data[:3] in ['CWS','ZWS']: - raise ServiceConfigError("Not a valid compressed Flash file.") + 'We only care about the flash files' + if not data[:3] in ['FWS','CWS','ZWS']: + raise ServiceConfigError("Not a valid Flash file.") def run(self, obj, config): @@ -57,10 +113,62 @@ def run(self, obj, config): if comp == 'ZWS': data.seek(12) # seek to LZMA props swf = 'FWS' + header + pylzma.decompress(data.read()) + if comp == 'FWS': + data.seek(0) + flare_path = config.get("flare_path", "") + # Needed some special temp file, since Flare only + # accepts files with swf file extension + tempdir = tempfile.mkdtemp() + self.directory = tempdir + tfile = os.path.join(tempdir, str(obj.id)+'.swf') + rfile = os.path.join(tempdir, str(obj.id)+'.flr') + (working_dir, filename) = os.path.split(tfile) + with open(tfile, "wb") as f: + f.write(data.read()) + + if os.path.isfile(tfile): + data.seek(0) + self._warning("data md5: %s "% md5(data.read()).hexdigest()) + args = [flare_path, filename] + # Flare does not generate a lot of output, so we should not have to + # worry about this hanging because the buffer is full + proc = subprocess.Popen(args, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, cwd=working_dir) + # Note that we are redirecting STDERR to STDOUT, so we can ignore + # the second element of the tuple returned by communicate(). + output = proc.communicate()[0] + self._warning("Flare output: %s" % output) + if proc.returncode: + msg = ("Flare could not process the file.") + self._warning(msg) + return + with open(rfile, "rb") as newfile: + ac3 = newfile.read() + h3 = md5(ac3).hexdigest() + # clean up the temp files and folders + if os.path.isdir(self.directory): + shutil.rmtree(self.directory) + res = handle_raw_data_file(ac3, self.obj.source, self.current_task.username, + title="Flare", data_type='text', + tool_name='Flare', tool_version='0.6', tool_details='http://www.nowrap.de/flare.html', + method=self.name, + copy_rels=True) + raw_obj = class_from_id("RawData", res["_id"]) + self._warning("obj.id: %s, raw_id:%s, suc: %s" % (str(obj.id), str(raw_obj.id), repr(res['success']) ) ) + # update relationship if a related top-level object is supplied + rel_type = "Related_To" + if obj.id != raw_obj.id: #don't form relationship to itself + resy = obj.add_relationship(rel_item=raw_obj, + rel_type=rel_type, + rel_date=datetime.now(), + analyst=self.current_task.username) + obj.save(username=self.current_task.username) + raw_obj.save(username=self.current_task.username) + self._warning("resy: %s" % (str(resy)) ) + self._add_result("file_added", rfile, {'md5': h3}) except Exception as exc: self._error("unswf: (%s)." % exc) return - if swf: h = md5(str(swf)).hexdigest() name = h diff --git a/unswf_service/forms.py b/unswf_service/forms.py new file mode 100644 index 00000000..58aa2782 --- /dev/null +++ b/unswf_service/forms.py @@ -0,0 +1,13 @@ +from django import forms + +class UnswfConfigForm(forms.Form): + error_css_class = 'error' + required_css_class = 'required' + flare_path = forms.CharField(required=True, + label="Flare Binary", + initial='/usr/local/bin/flare', + widget=forms.TextInput(), + help_text="Full path to Flare binary.") + + def __init__(self, *args, **kwargs): + super(UnswfConfigForm, self).__init__(*args, **kwargs)