Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dump raw guest VM memory feature #621

Merged
merged 29 commits into from
Sep 3, 2021
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
9d73ce1
added pre/post sample RAM dump
desecnd Aug 5, 2021
cb69ac3
moved code to _memory_dump function, added config dump path
desecnd Aug 11, 2021
123e364
added native outdir destination for dumps & config boolean option raw…
desecnd Aug 13, 2021
403429d
removed unnecessary format string syntax
desecnd Aug 16, 2021
0fa0de9
added raw_memory_dump option to drakrun config
desecnd Aug 16, 2021
34e952d
added pre/post sample RAM dump
desecnd Aug 5, 2021
271b772
moved code to _memory_dump function, added config dump path
desecnd Aug 11, 2021
c72ddbd
added native outdir destination for dumps & config boolean option raw…
desecnd Aug 13, 2021
a94f833
removed unnecessary format string syntax
desecnd Aug 16, 2021
86ac6e6
added raw_memory_dump option to drakrun config
desecnd Aug 16, 2021
5847e13
fixed bad formating with black
desecnd Aug 19, 2021
dd6a6f3
added snapshot hash information to metadata.json
desecnd Aug 30, 2021
d0ef7f5
added gzip compression and tempfile usage to _memory_dump function
desecnd Aug 30, 2021
17cd968
changed file_sha256 method to private
desecnd Aug 30, 2021
8cd3faa
fixed log format to uppercase
desecnd Aug 30, 2021
acaa119
merged metadata snapshot-hash and gzip compression
desecnd Aug 30, 2021
a10bbf1
format - fix no empty line after shebang
desecnd Aug 31, 2021
ada9395
Merge branch 'master' into dump-memory
desecnd Aug 31, 2021
655af70
Fixed formatting with black
desecnd Aug 31, 2021
c2a75b2
moved memory_dump to VirtualMachine class
desecnd Sep 1, 2021
f87f8de
moved snapshot hash functionality to init_drakrun
desecnd Sep 1, 2021
6a849fe
updated drakrun config.ini raw_memory_dump option description
desecnd Sep 1, 2021
526cded
added explicit .gz extension to function parameter
desecnd Sep 3, 2021
c26e477
Added draksetup memdump export functionality
desecnd Sep 3, 2021
8b10e43
removed bare except
desecnd Sep 3, 2021
174a2a4
fixed string format
desecnd Sep 3, 2021
514a0e1
added pause to vm.restore
desecnd Sep 3, 2021
7f28ac1
moved file-hashing code to auxiliary function file_sha256 in util.py
desecnd Sep 3, 2021
71a02d5
fixed typo's and misleading names in file_sha256 function
desecnd Sep 3, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions drakrun/drakrun/config.dist.ini
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ access_key=
secret_key=

[drakrun]
; if set, drakrun dumps raw VM guest memory after finished analysis with drakvuf
; compressed dumps are stored as "post_sample.raw_memdump.gz" in analysis folder
raw_memory_dump=0

; whether guest VMs should have access to the Internet or no
net_enable=0

Expand Down
74 changes: 74 additions & 0 deletions drakrun/drakrun/draksetup.py
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,79 @@ def get_minio_client(config):
)


@click.group(help="Manage VM raw memory pre-sample dumps")
def memdump():
pass


@memdump.command(name="export", help="Upload pre-sample raw memory dump to MinIO.")
@click.option("--instance", required=True, type=int, help="Instance ID of restored VM")
@click.option(
"--bucket",
default="presample-memdumps",
help="MinIO bucket to store the compressed raw image",
)
def memdump_export(bucket, instance):
install_info = InstallInfo.try_load()
if install_info is None:
logging.error(
"Missing installation info. Did you forget to set up the sandbox?"
)
return

backend = get_storage_backend(install_info)
vm = VirtualMachine(backend, instance)
if vm.is_running:
logging.exception(f"vm-{instance} is running")
return

logging.info("Calculating snapshot hash...")
snapshot_hash = hashlib.sha256()
with open(os.path.join(VOLUME_DIR, "snapshot.sav"), "rb") as f:
for block in iter(lambda: f.read(65536), b""):
snapshot_hash.update(block)
snapshot_sha256 = snapshot_hash.hexdigest()
chivay marked this conversation as resolved.
Show resolved Hide resolved
name = f"{snapshot_sha256}_pre_sample.raw_memdump.gz"

mc = get_minio_client(conf)

if not mc.bucket_exists(bucket):
logging.error("Bucket %s doesn't exist", bucket)
return

try:
mc.stat_object(bucket, name)
logging.info("This file already exists in specified bucket")
return
except NoSuchKey:
pass
except Exception:
logging.exception("Failed to check if object exists on minio")

logging.info("Restoring VM and performing memory dump")

try:
vm.restore()
chivay marked this conversation as resolved.
Show resolved Hide resolved
except subprocess.CalledProcessError:
logging.exception(f"Failed to restore VM {vm.vm_name}")
with open(f"/var/log/xen/qemu-dm-{vm.vm_name}.log", "rb") as f:
logging.error(f.read())
logging.info("VM restored")

with tempfile.NamedTemporaryFile() as compressed_memdump:
vm.memory_dump(compressed_memdump.name)

logging.info(f"Uploading {name} to {bucket}")
mc.fput_object(bucket, name, compressed_memdump.name)

try:
vm.destroy()
except Exception:
logging.exception("Failed to destroy VM")

logging.info("Done")


@click.group(help="Manage VM snapshots")
def snapshot():
pass
Expand Down Expand Up @@ -1247,6 +1320,7 @@ def main():
main.add_command(mount)
main.add_command(scale)
main.add_command(snapshot)
main.add_command(memdump)
main.add_command(cleanup)


Expand Down
24 changes: 23 additions & 1 deletion drakrun/drakrun/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,13 @@

from drakrun.version import __version__ as DRAKRUN_VERSION
from drakrun.drakpdb import dll_file_list
from drakrun.config import InstallInfo, ETC_DIR, PROFILE_DIR, APISCOUT_PROFILE_DIR
from drakrun.config import (
InstallInfo,
ETC_DIR,
PROFILE_DIR,
APISCOUT_PROFILE_DIR,
VOLUME_DIR,
)
from drakrun.storage import get_storage_backend
from drakrun.networking import start_tcpdump_collector, start_dnsmasq, setup_vm_network
from drakrun.util import (
Expand Down Expand Up @@ -248,6 +254,7 @@ def vm_name(self) -> str:
return f"vm-{self.instance_id}"

def init_drakrun(self):

generate_vm_conf(self.install_info, self.instance_id)

if not self.backend.minio.bucket_exists("drakrun"):
Expand All @@ -258,6 +265,13 @@ def init_drakrun(self):

setup_vm_network(self.instance_id, self.net_enable, out_interface, dns_server)

self.log.info("Caculating snapshot sha256 hash...")
snapshot_hash = hashlib.sha256()
with open(os.path.join(VOLUME_DIR, "snapshot.sav"), "rb") as f:
for block in iter(lambda: f.read(65536), b""):
snapshot_hash.update(block)
self.snapshot_sha256 = snapshot_hash.hexdigest()

def _karton_safe_get_headers(self, task, key, fallback):
ret = task.headers.get(key, fallback)
# intentional workaround due to a bug in karton
Expand Down Expand Up @@ -570,6 +584,9 @@ def analyze_sample(self, sample_path, workdir, outdir, start_command, timeout):

dns_server = self.config.config["drakrun"].get("dns_server", "8.8.8.8")
drakmon_log_fp = os.path.join(outdir, "drakmon.log")
raw_memory_dump = self.config.config["drakrun"].getboolean(
"raw_memory_dump", fallback=False
)

with self.run_vm() as vm, graceful_exit(
start_dnsmasq(self.instance_id, dns_server)
Expand Down Expand Up @@ -642,6 +659,9 @@ def analyze_sample(self, sample_path, workdir, outdir, start_command, timeout):
self.log.exception("DRAKVUF timeout expired")
raise e

if raw_memory_dump:
vm.memory_dump(os.path.join(outdir, "post_sample.raw_memdump.gz"))

return analysis_info

@with_logs("drakrun.log")
Expand All @@ -654,6 +674,7 @@ def process(self, task: Task):
self.log.info(f"Running on: {socket.gethostname()}")
self.log.info(f"Sample SHA256: {sha256sum}")
self.log.info(f"Analysis UID: {self.analysis_uid}")
self.log.info(f"Snapshot SHA256: {self.snapshot_sha256}")

# Timeout sanity check
timeout = task.payload.get("timeout") or self.default_timeout
Expand Down Expand Up @@ -717,6 +738,7 @@ def process(self, task: Task):

metadata = {
"sample_sha256": sha256sum,
"snapshot_sha256": self.snapshot_sha256,
"magic_output": magic_output,
"time_started": int(time.time()),
}
Expand Down
28 changes: 28 additions & 0 deletions drakrun/drakrun/vm.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import re
import logging
import subprocess
import tempfile
from pathlib import Path

from drakrun.storage import get_storage_backend, StorageBackendBase
Expand Down Expand Up @@ -174,3 +175,30 @@ def destroy(self, **kwargs):
f"Failed to destroy VM {self.vm_name}",
**kwargs,
)

def memory_dump(self, compressed_filepath):
"""Dump raw memory from running vm using vmi-dump-memory and compress it with gzip
:raises: subprocess.CalledProcessError
"""

with tempfile.NamedTemporaryFile() as raw_memdump, open(
compressed_filepath, "wb"
) as compressed_file:

log.info(f"Dumping raw memory from {self.vm_name} guest...")
try:
subprocess.run(
["vmi-dump-memory", self.vm_name, raw_memdump.name], check=True
)
except subprocess.CalledProcessError as e:
log.error(f"Dumping raw memory from {self.vm_name} failed.")
raise e

log.info(f"Compressing {self.vm_name} guest memory dump...")
try:
subprocess.run(
["gzip", "-c", raw_memdump.name], check=True, stdout=compressed_file
)
except subprocess.CalledProcessError as e:
log.error(f"Compressing raw memory from {self.vm_name} failed.")
raise e