Exemplo n.º 1
0
def create_rekall_profiles(injector: Injector):
    tmp = None

    for file in dll_file_list:
        try:
            logging.info(f"Fetching rekall profile for {file.path}")

            local_dll_path = os.path.join(PROFILE_DIR, file.dest)
            guest_dll_path = str(PureWindowsPath("C:/", file.path))

            cmd = injector.read_file(guest_dll_path, local_dll_path)
            out = json.loads(cmd.stdout.decode())
            if out["Status"] == "Error" and out["Error"] in [
                    "ERROR_FILE_NOT_FOUND", "ERROR_PATH_NOT_FOUND"
            ]:
                raise FileNotFoundError
            if out["Status"] != "Success":
                logging.debug("stderr: " + cmd.stderr.decode())
                logging.debug(out)
                # Take care if the error message is changed
                raise Exception("Some error occurred in injector")

            guid = pdb_guid(local_dll_path)
            tmp = fetch_pdb(guid["filename"], guid["GUID"], PROFILE_DIR)

            logging.debug("Parsing PDB into JSON profile...")
            profile = make_pdb_profile(tmp)
            with open(os.path.join(PROFILE_DIR, f"{file.dest}.json"),
                      'w') as f:
                f.write(profile)
        except json.JSONDecodeError:
            logging.debug(f"stdout: {cmd.stdout}")
            logging.debug(f"stderr: {cmd.stderr}")
            logging.debug(traceback.format_exc())
            raise Exception(f"Failed to parse json response on {file.path}")
        except FileNotFoundError:
            logging.warning(f"Failed to copy file {file.path}, skipping...")
        except RuntimeError:
            logging.warning(
                f"Failed to fetch profile for {file.path}, skipping...")
        except Exception as e:
            # Take care if the error message is changed
            if str(e) == "Some error occurred in injector":
                raise
            else:
                logging.warning(
                    f"Unexpected exception while creating rekall profile for {file.path}, skipping..."
                )
                # Can help in debugging
                logging.debug("stderr: " + cmd.stderr.decode())
                logging.debug(out)
                logging.debug(traceback.format_exc())
        finally:
            safe_delete(local_dll_path)
            # was crashing here if the first file reached some exception
            if tmp is not None:
                safe_delete(os.path.join(PROFILE_DIR, tmp))
Exemplo n.º 2
0
def setup(monkeysession):
    if not tool_exists("lvs"):
        pytest.skip("LVM is not found")

    temp_file_name = tempfile.NamedTemporaryFile(delete=False).name
    subprocess.run(
        ["dd", "if=/dev/zero", f"of={temp_file_name}", "bs=1M", "count=100"],
        stderr=subprocess.STDOUT,
        check=True,
    )
    loopback_file = (
        subprocess.check_output(
            ["losetup", "-f", "--show", temp_file_name], stderr=subprocess.STDOUT
        )
        .decode()
        .strip("\n")
    )

    # v is being added in start to ensure the lvm volume group starts with a letter
    # don't know if it is required or not
    volume_group = "v" + "".join(
        secrets.choice(string.ascii_letters + string.digits) for i in range(5)
    )

    # pvcreate is automatically used by this internally
    subprocess.check_output(
        ["vgcreate", volume_group, loopback_file], stderr=subprocess.STDOUT
    )

    def install_patch():
        return InstallInfo(
            vcpus=1,
            memory=512,
            storage_backend="lvm",
            disk_size="25M",
            iso_path=None,  # not being required
            zfs_tank_name=None,
            lvm_volume_group=volume_group,
            enable_unattended=None,
            iso_sha256=None,
        )

    monkeysession.setattr(InstallInfo, "load", install_patch)
    monkeysession.setattr(InstallInfo, "try_load", install_patch)

    yield

    subprocess.run(["vgchange", "-an", volume_group], stderr=subprocess.STDOUT)
    subprocess.run(["vgremove", "-y", volume_group], stderr=subprocess.STDOUT)
    subprocess.run(["losetup", "-d", loopback_file], stderr=subprocess.STDOUT)
    safe_delete(temp_file_name)
Exemplo n.º 3
0
def config():
    tmpf = tempfile.NamedTemporaryFile(delete=False).name
    module_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                              "..")
    cfg_path = os.path.join(module_dir, "tools", "test-hvm64-example.cfg")
    firmware_path = os.path.join(module_dir, "tools", "test-hvm64-example")

    with open(cfg_path, "r") as f:
        test_cfg = (f.read().replace("{{ FIRMWARE_PATH }}",
                                     firmware_path).encode("utf-8"))

    with open(tmpf, "wb") as f:
        f.write(test_cfg)

    yield tmpf
    safe_delete(tmpf)
Exemplo n.º 4
0
def setup(monkeysession):
    if not tool_exists('lvs'):
        pytest.skip("LVM is not found")

    temp_file_name = tempfile.NamedTemporaryFile(delete=False).name
    subprocess.run(
        ['dd', 'if=/dev/zero', f'of={temp_file_name}', 'bs=1M', 'count=100'],
        stderr=subprocess.STDOUT,
        check=True)
    loopback_file = subprocess.check_output(
        ['losetup', '-f', '--show', temp_file_name],
        stderr=subprocess.STDOUT).decode().strip('\n')

    # v is being added in start to ensure the lvm volume group starts with a letter
    # don't know if it is required or not
    volume_group = 'v' + ''.join(
        secrets.choice(string.ascii_letters + string.digits) for i in range(5))

    # pvcreate is automatically used by this internally
    subprocess.check_output(['vgcreate', volume_group, loopback_file],
                            stderr=subprocess.STDOUT)

    def install_patch():
        return InstallInfo(
            vcpus=1,
            memory=512,
            storage_backend='lvm',
            disk_size='25M',
            iso_path=None,  # not being required
            zfs_tank_name=None,
            lvm_volume_group=volume_group,
            enable_unattended=None,
            iso_sha256=None)

    monkeysession.setattr(InstallInfo, "load", install_patch)
    monkeysession.setattr(InstallInfo, "try_load", install_patch)

    yield

    subprocess.run(['vgchange', '-an', volume_group], stderr=subprocess.STDOUT)
    subprocess.run(['vgremove', '-y', volume_group], stderr=subprocess.STDOUT)
    subprocess.run(['losetup', '-d', loopback_file], stderr=subprocess.STDOUT)
    safe_delete(temp_file_name)
Exemplo n.º 5
0
def cleanup():
    if not check_root():
        return

    install_info = InstallInfo.try_load()

    if install_info is None:
        logging.error("The cleanup has been performed")
        return

    stop_all_drakruns()

    backend = get_storage_backend(install_info)
    vm_ids = get_all_vm_conf()

    net_enable = int(conf["drakrun"].get("net_enable", "0"))
    out_interface = conf["drakrun"].get("out_interface", "")
    dns_server = conf["drakrun"].get("dns_server", "")

    for vm_id in vm_ids:
        vm = VirtualMachine(backend, vm_id)
        vm.destroy()

        delete_vm_network(
            vm_id=vm_id,
            net_enable=net_enable,
            out_interface=out_interface,
            dns_server=dns_server,
        )
        if net_enable:
            stop_dnsmasq(vm_id=vm_id)

        backend.delete_vm_volume(vm_id)

        delete_vm_conf(vm_id)

    safe_delete(os.path.join(VOLUME_DIR, "snapshot.sav"))
    cleanup_postinstall_files()

    InstallInfo.delete()
Exemplo n.º 6
0
 def delete():
     if not safe_delete(InstallInfo._INSTALL_FILE_PATH):
         raise Exception("install.json not deleted")
Exemplo n.º 7
0
 def delete_vm_volume(self, vm_id: str):
     # unmount can be done here
     disk_path = os.path.join(VOLUME_DIR, f"vm-{vm_id}.img")
     if not safe_delete(disk_path):
         raise Exception(f"Couldn't delete vm-{vm_id}.img")
Exemplo n.º 8
0
def delete_vm_conf(vm_id: int) -> bool:
    return safe_delete(os.path.join(VM_CONFIG_DIR, f"vm-{vm_id}.cfg"))
Exemplo n.º 9
0
def snapshot_file():
    tmpf = tempfile.NamedTemporaryFile(delete=False).name
    yield tmpf
    safe_delete(tmpf)
Exemplo n.º 10
0
def cleanup_postinstall_files():
    for profile in os.listdir(PROFILE_DIR):
        safe_delete(os.path.join(PROFILE_DIR, profile))
Exemplo n.º 11
0
def postinstall(report, generate_usermode):
    if not check_root():
        return

    if os.path.exists(os.path.join(ETC_DIR, "no_usage_reports")):
        report = False

    install_info = InstallInfo.load()
    storage_backend = get_storage_backend(install_info)

    vm = VirtualMachine(storage_backend, 0)

    if vm.is_running is False:
        logging.exception("vm-0 is not running")
        return

    logging.info("Cleaning up leftovers(if any)")
    cleanup_postinstall_files()

    logging.info("Ejecting installation CDs")
    eject_cd("vm-0", FIRST_CDROM_DRIVE)
    if install_info.enable_unattended:
        # If unattended install is enabled, we have an additional CD-ROM drive
        eject_cd("vm-0", SECOND_CDROM_DRIVE)

    output = subprocess.check_output(['vmi-win-guid', 'name', 'vm-0'],
                                     timeout=30).decode('utf-8')

    try:
        version = re.search(r'Version: (.*)', output).group(1)
        pdb = re.search(r'PDB GUID: ([0-9a-f]+)', output).group(1)
        fn = re.search(r'Kernel filename: ([a-z]+\.[a-z]+)', output).group(1)
    except AttributeError:
        logging.error("Failed to obtain kernel PDB GUID/Kernel filename.")
        return

    logging.info("Determined PDB GUID: {}".format(pdb))
    logging.info("Determined kernel filename: {}".format(fn))

    logging.info("Fetching PDB file...")
    dest = fetch_pdb(fn, pdb, destdir=PROFILE_DIR)

    logging.info("Generating profile out of PDB file...")
    profile = make_pdb_profile(dest)

    logging.info("Saving profile...")
    kernel_profile = os.path.join(PROFILE_DIR, 'kernel.json')
    with open(kernel_profile, 'w') as f:
        f.write(profile)

    safe_delete(dest)

    vmi_offsets = extract_vmi_offsets('vm-0', kernel_profile)
    explorer_pid = extract_explorer_pid('vm-0', kernel_profile, vmi_offsets)
    runtime_info = RuntimeInfo(vmi_offsets=vmi_offsets,
                               inject_pid=explorer_pid)

    logging.info("Saving runtime profile...")
    with open(os.path.join(PROFILE_DIR, 'runtime.json'), 'w') as f:
        f.write(runtime_info.to_json(indent=4))

    logging.info("Saving VM snapshot...")

    # snapshot domain but don't destroy it, leave it in paused state
    subprocess.check_output('xl save -p vm-0 ' +
                            os.path.join(VOLUME_DIR, "snapshot.sav"),
                            shell=True)
    logging.info("Snapshot was saved succesfully.")

    logging.info("Snapshotting persistent memory...")
    storage_backend.snapshot_vm0_volume()

    logging.info("Unpausing VM")
    subprocess.check_output('xl unpause vm-0', shell=True)

    injector = Injector('vm-0', runtime_info, kernel_profile)
    if generate_usermode:
        try:
            for file in dll_file_list:
                create_rekall_profile(injector, file)
        except RuntimeError as e:
            logging.warning("Generating usermode profiles failed")
            logging.exception(e)

    subprocess.check_output('xl destroy vm-0', shell=True)

    if report:
        send_usage_report({
            "kernel": {
                "guid": pdb,
                "filename": fn,
                "version": version
            },
            "install_iso": {
                "sha256": install_info.iso_sha256
            }
        })

    logging.info("All right, drakrun setup is done.")
    logging.info("First instance of drakrun will be enabled automatically...")
    subprocess.check_output('systemctl enable drakrun@1', shell=True)
    subprocess.check_output('systemctl start drakrun@1', shell=True)

    logging.info("If you want to have more parallel instances, execute:")
    logging.info("  # draksetup scale <number of instances>")
Exemplo n.º 12
0
def postinstall(report, generate_usermode):
    if not check_root():
        return

    if os.path.exists(os.path.join(ETC_DIR, "no_usage_reports")):
        report = False

    install_info = InstallInfo.load()
    storage_backend = get_storage_backend(install_info)

    vm0 = VirtualMachine(storage_backend, 0)

    if vm0.is_running is False:
        logging.exception("vm-0 is not running")
        return

    logging.info("Cleaning up leftovers(if any)")
    cleanup_postinstall_files()

    logging.info("Ejecting installation CDs")
    eject_cd("vm-0", FIRST_CDROM_DRIVE)
    if install_info.enable_unattended:
        # If unattended install is enabled, we have an additional CD-ROM drive
        eject_cd("vm-0", SECOND_CDROM_DRIVE)

    kernel_info = vmi_win_guid("vm-0")

    logging.info(f"Determined PDB GUID: {kernel_info.guid}")
    logging.info(f"Determined kernel filename: {kernel_info.filename}")

    logging.info("Fetching PDB file...")
    dest = fetch_pdb(kernel_info.filename,
                     kernel_info.guid,
                     destdir=PROFILE_DIR)

    logging.info("Generating profile out of PDB file...")
    profile = make_pdb_profile(dest)

    logging.info("Saving profile...")
    kernel_profile = os.path.join(PROFILE_DIR, "kernel.json")
    with open(kernel_profile, "w") as f:
        f.write(profile)

    safe_delete(dest)

    vmi_offsets = extract_vmi_offsets("vm-0", kernel_profile)
    explorer_pid = extract_explorer_pid("vm-0", kernel_profile, vmi_offsets)
    runtime_info = RuntimeInfo(vmi_offsets=vmi_offsets,
                               inject_pid=explorer_pid)

    logging.info("Saving runtime profile...")
    with open(os.path.join(PROFILE_DIR, "runtime.json"), "w") as f:
        f.write(runtime_info.to_json(indent=4))

    logging.info("Saving VM snapshot...")

    # Create vm-0 snapshot, and destroy it
    # WARNING: qcow2 snapshot method is a noop. fresh images are created on the fly
    # so we can't keep the vm-0 running
    vm0.save(os.path.join(VOLUME_DIR, "snapshot.sav"))
    logging.info("Snapshot was saved succesfully.")

    # Memory state is frozen, we can't do any writes to persistent storage
    logging.info("Snapshotting persistent memory...")
    storage_backend.snapshot_vm0_volume()

    if report:
        send_usage_report({
            "kernel": {
                "guid": kernel_info.guid,
                "filename": kernel_info.filename,
                "version": kernel_info.version,
            },
            "install_iso": {
                "sha256": install_info.iso_sha256
            },
        })

    if generate_usermode:
        # Restore a VM and create usermode profiles
        create_missing_profiles()

    logging.info("All right, drakrun setup is done.")
    logging.info("First instance of drakrun will be enabled automatically...")
    subprocess.check_output("systemctl enable drakrun@1", shell=True)
    subprocess.check_output("systemctl start drakrun@1", shell=True)

    logging.info("If you want to have more parallel instances, execute:")
    logging.info("  # draksetup scale <number of instances>")
Exemplo n.º 13
0
def create_rekall_profile(injector: Injector, file: DLL, raise_on_error=False):
    pdb_tmp_filepath = None
    cmd = None
    out = None
    try:
        logging.info(f"Fetching rekall profile for {file.path}")

        local_dll_path = os.path.join(PROFILE_DIR, file.dest)
        guest_dll_path = str(PureWindowsPath("C:/", file.path))

        cmd = injector.read_file(guest_dll_path, local_dll_path)
        out = json.loads(cmd.stdout.decode())
        if out["Status"] == "Error" and out["Error"] in [
                "ERROR_FILE_NOT_FOUND",
                "ERROR_PATH_NOT_FOUND",
        ]:
            raise FileNotFoundError
        if out["Status"] != "Success":
            logging.debug("stderr: " + cmd.stderr.decode())
            logging.debug(out)
            # Take care if the error message is changed
            raise Exception("Some error occurred in injector")

        static_apiscout_dll_profile = make_static_apiscout_profile_for_dll(
            local_dll_path)
        with open(os.path.join(APISCOUT_PROFILE_DIR, f"{file.dest}.json"),
                  "w") as f:
            f.write(
                json.dumps(static_apiscout_dll_profile,
                           indent=4,
                           sort_keys=True))

        codeview_data = pe_codeview_data(local_dll_path)
        pdb_tmp_filepath = fetch_pdb(codeview_data["filename"],
                                     codeview_data["symstore_hash"],
                                     PROFILE_DIR)

        logging.debug("Parsing PDB into JSON profile...")
        profile = make_pdb_profile(
            pdb_tmp_filepath,
            dll_origin_path=guest_dll_path,
            dll_path=local_dll_path,
            dll_symstore_hash=codeview_data["symstore_hash"],
        )
        with open(os.path.join(PROFILE_DIR, f"{file.dest}.json"), "w") as f:
            f.write(profile)
    except json.JSONDecodeError:
        logging.debug(f"stdout: {cmd.stdout}")
        logging.debug(f"stderr: {cmd.stderr}")
        logging.debug(traceback.format_exc())
        raise Exception(f"Failed to parse json response on {file.path}")
    except FileNotFoundError as e:
        on_create_rekall_profile_failure(f"Failed to copy file {file.path}",
                                         raise_on_error, e)
    except RuntimeError as e:
        on_create_rekall_profile_failure(
            f"Failed to fetch profile for {file.path}", raise_on_error, e)
    except subprocess.TimeoutExpired as e:
        on_create_rekall_profile_failure(f"Injector timed out for {file.path}",
                                         raise_on_error, e)
    except Exception as e:
        # Take care if the error message is changed
        if str(e) == "Some error occurred in injector":
            raise
        else:
            # Can help in debugging
            if cmd:
                logging.debug("stdout: " + cmd.stdout.decode())
                logging.debug("stderr: " + cmd.stderr.decode())
                logging.debug("rc: " + str(cmd.returncode))
            logging.debug(traceback.format_exc())
            on_create_rekall_profile_failure(
                f"Unexpected exception while creating rekall profile for {file.path}",
                raise_on_error,
                e,
            )
    finally:
        safe_delete(local_dll_path)
        # was crashing here if the first file reached some exception
        if pdb_tmp_filepath is not None:
            safe_delete(os.path.join(PROFILE_DIR, pdb_tmp_filepath))