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

Port virtualbox scripts to VBoxManage CLI #625

Merged
merged 1 commit into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*.class
*.o
*.so
__pycache__/

# Packages #
############
Expand Down Expand Up @@ -35,3 +36,7 @@ Thumbs.db
# Pycharm artifacts
###################
.idea

# vscode
# #################
.vscode/
2 changes: 1 addition & 1 deletion virtualbox/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Cleaning FLARE-VM.20240604 🫧 Snapshots to delete:
VM state: Paused
⚠️ Snapshot deleting is slower in a running VM and may fail in a changing state

Confirm deletion ('y'):y
Confirm deletion (press 'y'):y

Deleting... (this may take some time, go for an 🍦!)
🫧 DELETED 'Snapshot 1'
Expand Down
188 changes: 130 additions & 58 deletions virtualbox/vbox-adapter-check.py
Original file line number Diff line number Diff line change
@@ -1,57 +1,120 @@
#!/usr/bin/python3

import argparse
import re
import sys
import textwrap
import argparse
import virtualbox
from virtualbox.library import NetworkAttachmentType as NetType
from gi.repository import Notify

DYNAMIC_VM_NAME = '.dynamic'
DISABLED_ADAPTER_TYPE = NetType.host_only
ALLOWED_ADAPTER_TYPES = (NetType.host_only, NetType.internal, NetType.null)

ENABLED_STRS = ('Disabled','Enabled ')

def check_and_disable_internet_access(session, machine_name, max_adapters, skip_disabled, do_not_modify):
"""
Checks if a VM's network adapter is set to an internet-accessible mode
and disables it if necessary, showing a warning popup.

Args:
session: The session of the virtual machine to check.
"""
adapters_with_internet = []
for i in range(max_adapters):
adapter = session.machine.get_network_adapter(i)

if skip_disabled and not adapter.enabled:
continue

print(f"{machine_name} {i+1}: {ENABLED_STRS[adapter.enabled]} {adapter.attachment_type}")
import gi

if DYNAMIC_VM_NAME in machine_name and adapter.attachment_type not in ALLOWED_ADAPTER_TYPES:
adapters_with_internet.append(i)
if not do_not_modify:
# Disable the adapter
adapter.attachment_type = DISABLED_ADAPTER_TYPE
gi.require_version("Notify", "0.7")
from gi.repository import Notify

if adapters_with_internet:
adapters_str = ", ".join(str(i+1) for i in adapters_with_internet)
if do_not_modify:
message = f"{machine_name} may be connected to the internet on adapter(s): {adapters_str}. Please double check your VMs settings."
from vboxcommon import *

DYNAMIC_VM_NAME = ".dynamic"
DISABLED_ADAPTER_TYPE = "hostonly"
ALLOWED_ADAPTER_TYPES = ("hostonly", "intnet", "none")


def get_vm_uuids(dynamic_only):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

recommend type hints, at least for function signatures, as a form of documentation

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not a blocker, but nice to have

"""Gets the machine UUID(s) for a given VM name using 'VBoxManage list vms'."""
machine_guids = []
try:
# regex VM name and extract the GUID
# "FLARE-VM.testing" {b76d628b-737f-40a3-9a16-c5f66ad2cfcc}
vms_output = run_vboxmanage(["list", "vms"])
pattern = r'"(.*?)" \{(.*?)\}'
stevemk14ebr marked this conversation as resolved.
Show resolved Hide resolved
matches = re.findall(pattern, vms_output)
for match in matches:
vm_name = match[0]
machine_guid = match[1]
# either get all vms if dynamic_only false, or just the dynamic vms if true
if (not dynamic_only) or DYNAMIC_VM_NAME in vm_name:
machine_guids.append((vm_name, machine_guid))
except Exception as e:
raise Exception(f"Error finding machines UUIDs") from e
return machine_guids


def change_network_adapters_to_hostonly(
machine_guid, vm_name, hostonly_ifname, do_not_modify
):
"""Verify all adapters are in an allowed configuration. Must be poweredoff"""
try:
# gather adapters in incorrect configurations
nics_with_internet = []
invalid_nics_msg = ""

# nic1="hostonly"
# nictype1="82540EM"
# nicspeed1="0"
# nic2="none"
# nic3="none"
# nic4="none"
# nic5="none"
# nic6="none"
# nic7="none"
# nic8="none"

vminfo = run_vboxmanage(["showvminfo", machine_guid, "--machinereadable"])
for nic_number, nic_value in re.findall(
'^nic(\d+)="(\S+)"', vminfo, flags=re.M
):
if nic_value not in ALLOWED_ADAPTER_TYPES:
nics_with_internet.append(f"nic{nic_number}")
stevemk14ebr marked this conversation as resolved.
Show resolved Hide resolved
invalid_nics_msg += f"{nic_number} "

# modify the invalid adapters if allowed
if nics_with_internet:
for nic in nics_with_internet:
stevemk14ebr marked this conversation as resolved.
Show resolved Hide resolved
stevemk14ebr marked this conversation as resolved.
Show resolved Hide resolved
if do_not_modify:
message = f"{vm_name} may be connected to the internet on adapter(s): {nic}. Please double check your VMs settings."
else:
message = f"{vm_name} may be connected to the internet on adapter(s): {nic}. The network adapter(s) have been disabled automatically to prevent an undesired internet connectivity. Please double check your VMs settings."
# different commands are necessary if the machine is running.
if get_vm_state(machine_guid) == "poweroff":
run_vboxmanage(
[
"modifyvm",
machine_guid,
f"--{nic}",
DISABLED_ADAPTER_TYPE,
]
)
else:
run_vboxmanage(
[
"controlvm",
machine_guid,
nic,
"hostonly",
hostonly_ifname,
]
)
print(f"Set VM {vm_name} adaper {nic} to hostonly")

if do_not_modify:
message = f"{vm_name} may be connected to the internet on adapter(s): {invalid_nics_msg}. Please double check your VMs settings."
else:
message = f"{vm_name} may be connected to the internet on adapter(s): {invalid_nics_msg}. The network adapter(s) have been disabled automatically to prevent an undesired internet connectivity. Please double check your VMs settings."

# Show notification using PyGObject
Notify.init("VirtualBox adapter check")
notification = Notify.Notification.new(
f"INTERNET IN VM: {vm_name}", message, "dialog-error"
)
# Set highest priority
notification.set_urgency(2)
notification.show()
print(f"{vm_name} network configuration not ok, sent notifaction")
return
else:
message = f"{machine_name} may be connected to the internet on adapter(s): {adapters_str}. The network adapter(s) have been disabled automatically to prevent an undesired internet connectivity. Please double check your VMs settings."

# Show notification using PyGObject
Notify.init("VirtualBox adapter check")
notification = Notify.Notification.new(f"INTERNET IN VM: {machine_name}", message, "dialog-error")
# Set highest priority
notification.set_urgency(2)
notification.show()
print(f"{vm_name} network configuration is ok")
return

session.machine.save_settings()
session.unlock_machine()
except Exception as e:
raise Exception("Failed to verify VM adapter configuration") from e


def main(argv=None):
Expand All @@ -66,28 +129,37 @@ def main(argv=None):

# Print status of all internet adapters without modifying any of them
vbox-adapter-check.vm --do_not_modify

# Print status of enabled internet adapters and disabled the enabled adapters with internet access in VMs with {DYNAMIC_VM_NAME} in the name
vbox-adapter-check.vm --skip_disabled

# # Print status of enabled internet adapters without modifying any of them
vbox-adapter-check.vm --skip_disabled --do_not_modify
"""
)
parser = argparse.ArgumentParser(
description=f"Print the status of all internet adapters of all VMs in VirtualBox. Notify if any VM with {DYNAMIC_VM_NAME} in the name has an adapter whose type is not allowed (internet access is undesirable for dynamic malware analysis)i. Optionally change the type of the adapters with non-allowed type to Host-Only.",
epilog=epilog,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument("--do_not_modify", action="store_true", help="Only print the status of the internet adapters without modifying them.")
parser.add_argument("--skip_disabled", action="store_true", help="Skip the disabled adapters.")
parser.add_argument(
"--do_not_modify",
action="store_true",
help="Only print the status of the internet adapters without modifying them.",
)
parser.add_argument(
"--dynamic_only",
action="store_true",
help="Only scan VMs with .dynamic in the name",
)
args = parser.parse_args(args=argv)

vbox = virtualbox.VirtualBox()
for machine in vbox.machines:
session = machine.create_session()
max_adapters = vbox.system_properties.get_max_network_adapters(machine.chipset_type)
check_and_disable_internet_access(session, machine.name, max_adapters, args.skip_disabled, args.do_not_modify)
try:
hostonly_ifname = ensure_hostonlyif_exists()
machine_guids = get_vm_uuids(args.dynamic_only)
if len(machine_guids) > 0:
for vm_name, machine_guid in machine_guids:
change_network_adapters_to_hostonly(
machine_guid, vm_name, hostonly_ifname, args.do_not_modify
)
else:
print(f"[Warning ⚠️] No VMs found")
except Exception as e:
print(f"Error verifying dynamic VM hostonly configuration: {e}")


if __name__ == "__main__":
Expand Down
Loading