def analyze_sample(self, sample_path, workdir, outdir, start_command, timeout): analysis_info = dict() dns_server = self.config.config['drakrun'].get('dns_server', '8.8.8.8') drakmon_log_fp = os.path.join(outdir, "drakmon.log") with self.run_vm() as vm, \ graceful_exit(start_dnsmasq(self.instance_id, dns_server)), \ graceful_exit(start_tcpdump_collector(self.instance_id, outdir)), \ open(drakmon_log_fp, "wb") as drakmon_log: analysis_info[ 'snapshot_version'] = vm.backend.get_vm0_snapshot_time() kernel_profile = os.path.join(PROFILE_DIR, "kernel.json") self.log.info("Copying sample to VM...") injector = Injector(self.vm_name, self.runtime_info, kernel_profile) result = injector.write_file( sample_path, f"%USERPROFILE%\\Desktop\\{os.path.basename(sample_path)}") injected_fn = json.loads(result.stdout)['ProcessName'] if "%f" not in start_command: self.log.warning("No file name in start command") cur_start_command = start_command.replace("%f", injected_fn) # don't include our internal maintanance commands analysis_info['start_command'] = cur_start_command self.log.info("Using command: %s", cur_start_command) if self.net_enable: self.log.info("Setting up network...") injector.create_process("cmd /C ipconfig /renew >nul", wait=True, timeout=120) drakvuf_cmd = self.build_drakvuf_cmdline( timeout=timeout, cwd=subprocess.list2cmdline([ntpath.dirname(injected_fn)]), full_cmd=cur_start_command, dump_dir=os.path.join(outdir, "dumps"), ipt_dir=os.path.join(outdir, "ipt"), workdir=workdir, ) try: subprocess.run(drakvuf_cmd, stdout=drakmon_log, check=True, timeout=timeout + 60) except subprocess.TimeoutExpired: self.log.exception("DRAKVUF timeout expired") return analysis_info
def dnsmasq_start_test(): if not tool_exists("dnsmasq"): pytest.skip("dnsmasq does not exist") # stale dnsmasq will create issues with the stopping test dnsmasq_pids = Path("/var/run/").glob("dnsmasq-vm*.pid") for pid in dnsmasq_pids: subprocess.run(["pkill", "-F", str(pid)]) start_dnsmasq(10, "8.8.8.8", True) # wait up to 10 seconds for the pidfile to appear for _ in range(10): cmd = subprocess.run(["pgrep", "-F", "/var/run/dnsmasq-vm10.pid"]) # we can break, pidfile appeared if cmd.returncode == 0: break time.sleep(1.0) assert cmd.returncode == 0 # starting already stopped dnsmasq # what should be the expected behavior? start_dnsmasq(10, "8.8.8.8", True)
def main(): parser = argparse.ArgumentParser( description='DRAKVUF Sandbox interactive shell') parser.add_argument('vm_id', type=int, help='VM id you want to control') parser.add_argument('--dns', default='8.8.8.8') args = parser.parse_args() with graceful_exit(start_dnsmasq(args.vm_id, args.dns)), \ DrakmonShell(args.vm_id, args.dns) as shell: helpers = { 'copy': shell.copy, 'drakvuf': shell.drakvuf, 'vm': shell.vm } embed(banner='', user_ns=helpers, colors='neutral')
def main(): parser = argparse.ArgumentParser( description='DRAKVUF Sandbox interactive shell') parser.add_argument('vm_id', type=int, help='VM id you want to control') parser.add_argument('--dns', default='8.8.8.8') args = parser.parse_args() logging.basicConfig(level=logging.DEBUG, format='[%(asctime)s][%(levelname)s] %(message)s', handlers=[logging.StreamHandler()]) with graceful_exit(start_dnsmasq(args.vm_id, args.dns)), \ DrakmonShell(args.vm_id, args.dns) as shell: helpers = { 'copy': shell.copy, 'drakvuf': shell.drakvuf, 'vm': shell.vm } embed(banner='', user_ns=helpers, colors='neutral')
def test_dnsmasq_start(): start_dnsmasq(1, '8.8.8.8', True) try: subprocess.check_output('pgrep dnsmasq', shell=True) except subprocess.CalledProcessError as e: assert e.returncode == 0
def process(self): sample = self.current_task.get_resource("sample") self.log.info("hostname: {}".format(socket.gethostname())) sha256sum = hashlib.sha256(sample.content).hexdigest() magic_output = magic.from_buffer(sample.content) self.log.info("running sample sha256: {}".format(sha256sum)) timeout = self.current_task.payload.get( 'timeout') or self.default_timeout hard_time_limit = 60 * 20 if timeout > hard_time_limit: self.log.error( "Tried to run the analysis for more than hard limit of %d seconds", hard_time_limit) return self.log.info(f"analysis UID: {self.analysis_uid}") self.rs.set(f"drakvnc:{self.analysis_uid}", self.instance_id, ex=3600) # 1h workdir = f"/tmp/drakrun/{self.vm_name}" extension = self.current_task.headers.get("extension", "exe").lower() if '(DLL)' in magic_output: extension = 'dll' self.log.info("Running file as %s", extension) file_name = self.current_task.payload.get("file_name", "malwar") + f".{extension}" # Alphanumeric, dot, underscore, dash if not re.match(r"^[a-zA-Z0-9\._\-]+$", file_name): self.log.error("Filename contains invalid characters") return self.log.info("Using file name %s", file_name) # Save sample to disk here as some branches of _get_start_command require file path. try: shutil.rmtree(workdir) except Exception as e: print(e) os.makedirs(workdir, exist_ok=True) with open(os.path.join(workdir, file_name), 'wb') as f: f.write(sample.content) start_command = self.current_task.payload.get( "start_command", self._get_start_command(extension, sample, os.path.join(workdir, file_name))) if not start_command: self.log.error( "Unable to run malware sample, could not generate any suitable command to run it." ) return outdir = os.path.join(workdir, 'output') os.mkdir(outdir) os.mkdir(os.path.join(outdir, 'dumps')) os.mkdir(os.path.join(outdir, 'ipt')) metadata = { "sample_sha256": sha256sum, "magic_output": magic_output, "time_started": int(time.time()) } with open(os.path.join(outdir, 'sample_sha256.txt'), 'w') as f: f.write(hashlib.sha256(sample.content).hexdigest()) watcher_tcpdump = None watcher_dnsmasq = None for _ in range(3): try: self.log.info("Running VM {}".format(self.instance_id)) watcher_dnsmasq = start_dnsmasq( self.instance_id, self.config.config['drakrun'].get('dns_server', '8.8.8.8')) snapshot_version = self.run_vm() metadata['snapshot_version'] = snapshot_version watcher_tcpdump = start_tcpdump_collector( self.instance_id, outdir) self.log.info("running monitor {}".format(self.instance_id)) hooks_list = os.path.join(ETC_DIR, "hooks.txt") kernel_profile = os.path.join(PROFILE_DIR, "kernel.json") dump_dir = os.path.join(outdir, "dumps") ipt_dir = os.path.join(outdir, "ipt") drakmon_log_fp = os.path.join(outdir, "drakmon.log") self.log.info("Copying sample to VM...") injector = Injector(self.vm_name, self.runtime_info, kernel_profile) result = injector.write_file( os.path.join(workdir, file_name), f"%USERPROFILE%\\Desktop\\{file_name}") injected_fn = json.loads(result.stdout)['ProcessName'] net_enable = int(self.config.config['drakrun'].get( 'net_enable', '0')) if "%f" not in start_command: self.log.warning("No file name in start command") cwd = subprocess.list2cmdline([ntpath.dirname(injected_fn)]) cur_start_command = start_command.replace("%f", injected_fn) # don't include our internal maintanance commands metadata['start_command'] = cur_start_command if net_enable: self.log.info("Setting up network...") injector.create_process("cmd /C ipconfig /renew >nul", wait=True, timeout=120) full_cmd = cur_start_command self.log.info("Using command: %s", full_cmd) task_quality = self.current_task.headers.get("quality", "high") drakvuf_cmd = ["drakvuf"] + self.generate_plugin_cmdline(task_quality) + \ ["-o", "json" "-j", "5", "-t", str(timeout), "-i", str(self.runtime_info.inject_pid), "-k", hex(self.runtime_info.vmi_offsets.kpgd), "-d", self.vm_name, "--dll-hooks-list", hooks_list, "--memdump-dir", dump_dir, "-r", kernel_profile, "-e", full_cmd, "-c", cwd] if self.config.config['drakrun'].getboolean('enable_ipt', fallback=False): drakvuf_cmd.extend(["--ipt-dir", ipt_dir]) drakvuf_cmd.extend(self.get_profile_list()) syscall_filter = self.config.config['drakrun'].get( 'syscall_filter', None) if syscall_filter: drakvuf_cmd.extend(["-S", syscall_filter]) with open(drakmon_log_fp, "wb") as drakmon_log: drakvuf = subprocess.Popen(drakvuf_cmd, stdout=drakmon_log) try: exit_code = drakvuf.wait(timeout + 60) except subprocess.TimeoutExpired as e: logging.error( "BUG: Monitor command doesn\'t terminate automatically after timeout expires." ) logging.error("Trying to terminate DRAKVUF...") drakvuf.terminate() drakvuf.wait(10) logging.error( "BUG: Monitor command also doesn\'t terminate after sending SIGTERM." ) drakvuf.kill() drakvuf.wait() logging.error("Monitor command was forcefully killed.") raise e if exit_code != 0: raise subprocess.CalledProcessError( exit_code, drakvuf_cmd) break except subprocess.CalledProcessError: self.log.info("Something went wrong with the VM {}".format( self.instance_id), exc_info=True) finally: try: subprocess.run(["xl", "destroy", self.vm_name], cwd=workdir, check=True) except subprocess.CalledProcessError: self.log.info("Failed to destroy VM {}".format( self.instance_id), exc_info=True) if watcher_dnsmasq: watcher_dnsmasq.terminate() else: self.log.info( "Failed to analyze sample after 3 retries, giving up.") return self.log.info("waiting for tcpdump to exit") if watcher_tcpdump: try: watcher_tcpdump.wait(timeout=60) except subprocess.TimeoutExpired: self.log.exception("tcpdump doesn't exit cleanly after 60s") self.crop_dumps(os.path.join(outdir, 'dumps'), os.path.join(outdir, 'dumps.zip')) if self.config.config['drakrun'].getboolean('enable_ipt', fallback=False): self.compress_ipt(os.path.join(outdir, 'ipt'), os.path.join(outdir, 'ipt.zip')) self.log.info("uploading artifacts") metadata['time_finished'] = int(time.time()) with open(os.path.join(outdir, 'metadata.json'), 'w') as f: f.write(json.dumps(metadata)) payload = {"analysis_uid": self.analysis_uid} payload.update(metadata) headers = dict(self.headers) headers["quality"] = self.current_task.headers.get("quality", "high") t = Task(headers, payload=payload) for resource in self.upload_artifacts(self.analysis_uid, workdir): t.add_payload(resource.name, resource) t.add_payload('sample', sample) self.send_task(t)
def analyze_sample(self, sample_path, workdir, outdir, start_command, timeout): analysis_info = dict() dns_server = self.config.config["drakrun"].get("dns_server", "8.8.8.8") drakmon_log_fp = os.path.join(outdir, "drakmon.log") with self.run_vm() as vm, graceful_exit( start_dnsmasq(self.instance_id, dns_server)), graceful_exit( start_tcpdump_collector(self.instance_id, outdir)), open( drakmon_log_fp, "wb") as drakmon_log: analysis_info[ "snapshot_version"] = vm.backend.get_vm0_snapshot_time() kernel_profile = os.path.join(PROFILE_DIR, "kernel.json") self.log.info("Copying sample to VM...") injector = Injector(self.vm_name, self.runtime_info, kernel_profile) result = injector.write_file( sample_path, f"%USERPROFILE%\\Desktop\\{os.path.basename(sample_path)}") try: injected_fn = json.loads(result.stdout)["ProcessName"] except ValueError as e: self.log.error( "JSON decode error occurred when tried to parse injector's logs." ) self.log.error(f"Raw log line: {result.stdout}") raise e # don't include our internal maintanance commands start_command = start_command.replace("%f", injected_fn) analysis_info["start_command"] = start_command self.log.info("Using command: %s", start_command) if self.net_enable: self.log.info("Setting up network...") injector.create_process("cmd /C ipconfig /renew >nul", wait=True, timeout=120) task_quality = self.current_task.headers.get("quality", "high") requested_plugins = self.current_task.payload.get( "plugins", self.active_plugins["_all_"]) analysis_info["plugins"] = self.get_plugin_list( task_quality, requested_plugins) drakvuf_cmd = self.build_drakvuf_cmdline( timeout=timeout, cwd=subprocess.list2cmdline([ntpath.dirname(injected_fn)]), full_cmd=start_command, dump_dir=os.path.join(outdir, "dumps"), ipt_dir=os.path.join(outdir, "ipt"), workdir=workdir, enabled_plugins=analysis_info["plugins"], ) try: subprocess.run(drakvuf_cmd, stdout=drakmon_log, check=True, timeout=timeout + 60) except subprocess.CalledProcessError as e: # see DRAKVUF src/exitcodes.h for more details INJECTION_UNSUCCESSFUL = 4 if e.returncode == INJECTION_UNSUCCESSFUL: self.log_startup_failure(drakmon_log_fp) raise e except subprocess.TimeoutExpired as e: self.log.exception("DRAKVUF timeout expired") raise e return analysis_info
def install(vcpus, memory, storage_backend, disk_size, iso_path, zfs_tank_name, lvm_volume_group, unattended_xml): if not check_root(): return if storage_backend == "lvm" and lvm_volume_group is None: raise Exception("lvm storage backend requires --lvm-volume-group") if storage_backend == "zfs" and zfs_tank_name is None: raise Exception("zfs storage backend requires --zfs-tank-name") if not sanity_check(): logging.error("Sanity check failed.") return stop_all_drakruns() logging.info("Performing installation...") if vcpus < 1: logging.error("Your VM must have at least 1 vCPU.") return if memory < 512: logging.error("Your VM must have at least 512 MB RAM.") return if memory < 1536: logging.warning( "Using less than 1.5 GB RAM per VM is not recommended for any supported system." ) if unattended_xml: logging.info("Baking unattended.iso for automated installation") with tempfile.TemporaryDirectory() as tmpdir: tmp_xml_path = os.path.join(tmpdir, 'autounattend.xml') with open(tmp_xml_path, 'wb') as fw: with open(unattended_xml, 'rb') as fr: fw.write(fr.read()) try: subprocess.check_output([ 'genisoimage', '-o', os.path.join(VOLUME_DIR, "unattended.iso"), '-J', '-r', tmp_xml_path ], stderr=subprocess.STDOUT) except subprocess.CalledProcessError: logging.exception("Failed to generate unattended.iso.") sha256_hash = hashlib.sha256() logging.info("Calculating hash of iso") iso_file_size = os.stat(iso_path).st_size block_size = 128 * 1024 with tqdm(total=iso_file_size, unit_scale=True) as pbar: with open(iso_path, "rb") as f: for byte_block in iter(lambda: f.read(block_size), b""): pbar.update(block_size) sha256_hash.update(byte_block) iso_sha256 = sha256_hash.hexdigest() install_info = InstallInfo(vcpus=vcpus, memory=memory, storage_backend=storage_backend, disk_size=disk_size, iso_path=os.path.abspath(iso_path), zfs_tank_name=zfs_tank_name, lvm_volume_group=lvm_volume_group, enable_unattended=unattended_xml is not None, iso_sha256=iso_sha256) install_info.save() try: subprocess.check_output('xl uptime vm-0', shell=True, stderr=subprocess.STDOUT) except subprocess.CalledProcessError: pass else: logging.info('Detected that vm-0 is already running, stopping it.') subprocess.run('xl destroy vm-0', shell=True, check=True) generate_vm_conf(install_info, 0) backend = get_storage_backend(install_info) backend.initialize_vm0_volume(disk_size) try: subprocess.check_output("brctl show", shell=True) except subprocess.CalledProcessError: logging.exception( "Failed to execute brctl show. Make sure you have bridge-utils installed." ) return net_enable = conf['drakrun'].getboolean('net_enable', fallback=False) out_interface = conf['drakrun'].get('out_interface', '') dns_server = conf['drakrun'].get('dns_server', '') setup_vm_network(vm_id=0, net_enable=net_enable, out_interface=out_interface, dns_server=dns_server) if net_enable: start_dnsmasq(vm_id=0, dns_server=dns_server, background=True) cfg_path = os.path.join(VM_CONFIG_DIR, "vm-0.cfg") try: subprocess.run('xl create {}'.format(shlex.quote(cfg_path)), shell=True, check=True) except subprocess.CalledProcessError: logging.exception("Failed to launch VM vm-0") return logging.info("-" * 80) logging.info("Initial VM setup is complete and the vm-0 was launched.") logging.info( "Please now VNC to the port 5900 on this machine to perform Windows installation." ) logging.info( "After you have installed Windows and booted it to the desktop, please execute:" ) logging.info("# draksetup postinstall") with open(cfg_path, "r") as f: data = f.read() m = re.search(r'vncpasswd[ ]*=(.*)', data) if m: passwd = m.group(1).strip() if passwd[0] == '"' and passwd[-1] == '"': passwd = passwd[1:-1] logging.info("Your configured VNC password is:") logging.info(passwd) logging.info( "Please note that on some machines, system installer may boot for up to 10 minutes" ) logging.info( "and may look unresponsive during the process. Please be patient.") logging.info("-" * 80)
def snapshot_import(name, bucket, full, zpool): local_install = InstallInfo.try_load() if local_install is not None: click.confirm("Detected local snapshot. It will be REMOVED. Continue?", abort=True) mc = get_minio_client(conf) if not mc.bucket_exists(bucket): logging.error("Bucket %s doesn't exist", bucket) return ensure_dirs() try: if full: logging.warning( "Importing full snapshot. This may not work if hardware is different" ) do_import_full(mc, name, bucket, zpool) else: do_import_minimal(mc, name, bucket, zpool) # This could probably use some refactoring # We're duplicating quite a lot of code from install function install_info = InstallInfo.load() generate_vm_conf(install_info, 0) backend = get_storage_backend(install_info) backend.rollback_vm_storage(0) net_enable = int(conf["drakrun"].get("net_enable", "0")) out_interface = conf["drakrun"].get("out_interface", "") dns_server = conf["drakrun"].get("dns_server", "") setup_vm_network( vm_id=0, net_enable=net_enable, out_interface=out_interface, dns_server=dns_server, ) if net_enable: start_dnsmasq(vm_id=0, dns_server=dns_server, background=True) cfg_path = os.path.join(VM_CONFIG_DIR, "vm-0.cfg") try: subprocess.run(["xl" "create", cfg_path], check=True) except subprocess.CalledProcessError: logging.exception("Failed to launch VM vm-0") return logging.info( "Minimal snapshots require postinstall to work correctly") logging.info( "Please VNC to the port 5900 to ensure the OS booted correctly" ) logging.info( "After that, execute this command to finish the setup") logging.info("# draksetup postinstall") except NoSuchKey: logging.error("Import failed. Missing files in bucket.")
def install(storage_backend, disk_size, iso_path, zfs_tank_name, unattended_xml): logging.info("Ensuring that drakrun@* services are stopped...") subprocess.check_output('systemctl stop \'drakrun@*\'', shell=True, stderr=subprocess.STDOUT) logging.info("Performing installation...") if unattended_xml: logging.info("Baking unattended.iso for automated installation") with tempfile.TemporaryDirectory() as tmpdir: tmp_xml_path = os.path.join(tmpdir, 'autounattend.xml') with open(tmp_xml_path, 'wb') as fw: with open(unattended_xml, 'rb') as fr: fw.write(fr.read()) try: subprocess.check_output([ 'genisoimage', '-o', os.path.join(VOLUME_DIR, "unattended.iso"), '-J', '-r', tmp_xml_path ], stderr=subprocess.STDOUT) except subprocess.CalledProcessError: logging.exception("Failed to generate unattended.iso.") sha256_hash = hashlib.sha256() with open(iso_path, "rb") as f: for byte_block in iter(lambda: f.read(4096), b""): sha256_hash.update(byte_block) iso_sha256 = sha256_hash.hexdigest() install_info = InstallInfo(storage_backend=storage_backend, disk_size=disk_size, iso_path=os.path.abspath(iso_path), zfs_tank_name=zfs_tank_name, enable_unattended=unattended_xml is not None, iso_sha256=iso_sha256) install_info.save() logging.info("Checking xen-detect...") proc = subprocess.run('xen-detect -N', shell=True) if proc.returncode != 1: logging.error( 'It looks like the system is not running on Xen. Please reboot your machine into Xen hypervisor.' ) return logging.info("Testing if xl tool is sane...") try: subprocess.check_output('xl info', shell=True, stderr=subprocess.STDOUT) except subprocess.CalledProcessError: logging.exception("Failed to test xl command.") return try: subprocess.check_output('xl uptime vm-0', shell=True, stderr=subprocess.STDOUT) except subprocess.CalledProcessError: pass else: logging.info('Detected that vm-0 is already running, stopping it.') subprocess.run('xl destroy vm-0', shell=True, check=True) generate_vm_conf(install_info, 0) backend = get_storage_backend(install_info) backend.initialize_vm0_volume(disk_size) try: subprocess.check_output("brctl show", shell=True) except subprocess.CalledProcessError: logging.exception( "Failed to execute brctl show. Make sure you have bridge-utils installed." ) return net_enable = int(conf['drakrun'].get('net_enable', '0')) out_interface = conf['drakrun'].get('out_interface', '') dns_server = conf['drakrun'].get('dns_server', '') setup_vm_network(vm_id=0, net_enable=net_enable, out_interface=out_interface, dns_server=dns_server) if net_enable: start_dnsmasq(vm_id=0, dns_server=dns_server, background=True) cfg_path = os.path.join(VM_CONFIG_DIR, "vm-0.cfg") try: subprocess.run('xl create {}'.format(shlex.quote(cfg_path)), shell=True, check=True) except subprocess.CalledProcessError: logging.exception("Failed to launch VM vm-0") return logging.info("-" * 80) logging.info("Initial VM setup is complete and the vm-0 was launched.") logging.info( "Please now VNC to the port 5900 on this machine to perform Windows installation." ) logging.info( "After you have installed Windows and booted it to the desktop, please execute:" ) logging.info("# draksetup postinstall") with open(cfg_path, "r") as f: data = f.read() m = re.search(r'vncpasswd[ ]*=(.*)', data) if m: passwd = m.group(1).strip() if passwd[0] == '"' and passwd[-1] == '"': passwd = passwd[1:-1] logging.info("Your configured VNC password is:") logging.info(passwd) logging.info( "Please note that on some machines, system installer may boot for up to 10 minutes" ) logging.info( "and may look unresponsive during the process. Please be patient.") logging.info("-" * 80)