diff --git a/lib/cuckoo/core/analysis_manager.py b/lib/cuckoo/core/analysis_manager.py index 16ae6a4c69b..4f230d3da35 100644 --- a/lib/cuckoo/core/analysis_manager.py +++ b/lib/cuckoo/core/analysis_manager.py @@ -422,7 +422,7 @@ def run_analysis_on_guest(self) -> None: options["clock"] = self.db.update_clock(self.task.id) self.db.guest_set_status(self.task.id, "starting") guest_manager.start_analysis(options) - + try: if guest_manager.get_status_from_db() == "starting": guest_manager.set_status_in_db("running") @@ -430,7 +430,7 @@ def run_analysis_on_guest(self) -> None: guest_manager.set_status_in_db("stopping") except Exception as e: guest_manager.set_status_in_db("failed") - self.log.exception(f"Unknown exception waiting for guest completion: {e}") + self.log.exception("Unknown exception waiting for guest completion: %s", str(e)) return diff --git a/modules/machinery/gcp.py b/modules/machinery/gcp.py index 05d8127dc33..9655e14e38f 100644 --- a/modules/machinery/gcp.py +++ b/modules/machinery/gcp.py @@ -1,4 +1,6 @@ import logging +import random +import string from lib.cuckoo.common.config import Config from typing import List @@ -33,6 +35,8 @@ class GCP(Machinery): ABORTED = "ABORTED" ERROR = "ERROR" + OPERATION_TIMEOUT = 120 # 2 minutes + def _initialize_check(self): """Runs all checks when a machine manager is initialized. @raise CuckooDependencyError: if google-cloud-compute is not installed @@ -50,16 +54,17 @@ def _initialize_check(self): log.info("Connecting to GCP Project: %s, Zone: %s", self.project, self.zone) # Initialize Clients + creds = None if self.json_key_path: creds = service_account.Credentials.from_service_account_file(self.json_key_path) - self.instances_client = compute_v1.InstancesClient(credentials=creds) elif self.running_in_gcp: log.info("Using Compute Engine credentials") creds = compute_engine.Credentials() - self.instances_client = compute_v1.InstancesClient(credentials=creds) else: log.info("No Service Account JSON provided; using Application Default Credentials") - self.instances_client = compute_v1.InstancesClient() + + self.instances_client = compute_v1.InstancesClient(credentials=creds) + self.disks_client = compute_v1.DisksClient(credentials=creds) super()._initialize_check() @@ -145,5 +150,91 @@ def stop(self, label): instance=label ) self.instances_client.stop(request=request) + snapshot = self.db.view_machine_by_label(label).snapshot + if snapshot is not None: + self._wait_status(label, self.POWEROFF) + self._restore(label, snapshot) except Exception as e: raise CuckooMachineError(f"Unable to stop machine '{label}': {e}") from e + + def _restore(self, label, snapshot): + """ + Restore a virtual machine according to the configured snapshot. + @param label: virtual machine label. + @raise CuckooMachineError: if unable to restore. + """ + log.debug("Restoring VM %s", label) + try: + request = compute_v1.GetInstanceRequest( + project=self.project, + zone=self.zone, + instance=label + ) + instance = self.instances_client.get(request=request) + if len(instance.disks) > 1: + log.error("Unable to restore machine '%s': wrong number of disks", label) + raise CuckooMachineError(f"Unable to restore machine '{label}': wrong number of disks") + elif len(instance.disks) == 1: + # Detach old disk + old_disk = instance.disks[0] + log.debug("Detaching disk %s", old_disk.device_name) + request = compute_v1.DetachDiskInstanceRequest( + project=self.project, + zone=self.zone, + instance=label, + device_name=old_disk.device_name + ) + operation = self.instances_client.detach_disk(request=request) + operation.result(timeout=self.OPERATION_TIMEOUT) + self._wait_and_check_operation(operation, label, "unable to detach disk") + + # Delete old disk + log.debug("Deleting disk %s", old_disk.device_name) + request = compute_v1.DeleteDiskRequest( + project=self.project, + zone=self.zone, + disk=old_disk.device_name + ) + operation = self.disks_client.delete(request=request) + self._wait_and_check_operation(operation, label, "unable to delete disk") + + # Create disk from snapshot + new_disk_name = instance.name + ''.join(random.choices(string.ascii_lowercase, k=5)) + log.debug("Creating disk %s from snapshot %s", new_disk_name, snapshot) + new_disk = compute_v1.Disk( + name=new_disk_name, + source_snapshot=f"projects/{self.project}/global/snapshots/{snapshot}", + zone=instance.zone + ) + operation = self.disks_client.insert( + project=self.project, + zone=self.zone, + disk_resource=new_disk + ) + operation.result(timeout=self.OPERATION_TIMEOUT) + self._wait_and_check_operation(operation, label, "unable to create disk") + + # Attach new disk + log.debug("Attaching disk %s", new_disk.name) + request = compute_v1.AttachDiskInstanceRequest( + project=self.project, + zone=self.zone, + instance=label, + attached_disk_resource=compute_v1.AttachedDisk( + source=f"/projects/{self.project}/zones/{self.zone}/disks/{new_disk.name}", + mode="READ_WRITE", + auto_delete=True, + boot=True + ) + ) + operation = self.instances_client.attach_disk(request=request) + self._wait_and_check_operation(operation, label, "unable to attach disk") + except Exception as e: + raise CuckooMachineError(f"Unable to restore machine '{label}': {e}") from e + + def _wait_and_check_operation(self, operation, label: str, error_message: str): + """Waits for a GCP operation to complete and raises an error if it fails.""" + operation.result(timeout=self.OPERATION_TIMEOUT) + if operation.error_code: + log.error("Unable to restore machine '%s': %s. Error: %s", label, error_message, operation.error_message) + raise CuckooMachineError(f"Unable to restore machine '{label}': {error_message}") diff --git a/modules/processing/network.py b/modules/processing/network.py index 030f5c36c03..5f1984421ed 100644 --- a/modules/processing/network.py +++ b/modules/processing/network.py @@ -789,6 +789,10 @@ def run(self): connection["sport"] = tcp.sport connection["dport"] = tcp.dport + if tcp.flags & dpkt.tcp.TH_SYN and tcp.flags & dpkt.tcp.TH_ACK: + connection["src"], connection["dst"] = connection["dst"], connection["src"] + connection["sport"], connection["dport"] = connection["dport"], connection["sport"] + if tcp.data: self._tcp_dissect(connection, tcp.data, ts) src, sport, dst, dport = connection["src"], connection["sport"], connection["dst"], connection["dport"]