diff --git a/MANIFEST.in b/MANIFEST.in index 0e1ad3b..02d9f64 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,7 +1,7 @@ include LICENSE README.adoc TODO.adoc # Include the data files -include config-example.json +include config-example.js graft docs prune docs/presentation/ graft malboxes diff --git a/Makefile b/Makefile index 69e10ed..5415f31 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,7 @@ test: pylint malboxes + ./tests/config_example_valid.sh + python -m unittest discover pkg_clean: rm -r build/ dist/ malboxes.egg-info/ diff --git a/README.adoc b/README.adoc index d79adfe..f94e5a2 100644 --- a/README.adoc +++ b/README.adoc @@ -24,6 +24,7 @@ https://github.com/gosecure/malboxes * Python 3.3+ * appdirs * jinja2 +* jsmin * packer: https://www.packer.io/intro/getting-started/setup.html * vagrant: https://www.vagrantup.com/downloads.html @@ -101,7 +102,7 @@ For example: malboxes build win10_64_analyst If you want to customize your configuration, look at the following location -for a `config.json` file: +for a `config.js` file: * Linux/Unix: `~/.config/malboxes/` * Mac OS X: `~/Library/Application Support/malboxes/` diff --git a/TODO.adoc b/TODO.adoc index b673b48..97a504e 100644 --- a/TODO.adoc +++ b/TODO.adoc @@ -1,20 +1,13 @@ = TODO -== pip packaging - -* where should the built boxes go? - -== Misc - -* Make work with trial ISOs - == Minimal malware analyst use case -* push sample w/ IDA debugger -* open ports for remote IDA debugging +* user/pass from config.js +* vagrant: punch hole through NAT for IDA in firewall +* disable Windows Defender * vagrant no net, NAT -* vagrant update box + send on network (archiving) -* vagrant team workflow +* doc: vagrant update box + send on network (archiving) +* doc: vagrant team workflow ** vagrant box repackaging covered here: http://huestones.co.uk/node/305 * git malware analysis template integrated with malboxes * Integrate virtualbox and wireshark tips: https://www.virtualbox.org/wiki/Network_tips @@ -106,3 +99,9 @@ To do malware analysis on embedded systems. == Support WinXP Talk to sholmes, he did it. + +== Optimizations + +=== Use VirtualBox's linked_clones Vagrant config + +https://www.vagrantup.com/docs/virtualbox/configuration.html diff --git a/config-example.js b/config-example.js new file mode 100644 index 0000000..ef76ade --- /dev/null +++ b/config-example.js @@ -0,0 +1,35 @@ +{ + /* + * Malboxes Example Configuration File + * + * Uncomment a specific section of the file to trigger a particular feature. + * + * Paths should be written using forward slashes even on Windows. + * For ex: C:/Tools + */ + + // This allows you to use a local filestore for ISOs. + // For all versions of Windows except Windows 10 you will need this. + // "iso_path": "/path/to/your/windows/isos/", + + // Trial or registered version? + // If using a registered product update the product_key and set trial to 'false'. + // See https://github.com/GoSecure/malboxes/blob/master/docs/windows-licenses.adoc for more information. + "trial": "true", + //"trial": "false", + //"product_key": "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX", + + // VM username and password + // TODO. It doesn't work now. + //"username": "vagrant", + //"password": "vagrant", + + // Setting the IDA Path will copy the IDA remote debugging tools into the guest + //"ida_path": "/path/to/your/ida", + + // Setting Tools Path will copy all the files under the given path into the guest. + // Useful to copy proprietary or unpackaged tools. + //"tools_path": "/path/to/your/tools", + + "_comment": "last line must finish without a comma for file to be valid json" +} diff --git a/config-example.json b/config-example.json deleted file mode 100644 index e40d77a..0000000 --- a/config-example.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "iso_path": "/path/to/your/windows/isos/", - "_comment": "If using a registered product update the product_key and set trial to 'false'.", - "_comment": "See docs/windows-licenses.adoc for more information.", - "trial": "true", - "product_key": "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX", - "username": "vagrant", - "password": "vagrant" -} diff --git a/docs/windows-licenses.adoc b/docs/windows-licenses.adoc index ef9088d..03766d1 100644 --- a/docs/windows-licenses.adoc +++ b/docs/windows-licenses.adoc @@ -6,7 +6,7 @@ Trial versions of Windows use a different .ISO. You can find them here: https://www.microsoft.com/en-us/evalcenter/evaluate-windows If you want to use a trial version make sure you have the following in your -`config.json`: +`config.js`: "trial": true @@ -15,7 +15,7 @@ longer available. Open an issue if you can provide missing information. == Registered -Specify your product key in your `config.json` file. For example: +Specify your product key in your `config.js` file. For example: "trial": false, "product_key": "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX" diff --git a/malboxes/__init__.py b/malboxes/__init__.py index ad8cfb0..ad59432 100755 --- a/malboxes/__init__.py +++ b/malboxes/__init__.py @@ -17,14 +17,5 @@ # def main(): - from malboxes.malboxes import initialize, cleanup - try: - parser, args = initialize() - args.func(parser, args) - - finally: - cleanup() - - -if __name__ == "__main__": + from malboxes.malboxes import main main() diff --git a/malboxes/malboxes.py b/malboxes/malboxes.py index 5705b7e..e66dc54 100755 --- a/malboxes/malboxes.py +++ b/malboxes/malboxes.py @@ -32,10 +32,12 @@ from appdirs import AppDirs from jinja2 import Environment, FileSystemLoader +from jsmin import jsmin from malboxes._version import __version__ DIRS = AppDirs("malboxes") +DEBUG = False def initialize(): # create appdata directories if they don't exist @@ -53,6 +55,7 @@ def init_parser(): "and config generator for malware analysis.") parser.add_argument('-V', '--version', action='version', version='%(prog)s ' + __version__) + parser.add_argument('-d', '--debug', action='store_true', help="Debug mode") subparsers = parser.add_subparsers() # list command @@ -67,6 +70,12 @@ def init_parser(): parser_build.add_argument('profile', help='Name of the profile to build. ' 'Use list command to view ' 'available profiles.') + parser_build.add_argument('--skip-packer-build', action='store_true', + help='Skip packer build phase. ' + 'Only useful for debugging.') + parser_build.add_argument('--skip-vagrant-box-add', action='store_true', + help='Skip vagrant box add phase. ' + 'Only useful for debugging.') parser_build.set_defaults(func=build) # spin command @@ -140,8 +149,7 @@ def prepare_autounattend(config): Uses jinja2 template syntax to generate the resulting XML file. """ - # os type is extracted from profile json - os_type = config['builders'][0]['guest_os_type'].lower() + os_type = _get_os_type(config) filepath = resource_filename(__name__, "installconfig/") env = Environment(loader=FileSystemLoader(filepath)) @@ -151,36 +159,87 @@ def prepare_autounattend(config): f.close() -def load_config(profile): +def prepare_packer_template(config, template_name): """ - Config is in JSON since we can re-use the same in both malboxes and packer + Prepares a packer template JSON file according to configuration and writes + it into a temporary location where packer later expects it. + + Uses jinja2 template syntax to generate the resulting JSON file. + Templates are in profiles/ and snippets in profiles/snippets/. """ try: profile_fd = resource_stream(__name__, - 'profiles/{}.json'.format(profile)) + 'profiles/{}.json'.format(template_name)) except FileNotFoundError: - print("Profile doesn't exist: {}".format(profile)) + print("Profile doesn't exist: {}".format(template_name)) sys.exit(2) + filepath = resource_filename(__name__, 'profiles/') + env = Environment(loader=FileSystemLoader(filepath), autoescape=False, + trim_blocks=True, lstrip_blocks=True) + template = env.get_template("{}.json".format(template_name)) + + # write to temporary file + f = create_cachefd('{}.json'.format(template_name)) + f.write(template.render(config)) # pylint: disable=no-member + f.close() + return f.name + + +def prepare_config(profile): + """ + Prepares Malboxes configuration and merge with Packer profile configuration + + Packer uses a configuration in JSON so we decided to go with JSON as well. + However since we have features that should be easily "toggled" by our users + I wanted to add an easy way of "commenting out" or "uncommenting" a + particular feature. JSON doesn't support comments. However JSON's author + gives a nice suggestion here[1] that I will follow. + + In a nutshell, our configuration is Javascript, which when minified gives + JSON and then it gets merged with the selected profile. + + [1]: https://plus.google.com/+DouglasCrockfordEsq/posts/RK8qyGVaGSr + """ # if config does not exist, copy default one - config_file = os.path.join(DIRS.user_config_dir, 'config.json') + config_file = os.path.join(DIRS.user_config_dir, 'config.js') if not os.path.isfile(config_file): print("Default configuration doesn't exist. Populating one: {}" .format(config_file)) - shutil.copy(resource_filename(__name__, 'config-example.json'), + shutil.copy(resource_filename(__name__, 'config-example.js'), config_file) - # load general config - config = {} - with open(config_file, 'r') as f: - config = json.load(f) + config = load_config(config_file, profile) + + packer_tmpl = prepare_packer_template(config, profile) # merge/update with profile config - config.update(json.load(TextIOWrapper(profile_fd))) + with open(packer_tmpl, 'r') as f: + config.update(json.loads(f.read())) + + return config, packer_tmpl + +def load_config(config_file, profile): + """Loads the minified JSON config. Returns a dict.""" + config = {} + with open(config_file, 'r') as f: + # minify then load as JSON + config = json.loads(jsmin(f.read())) + + # add packer required variables + # Note: Backslashes are replaced with forward slashes (Packer on Windows) + config['cache_dir'] = DIRS.user_cache_dir.replace('\\', '/') + config['dir'] = resource_filename(__name__, "").replace('\\', '/') + config['profile_name'] = profile return config +def _get_os_type(config): + """OS Type is extracted from profile json config""" + return config['builders'][0]['guest_os_type'].lower() + + tempfiles = [] def create_cachefd(filename): tempfiles.append(filename) @@ -188,12 +247,15 @@ def create_cachefd(filename): def cleanup(): - """Removes temporary files""" - for f in tempfiles: - os.remove(os.path.join(DIRS.user_cache_dir, f)) + """Removes temporary files. Keep them in debug mode.""" + if not DEBUG: + for f in tempfiles: + os.remove(os.path.join(DIRS.user_cache_dir, f)) def run_foreground(command): + if DEBUG: + print("DEBUG: Executing {}".format(command)) p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) try: @@ -212,44 +274,60 @@ def run_foreground(command): return p.returncode -def run_packer(packer_config): +def run_packer(packer_tmpl, args): print("Starting packer to generate the VM") print("----------------------------------") - # packer or packer-io? - binary = 'packer' - if shutil.which(binary) == None: - binary = 'packer-io' + prev_cwd = os.getcwd() + os.chdir(DIRS.user_cache_dir) + + try: + # packer or packer-io? + binary = 'packer' if shutil.which(binary) == None: - print("packer not found. Install it: " - "https://www.packer.io/intro/getting-started/setup.html") - return 254 - - # run packer with relevant config - configfile = os.path.join(DIRS.user_config_dir, 'config.json') - cmd = [binary, 'build', - '-var-file={}'.format(configfile), - "-var", "malboxes_cache_dir={}".format(DIRS.user_cache_dir), - packer_config] - ret = run_foreground(cmd) + binary = 'packer-io' + if shutil.which(binary) == None: + print("packer not found. Install it: " + "https://www.packer.io/intro/getting-started/setup.html") + return 254 + + # run packer with relevant config minified + configfile = os.path.join(DIRS.user_config_dir, 'config.js') + with open(configfile, 'r') as config: + f = create_cachefd('packer_var_file.json') + f.write(jsmin(config.read())) + f.close() + + flags = ['-var-file={}'.format(f.name)] + if DEBUG: + flags.append('-debug') + + cmd = [binary, 'build'] + cmd.extend(flags) + cmd.append(packer_tmpl) + ret = run_foreground(cmd) + + finally: + os.chdir(prev_cwd) print("----------------------------------") print("packer completed with return code: {}".format(ret)) return ret -def import_box(config, args): - print("Importing box into vagrant") +def add_box(config, args): + print("Adding box into vagrant") print("--------------------------") box = config['post-processors'][0]['output'] + box = os.path.join(DIRS.user_cache_dir, box) box = box.replace('{{user `name`}}', args.profile) cmd = ['vagrant', 'box', 'add', box, '--name={}'.format(args.profile)] ret = run_foreground(cmd) print("----------------------------") - print("vagrant box import completed with return code: {}".format(ret)) + print("vagrant box add completed with return code: {}".format(ret)) return ret @@ -271,19 +349,26 @@ def list_profiles(parser, args): def build(parser, args): - config = load_config(args.profile) print("Generating configuration files...") + config, packer_tmpl = prepare_config(args.profile) prepare_autounattend(config) print("Configuration files are ready") - ret = run_packer(resource_filename(__name__, - "profiles/{}.json".format(args.profile))) + + if not args.skip_packer_build: + ret = run_packer(packer_tmpl, args) + else: + ret = 0 if ret != 0: print("Packer failed. Build failed. Exiting...") sys.exit(3) - ret = import_box(config, args) + if not args.skip_vagrant_box_add: + ret = add_box(config, args) + else: + ret = 0 + if ret != 0: print("'vagrant box add' failed. Build failed. Exiting...") sys.exit(4) @@ -296,20 +381,20 @@ def build(parser, args): malboxes spin {} - You can safely remove the boxes/ directory if you don't plan on - hosting or sharing your base box. + You can safely remove the {}/boxes/ + directory if you don't plan on hosting or sharing your base box. You can re-use this base box several times by using `malboxes spin`. Each VM will be independent of each other. ===============================================================""") - .format(args.profile)) + .format(args.profile, DIRS.user_cache_dir)) def spin(parser, args): """ Creates a Vagrantfile based on a template using the jinja2 engine """ - config = load_config(args.profile) + config, _ = prepare_config(args.profile) print("Creating a Vagrantfile") filepath = resource_filename(__name__, "vagrantfiles/") @@ -446,8 +531,11 @@ def document(parser, args): def main(): + global DEBUG try: parser, args = initialize() + if args.debug: + DEBUG = True args.func(parser, args) finally: diff --git a/malboxes/profiles/snippets/builder_virtualbox_windows.json b/malboxes/profiles/snippets/builder_virtualbox_windows.json new file mode 100644 index 0000000..af38151 --- /dev/null +++ b/malboxes/profiles/snippets/builder_virtualbox_windows.json @@ -0,0 +1,15 @@ + "type": "virtualbox-iso", + "guest_additions_mode": "attach", + "headless": "false", + "communicator": "winrm", + "winrm_username": "{{ '{{user `winrm_user`}}' }}", + "winrm_password": "{{ '{{user `winrm_pass`}}' }}", + "winrm_timeout": "30m", + "shutdown_command": "shutdown /s /f /t 10", + "vboxmanage": [ + ["modifyvm", "{{ '{{.Name}}' }}", "--memory", "4096"], + ["modifyvm", "{{ '{{.Name}}' }}", "--cpus", "1"] + ], + "boot_wait": "10s", + "disk_size": "15360", + "output_directory": "builds" diff --git a/malboxes/profiles/snippets/ida_remote_32.json b/malboxes/profiles/snippets/ida_remote_32.json new file mode 100644 index 0000000..80eb328 --- /dev/null +++ b/malboxes/profiles/snippets/ida_remote_32.json @@ -0,0 +1,5 @@ + { + "type": "file", + "source": "{{ ida_path }}/dbgsrv/win32_remote.exe", + "destination": "C:\\Tools\\win32_remote.exe" + } diff --git a/malboxes/profiles/snippets/ida_remote_64.json b/malboxes/profiles/snippets/ida_remote_64.json new file mode 100644 index 0000000..0658a82 --- /dev/null +++ b/malboxes/profiles/snippets/ida_remote_64.json @@ -0,0 +1,5 @@ + { + "type": "file", + "source": "{{ ida_path }}/dbgsrv/win64_remotex64.exe", + "destination": "C:\\Tools\\win64_remotex64.exe" + } diff --git a/malboxes/profiles/snippets/postprocessor_vagrant.json b/malboxes/profiles/snippets/postprocessor_vagrant.json new file mode 100644 index 0000000..d174e6f --- /dev/null +++ b/malboxes/profiles/snippets/postprocessor_vagrant.json @@ -0,0 +1,5 @@ + "post-processors": [{ + "type": "vagrant", + "output": "boxes/{{ profile_name }}.box", + "vagrantfile_template": "{{ dir }}/vagrantfiles/box_win.rb" + }] diff --git a/malboxes/profiles/snippets/tools.json b/malboxes/profiles/snippets/tools.json new file mode 100644 index 0000000..85f151f --- /dev/null +++ b/malboxes/profiles/snippets/tools.json @@ -0,0 +1,5 @@ + { + "type": "file", + "source": "{{ tools_path }}/", + "destination": "C:\\Tools" + } diff --git a/malboxes/profiles/win10_32_analyst.json b/malboxes/profiles/win10_32_analyst.json index 7c91391..53157e3 100644 --- a/malboxes/profiles/win10_32_analyst.json +++ b/malboxes/profiles/win10_32_analyst.json @@ -2,59 +2,43 @@ "variables": { "winrm_user": "vagrant", "winrm_pass": "vagrant", - "name": "win10_32_analyst", - "malboxes_dir": "{{ template_dir }}/../" + "name": "win10_32_analyst" }, "builders": [{ - "type": "virtualbox-iso", "guest_os_type": "Windows10", - "guest_additions_mode": "attach", - "headless": "false", - "communicator": "winrm", - "vboxmanage": [ - ["modifyvm", "{{.Name}}", "--memory", "4096"], - ["modifyvm", "{{.Name}}", "--cpus", "1"] - ], - - "disk_size": "15360", - - "output_directory": "builds", + {% include 'snippets/builder_virtualbox_windows.json' %}, "iso_urls": [ - "file://{{ user `iso_path` }}/10586.0.151029-1700.TH2_RELEASE_CLIENTENTERPRISEEVAL_OEMRET_X86FRE_EN-US.ISO", + "file://{{ iso_path }}/10586.0.151029-1700.TH2_RELEASE_CLIENTENTERPRISEEVAL_OEMRET_X86FRE_EN-US.ISO", "http://care.dlservice.microsoft.com/dl/download/B/B/3/BB3611B6-9781-437F-A293-AB43B85C2190/10586.0.151029-1700.TH2_RELEASE_CLIENTENTERPRISEEVAL_OEMRET_X86FRE_EN-US.ISO" ], "iso_checksum": "e431a4e259a5c056a5d58b9cd0628a3c59f112f9", "iso_checksum_type": "sha1", - "winrm_username": "{{user `winrm_user`}}", - "winrm_password": "{{user `winrm_pass`}}", - "winrm_timeout": "30m", - - "shutdown_command": "shutdown /s /f /t 10", - - "boot_wait": "10s", "floppy_files": [ - "{{user `malboxes_cache_dir`}}/Autounattend.xml", - "{{user `malboxes_dir`}}/installconfig/windows10/enablewinrm.ps1" + "{{ cache_dir }}/Autounattend.xml", + "{{ dir }}/installconfig/windows10/enablewinrm.ps1" ] }], - "post-processors": [{ - "type": "vagrant", - "output": "boxes/{{user `name`}}.box", - "vagrantfile_template": "{{user `malboxes_dir`}}/vagrantfiles/box_win.rb" - }], + {% include 'snippets/postprocessor_vagrant.json' %}, "provisioners": [{ "type": "powershell", "scripts": [ - "{{user `malboxes_dir`}}/scripts/windows/vmtools.ps1", - "{{user `malboxes_dir`}}/scripts/windows/enablerdp.ps1", - "{{user `malboxes_dir`}}/scripts/windows/installtools.ps1" + "{{ dir }}/scripts/windows/vmtools.ps1", + "{{ dir }}/scripts/windows/malware_analysis.ps1", + "{{ dir }}/scripts/windows/installtools.ps1" ] - }] + } + {% if tools_path %}, + {% include 'snippets/tools.json' %} + {% endif %} + {% if ida_path %}, + {% include 'snippets/ida_remote_32.json' %} + {% endif %} + ] } diff --git a/malboxes/profiles/win10_64_analyst.json b/malboxes/profiles/win10_64_analyst.json index 2c485eb..6525d32 100644 --- a/malboxes/profiles/win10_64_analyst.json +++ b/malboxes/profiles/win10_64_analyst.json @@ -2,60 +2,42 @@ "variables": { "winrm_user": "vagrant", "winrm_pass": "vagrant", - "name": "win10_64_analyst", - "malboxes_dir": "{{ template_dir }}/../" + "name": "win10_64_analyst" }, "builders": [{ - "type": "virtualbox-iso", "guest_os_type": "Windows10_64", - "guest_additions_mode": "attach", - "headless": "false", - "communicator": "winrm", - - "vboxmanage": [ - ["modifyvm", "{{.Name}}", "--memory", "4096"], - ["modifyvm", "{{.Name}}", "--cpus", "1"] - ], - - "disk_size": "15360", - - "output_directory": "builds", - + {% include 'snippets/builder_virtualbox_windows.json' %}, "iso_urls": [ - "file://{{ user `iso_path` }}/10240.16384.150709-1700.TH1_CLIENTENTERPRISEEVAL_OEMRET_X64FRE_EN-US.ISO", + "file://{{ iso_path }}/10240.16384.150709-1700.TH1_CLIENTENTERPRISEEVAL_OEMRET_X64FRE_EN-US.ISO", "http://care.dlservice.microsoft.com/dl/download/C/3/9/C399EEA8-135D-4207-92C9-6AAB3259F6EF/10240.16384.150709-1700.TH1_CLIENTENTERPRISEEVAL_OEMRET_X64FRE_EN-US.ISO" ], "iso_checksum": "56ab095075be28a90bc0b510835280975c6bb2ce", "iso_checksum_type": "sha1", - "winrm_username": "{{user `winrm_user`}}", - "winrm_password": "{{user `winrm_pass`}}", - "winrm_timeout": "30m", - - "shutdown_command": "shutdown /s /f /t 10", - - "boot_wait": "10s", - "floppy_files": [ - "{{user `malboxes_cache_dir`}}/Autounattend.xml", - "{{user `malboxes_dir`}}/installconfig/windows10_64/enablewinrm.ps1" + "{{ cache_dir }}/Autounattend.xml", + "{{ dir }}/installconfig/windows10_64/enablewinrm.ps1" ] }], - "post-processors": [{ - "type": "vagrant", - "output": "boxes/{{user `name`}}.box", - "vagrantfile_template": "{{user `malboxes_dir`}}/vagrantfiles/box_win.rb" - }], + {% include 'snippets/postprocessor_vagrant.json' %}, "provisioners": [{ "type": "powershell", "scripts": [ - "{{user `malboxes_dir`}}/scripts/windows/vmtools.ps1", - "{{user `malboxes_dir`}}/scripts/windows/enablerdp.ps1", - "{{user `malboxes_dir`}}/scripts/windows/installtools.ps1" + "{{ dir }}/scripts/windows/vmtools.ps1", + "{{ dir }}/scripts/windows/malware_analysis.ps1", + "{{ dir }}/scripts/windows/installtools.ps1" ] - }] + } + {% if tools_path %}, + {% include 'snippets/tools.json' %} + {% endif %} + {% if ida_path %}, + {% include 'snippets/ida_remote_64.json' %}, + {% include 'snippets/ida_remote_32.json' %} + {% endif %} + ] } diff --git a/malboxes/profiles/win7_32_analyst.json b/malboxes/profiles/win7_32_analyst.json index 3ee60d8..a9e2c5a 100644 --- a/malboxes/profiles/win7_32_analyst.json +++ b/malboxes/profiles/win7_32_analyst.json @@ -2,56 +2,38 @@ "variables": { "winrm_user": "vagrant", "winrm_pass": "vagrant", - "name": "win7_32_analyst", - "malboxes_dir": "{{ template_dir }}/../" + "name": "win7_32_analyst" }, "builders": [{ - "type": "virtualbox-iso", "guest_os_type": "Windows7", - "guest_additions_mode": "attach", - "headless": "false", - "communicator": "winrm", + {% include 'snippets/builder_virtualbox_windows.json' %}, - "vboxmanage": [ - ["modifyvm", "{{.Name}}", "--memory", "4096"], - ["modifyvm", "{{.Name}}", "--cpus", "1"] - ], - - "disk_size": "15360", - - "output_directory": "builds", - - "iso_url": "file://{{ user `iso_path` }}/en_windows_7_professional_with_sp1_x86_dvd_u_677056.iso", + "iso_url": "file://{{ iso_path }}/en_windows_7_professional_with_sp1_x86_dvd_u_677056.iso", "iso_checksum": "d89937df3a9bc2ec1a1486195fd308cd3dade928", "iso_checksum_type": "sha1", - "winrm_username": "{{user `winrm_user`}}", - "winrm_password": "{{user `winrm_pass`}}", - "winrm_timeout": "30m", - - "shutdown_command": "shutdown /s /f /t 10", - - "boot_wait": "10s", - "floppy_files": [ - "{{user `malboxes_cache_dir`}}/Autounattend.xml", - "{{user `malboxes_dir`}}/installconfig/windows7/enablewinrm.ps1" + "{{ cache_dir }}/Autounattend.xml", + "{{ dir }}/installconfig/windows7/enablewinrm.ps1" ] }], - "post-processors": [{ - "type": "vagrant", - "output": "boxes/{{user `name`}}.box", - "vagrantfile_template": "{{user `malboxes_dir`}}/vagrantfiles/box_win.rb" - }], + {% include 'snippets/postprocessor_vagrant.json' %}, "provisioners": [{ "type": "powershell", "scripts": [ - "{{user `malboxes_dir`}}/scripts/windows/vmtools.ps1", - "{{user `malboxes_dir`}}/scripts/windows/enablerdp.ps1", - "{{user `malboxes_dir`}}/scripts/windows/installtools.ps1" + "{{ dir }}/scripts/windows/vmtools.ps1", + "{{ dir }}/scripts/windows/malware_analysis.ps1", + "{{ dir }}/scripts/windows/installtools.ps1" ] - }] + } + {% if tools_path %}, + {% include 'snippets/tools.json' %} + {% endif %} + {% if ida_path %}, + {% include 'snippets/ida_remote_32.json' %} + {% endif %} + ] } diff --git a/malboxes/scripts/windows/enablerdp.ps1 b/malboxes/scripts/windows/malware_analysis.ps1 similarity index 65% rename from malboxes/scripts/windows/enablerdp.ps1 rename to malboxes/scripts/windows/malware_analysis.ps1 index a2eebe2..4bcaa1d 100644 --- a/malboxes/scripts/windows/enablerdp.ps1 +++ b/malboxes/scripts/windows/malware_analysis.ps1 @@ -1,4 +1,8 @@ +# Enable RDP Set-ItemProperty -Path "HKLM:\System\CurrentControlSet\Control\Terminal Server" -Name "fDenyTSConnections" -Value 0 # The rule below doesn't work for Windows 7 # Enable-NetFirewallRule -DisplayGroup "Remote Desktop" netsh advfirewall firewall set rule group="remote desktop" new enable=Yes + +# IDA Remote Debugging +netsh advfirewall firewall add rule name="IDA Remote Debugging" dir=in action=allow protocol=TCP localport=23946 diff --git a/requirements.txt b/requirements.txt index 68e55ae..1da94a8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ appdirs Jinja2 +jsmin diff --git a/setup.py b/setup.py index 2fcbd3d..a1a2f09 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ def _prepare(): # unrelated to actual build # pip will install data_files in an odd location so I copy them in package at # build time - root_data_files = ['LICENSE', 'README.adoc', 'TODO.adoc', 'config-example.json'] + root_data_files = ['LICENSE', 'README.adoc', 'TODO.adoc', 'config-example.js'] for f in root_data_files: _tempfiles.append(shutil.copy(path.join(here, f), @@ -99,7 +99,7 @@ def _teardown(): # your project is installed. For an analysis of "install_requires" vs pip's # requirements files see: # https://packaging.python.org/en/latest/requirements.html - install_requires=['appdirs', 'Jinja2'], + install_requires=['appdirs', 'Jinja2', 'jsmin'], # List additional groups of dependencies here (e.g. development # dependencies). You can install these using the following syntax, diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/config_example_valid.sh b/tests/config_example_valid.sh new file mode 100755 index 0000000..38659f3 --- /dev/null +++ b/tests/config_example_valid.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +F=config-example.js +echo "Processing file $F"; +# minifying first to strip comments +python3 -m jsmin $F | python3 -m json.tool 1>/dev/null +if [[ $? -ne 0 ]]; then + echo "Badly formatted JSON file! Failing test..."; + exit 1; +else + echo "Properly formatted JSON file"; +fi diff --git a/tests/test_packer_templates.py b/tests/test_packer_templates.py new file mode 100755 index 0000000..7386ca6 --- /dev/null +++ b/tests/test_packer_templates.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +# +# This file is part of the Malboxes project. +# +# Malboxes - Vagrant box builder and config generator for malware analysis +# https://github.com/gosecure/malboxes +# +# Olivier Bilodeau +# Copyright (C) 2016 GoSecure Inc. +# All rights reserved. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +import glob +import json +import os +import re +import sys +import unittest + +from jinja2 import Environment, FileSystemLoader + +from malboxes.malboxes import load_config + +class PackerTemplateTestCase(unittest.TestCase): + + def setUp(self): + self.env = Environment(loader=FileSystemLoader('malboxes/profiles/'), + autoescape=False) + + def test_packer_template_rendering(self): + for profile in glob.glob("malboxes/profiles/*.json"): + print("Processing file {}".format(profile)) + + # process profile + profile_name = os.path.basename(profile) + config = load_config('config-example.js', + re.match('(.*).json$', profile_name).group(1)) + + try: + template = self.env.get_template(os.path.basename(profile_name)) + profile_json = template.render(config) # pylint: disable=no-member + print("Properly formatted Jinja2 template") + except: + print("Badly formatted Jinja2 template! Failing test...") + raise() + + # test if json is valid + try: + json.loads(profile_json) + print("Properly formatted JSON file") + except: + print("Badly formatted JSON file! Failing test...") + raise() + +if __name__ == '__main__': + unittest.main()