Skip to content

Commit e2e0356

Browse files
authored
Merge pull request #679 from Ana06/vbox-adapter-check
Improve vbox-adapter-check
2 parents 42e3397 + 4349a3f commit e2e0356

3 files changed

Lines changed: 187 additions & 127 deletions

File tree

3.82 KB
Loading

virtualbox/README.md

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# VirtualBox scripts
22

33
**This folder contains several scripts related to enhance building, exporting, and using FLARE-VM in VirtualBox.**
4+
The scripts have been tested in Debian 12 with GNOME 44.9.
45

56

67
## Clean up snapshots
@@ -12,7 +13,7 @@ It is not possible to select and delete several snapshots in VirtualBox, making
1213
### Example
1314

1415
```
15-
$ ./vbox-remove-snapshots.py FLARE-VM.20240604
16+
$ ./vbox-remove-snapshots.py FLARE-VM.20240604 --protected empty,clean,done,important
1617
1718
Snapshots with the following strings in the name (case insensitive) won't be deleted:
1819
clean
@@ -65,31 +66,20 @@ See you next time you need to clean up your VMs! ✨
6566
## Check internet adapter status
6667

6768
[`vbox-adapter-check.py`](vbox-adapter-check.py) prints the status of all internet adapters of all VMs in VirtualBox.
68-
The script also notifies if any dynamic analysis VM (with `.dynamic` in the name) has an adapter whose type is not allowed (internet access is undesirable for dynamic malware analysis).
69-
Unless the argument `--do_not_modify` is provided, the script changes the type of the adapters with non-allowed type to Host-Only.
70-
Unless the argument `--skip_disabled` is provided, the script also explores the disabled adapters, printing their status and possibly changing their type.
71-
The script has been tested in Debian 12 with GNOME 44.9.
69+
If the argument `--dynamic_only` is provided, the script only print the status of the dynamic analysis VM (with `.dynamic` in the name).
70+
Unless the argument `--do_not_modify` is provided, if internet is detected in any dynamic analysis VM, the script sends a notification and changes the adapters type to Host-Only.
71+
The script is useful to detect internet access, which is undesirable for dynamic malware analysis.
7272

7373
### Example
7474

7575
```
76-
$ ./vbox-adapter-check.py
77-
windows10 1: Enabled HostOnly
78-
windows10 2: Disabled Null
79-
windows10 3: Disabled Null
80-
windows10 4: Disabled Null
81-
windows10 5: Disabled Null
82-
windows10 6: Disabled Null
83-
windows10 7: Disabled Null
84-
windows10 8: Disabled Null
85-
FLARE-VM.20240808.dynamic 1: Enabled NAT
86-
FLARE-VM.20240808.dynamic 2: Disabled NAT
87-
FLARE-VM.20240808.dynamic 3: Disabled Bridged
88-
FLARE-VM.20240808.dynamic 4: Enabled Internal
89-
FLARE-VM.20240808.dynamic 5: Disabled Null
90-
FLARE-VM.20240808.dynamic 6: Disabled Null
91-
FLARE-VM.20240808.dynamic 7: Disabled Null
92-
FLARE-VM.20240808.dynamic 8: Disabled Null
76+
$ ~/github/flare-vm/virtualbox/vbox-adapter-check.py
77+
VM {2bc66f50-9ecb-4b10-a4dd-0cc329bc383d} ⚠️ FLARE-VM.testing is connected to the internet on adapter(s): 1
78+
VM {a23c0c37-2062-4cf0-882b-9e9747dd33b6} ✅ REMnux.20241217.dynamic network configuration is ok
79+
VM {fa0b3733-50cb-43fd-8428-745d0e9159cb} ✅ FLARE-VM.Win10.20250211.dynamic network configuration is ok
80+
VM {e5f509ed-cbc8-4abc-b052-664246207e89} ⚠️ FLARE-VM.Win10.20250211.full.dynamic is connected to the internet on adapter(s): 1, 2
81+
VM {e5f509ed-cbc8-4abc-b052-664246207e89} ⚙️ FLARE-VM.Win10.20250211.full.dynamic set adapter 1 to hostonly
82+
VM {e5f509ed-cbc8-4abc-b052-664246207e89} ⚙️ FLARE-VM.Win10.20250211.full.dynamic set adapter 2 to hostonly
9383
```
9484

9585
#### Notification
@@ -134,7 +124,7 @@ This configuration file specifies the VM name, the exported VM name, and details
134124
Individual snapshot configurations can include custom commands to be executed within the guest, legal notices to be applied, and file/folder exclusions for the automated cleanup process.
135125
See the configuration example file [`configs/win10_flare-vm.yaml`](configs/win10_flare-vm.yaml).
136126

137-
The `BUILD-READY` snapshot is expected to be an empty Windows installation that satisfies the FLARE-VM installation requirements and has UAC disabled
127+
The `BUILD-READY` snapshot is expected to be an empty Windows installation that satisfies the FLARE-VM installation requirements and has UAC disabled.
138128
To disable UAC execute in a cmd console with admin rights and restart the VM for the change to take effect:
139129
```
140130
%windir%\System32\reg.exe ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v EnableLUA /t REG_DWORD /d 0 /f

virtualbox/vbox-adapter-check.py

Lines changed: 174 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -30,122 +30,193 @@
3030
ALLOWED_ADAPTER_TYPES = ("hostonly", "intnet", "none")
3131

3232
DESCRIPTION = f"""Print the status of all internet adapters of all VMs in VirtualBox.
33-
Notify if any VM with {DYNAMIC_VM_NAME} in the name has an adapter whose type is not allowed.
34-
This is useful to detect internet access which is undesirable for dynamic malware analysis.
35-
Optionally change the type of the adapters with non-allowed type to Host-Only."""
33+
Optionally, if any VM with {DYNAMIC_VM_NAME} in the name has an adapter whose type is not allowed,
34+
send a notification and change the type of the adapters with non-allowed type to {DISABLED_ADAPTER_TYPE}.
35+
This is useful to detect internet access which is undesirable for dynamic malware analysis."""
36+
3637

3738
EPILOG = textwrap.dedent(
3839
f"""
3940
Example usage:
40-
# Print status of all interfaces and disable internet access in VMs whose name contain {DYNAMIC_VM_NAME}
41+
# Print status of all interfaces. For the VMs whose name contain {DYNAMIC_VM_NAME},
42+
# show a notification and disable internet if enabled.
4143
vbox-adapter-check.vm
4244
43-
# Print status of all interfaces without modifying any of them
45+
# For the VMs whose name contain {DYNAMIC_VM_NAME}, print the status of their interfaces.
46+
# If internet is enabled, show a notification and disable internet.
47+
vbox-adapter-check.vm --dynamic_only
48+
49+
# Print status of all interfaces without modifying any of them.
4450
vbox-adapter-check.vm --do_not_modify
51+
52+
# Print status of all interfaces in VMs whose name contain {DYNAMIC_VM_NAME} without modifying any of them.
53+
vbox-adapter-check.vm --dynamic_only --do_not_modify
4554
"""
4655
)
4756

4857

49-
def get_vm_uuids(dynamic_only):
50-
"""Gets the machine UUID(s) for a given VM name using 'VBoxManage list vms'."""
51-
vm_uuids = []
52-
try:
53-
# regex VM name and extract the GUID
54-
# "FLARE-VM.testing" {b76d628b-737f-40a3-9a16-c5f66ad2cfcc}
55-
vms_info = run_vboxmanage(["list", "vms"])
56-
pattern = r'"(.*?)" \{(.*?)\}'
57-
matches = re.findall(pattern, vms_info)
58-
for match in matches:
59-
vm_name = match[0]
60-
vm_uuid = match[1]
61-
# either get all vms if dynamic_only false, or just the dynamic vms if true
62-
if (not dynamic_only) or DYNAMIC_VM_NAME in vm_name:
63-
vm_uuids.append((vm_name, vm_uuid))
64-
except Exception as e:
65-
raise Exception("Error finding machines UUIDs") from e
66-
return vm_uuids
58+
def get_vms(dynamic_only):
59+
"""Get the names and UUID of the VirtualBox VMs using 'VBoxManage list vms'.
60+
61+
Args:
62+
dynamic_only: If true, only the VMs containing DYNAMIC_VM_NAME in the name are returned.
63+
64+
Returns:
65+
A list of tuples, where each tuple contains the VM name (str) and VM UUID (str).
66+
Returns an empty list if no VMs are found.
67+
"""
68+
vms_list = []
69+
# regex VM name and extract the GUID
70+
# Example of `VBoxManage list vms` output:
71+
# "FLARE-VM.testing" {b76d628b-737f-40a3-9a16-c5f66ad2cfcc}
72+
# "FLARE-VM" {a23c0c37-2062-4cf0-882b-9e9747dd33b6}
73+
vms_info = run_vboxmanage(["list", "vms"])
74+
75+
vms = re.findall(r'"(.*?)" (\{.*?\})', vms_info)
76+
for vm_name, vm_uuid in vms:
77+
# Get only the VMs containing DYNAMIC_VM_NAME in the name if dynamic_only is true
78+
if not (dynamic_only and (DYNAMIC_VM_NAME in vm_name)):
79+
vms_list.append((vm_name, vm_uuid))
80+
return vms_list
81+
82+
83+
def get_nics(vm_uuid, only_nic=None):
84+
"""
85+
Retrieves the configured network interfaces and their types for a given virtual machine.
86+
87+
Args:
88+
vm_uuid: The unique identifier (UUID) of the virtual machine.
89+
only_nic: An optional string specifying a specific NIC number to retrieve
90+
(e.g., "1" for nic1). If None, information for all configured NICs
91+
will be returned.
92+
93+
Returns:
94+
A list of tuples, where each tuple contains:
95+
- The NIC number as a string (e.g., "1", "2")
96+
- The NIC value (e.g., "hostonly", "nat")
97+
"""
98+
99+
# Example of `VBoxManage showvm_info <VM_UUID> --machinereadable` relevant output:
100+
# nic1="hostonly"
101+
# nictype1="82540EM"
102+
# nicspeed1="0"
103+
# nic2="none"
104+
# nic3="none"
105+
# nic4="none"
106+
# nic5="none"
107+
# nic6="none"
108+
# nic7="none"
109+
# nic8="none"
110+
vm_info = run_vboxmanage(["showvminfo", vm_uuid, "--machinereadable"])
111+
112+
# If no nic provided, get all possible numbers using RegExp
113+
only_nic = r"\d+"
67114

115+
# Get adapters numbers and their values as a list: [(nic_number, nic_value)]
116+
return re.findall(rf'^nic({only_nic})="(\S+)"', vm_info, flags=re.M)
68117

69-
def change_network_adapters_to_hostonly(vm_uuid, vm_name, hostonly_ifname, do_not_modify):
70-
"""Verify all adapters are in an allowed configuration. Must be poweredoff"""
118+
119+
def disable_adapter(vm_uuid, nic_number, hostonly_ifname):
120+
"""Disable the network adapter of the VM by setting it to DISABLED_ADAPTER_TYPE
121+
122+
Args:
123+
vm_uuid: VM UUID
124+
nic_number: nic to disable
125+
126+
Raises:
127+
RuntimeError: If the nic type is not changed to DISABLED_ADAPTER_TYPE
128+
"""
129+
# We need to run a different command if the machine is running.
130+
if get_vm_state(vm_uuid) in ("poweroff", "aborted"):
131+
run_vboxmanage(
132+
[
133+
"modifyvm",
134+
vm_uuid,
135+
f"--nic{nic_number}",
136+
DISABLED_ADAPTER_TYPE,
137+
]
138+
)
139+
# Set the hostonlyadapter for nic as "VBoxManage modifyvm --nic" does not set it
140+
# If hostonlyadapter is empty, starting the VM raises an error
141+
run_vboxmanage(
142+
[
143+
"modifyvm",
144+
vm_uuid,
145+
f"--hostonlyadapter{nic_number}",
146+
hostonly_ifname,
147+
]
148+
)
149+
else:
150+
run_vboxmanage(
151+
[
152+
"controlvm",
153+
vm_uuid,
154+
f"nic{nic_number}",
155+
DISABLED_ADAPTER_TYPE,
156+
hostonly_ifname,
157+
]
158+
)
159+
160+
# Verify nic has been modify as the command may return code 0 even if it fails to set the adapter
161+
_, nic_value = get_nics(vm_uuid, nic_number)[0]
162+
if nic_value != DISABLED_ADAPTER_TYPE:
163+
raise RuntimeError(f"nic{nic_number} has type '{nic_value}'")
164+
165+
166+
def list_to_str(string_list):
167+
"""Joins a list of strings with ", "."""
168+
return ", ".join(string_list)
169+
170+
171+
def verify_network_adapters(vm_uuid, vm_name, hostonly_ifname, modify_and_notify):
172+
"""Verify and optionally correct network adapter configurations for a given VM.
173+
174+
Check the network adapter types of a given VM against a list of allowed types (`ALLOWED_ADAPTER_TYPES`).
175+
If not allowed adapter types are found, print a warning and, if `do_not_modify` is False, disable the adapters and sends a desktop notification.
176+
177+
Args:
178+
vm_uuid: The unique identifier (UUID) of the VM.
179+
vm_name: The name of the VM.
180+
hostonly_ifname: The name of the host-only network interface. This is passed for potential use in
181+
disabling adapters (though not directly used in the verification logic).
182+
modify_and_notify: A boolean flag. If False, invalid adapters will only be reported, without automatic modification and notification.
183+
"""
71184
try:
72-
# gather adapters in incorrect configurations
73-
nics_with_internet = []
74-
invalid_nics_msg = ""
75-
76-
# nic1="hostonly"
77-
# nictype1="82540EM"
78-
# nicspeed1="0"
79-
# nic2="none"
80-
# nic3="none"
81-
# nic4="none"
82-
# nic5="none"
83-
# nic6="none"
84-
# nic7="none"
85-
# nic8="none"
86-
87-
vminfo = run_vboxmanage(["showvminfo", vm_uuid, "--machinereadable"])
88-
for nic_number, nic_value in re.findall(r'^nic(\d+)="(\S+)"', vminfo, flags=re.M):
185+
invalid_nics = []
186+
for nic_number, nic_value in get_nics(vm_uuid):
89187
if nic_value not in ALLOWED_ADAPTER_TYPES:
90-
nics_with_internet.append(f"nic{nic_number}")
91-
invalid_nics_msg += f"{nic_number} "
92-
93-
# modify the invalid adapters if allowed
94-
if nics_with_internet:
95-
for nic in nics_with_internet:
96-
if do_not_modify:
97-
message = f"{vm_name} may be connected to the internet on adapter(s): {nic}. Please double check your VMs settings."
98-
else:
99-
message = (
100-
f"{vm_name} may be connected to the internet on adapter(s): {nic}."
101-
"The network adapter(s) have been disabled automatically to prevent an undesired internet connectivity."
102-
"Please double check your VMs settings."
103-
)
104-
# different commands are necessary if the machine is running.
105-
if get_vm_state(vm_uuid) == "poweroff":
106-
run_vboxmanage(
107-
[
108-
"modifyvm",
109-
vm_uuid,
110-
f"--{nic}",
111-
DISABLED_ADAPTER_TYPE,
112-
]
113-
)
114-
else:
115-
run_vboxmanage(
116-
[
117-
"controlvm",
118-
vm_uuid,
119-
nic,
120-
"hostonly",
121-
hostonly_ifname,
122-
]
123-
)
124-
print(f"Set VM {vm_name} adaper {nic} to hostonly")
125-
126-
if do_not_modify:
127-
message = f"{vm_name} may be connected to the internet on adapter(s): {invalid_nics_msg}. Please double check your VMs settings."
128-
else:
129-
message = (
130-
f"{vm_name} may be connected to the internet on adapter(s): {invalid_nics_msg}."
131-
"The network adapter(s) have been disabled automatically to prevent an undesired internet connectivity."
132-
"Please double check your VMs settings."
133-
)
188+
invalid_nics.append(nic_number)
134189

190+
if not invalid_nics:
191+
print(f"VM {vm_uuid}{vm_name} network configuration is ok")
192+
return
193+
194+
invalid_nics_msg = list_to_str(invalid_nics)
195+
print(f"VM {vm_uuid} ⚠️ {vm_name} is connected to the internet on adapter(s): {invalid_nics_msg}")
196+
197+
if modify_and_notify:
198+
# Disable invalid nics
199+
for nic in invalid_nics:
200+
try:
201+
disable_adapter(vm_uuid, nic, hostonly_ifname)
202+
print(f"VM {vm_uuid} ⚙️ {vm_name} set adapter {nic} to {DISABLED_ADAPTER_TYPE}")
203+
except Exception as e:
204+
print(f"VM {vm_uuid}{vm_name} unable to disable adapter {nic}: {e}")
205+
206+
message = (
207+
f"{vm_name} may be connected to the internet on adapter(s): {invalid_nics_msg}."
208+
"The network adapter(s) may have been disabled automatically to prevent an undesired internet connectivity."
209+
"Please double check your VMs settings."
210+
)
135211
# Show notification using PyGObject
136212
Notify.init("VirtualBox adapter check")
137-
notification = Notify.Notification.new(f"INTERNET IN VM: {vm_name}", message, "dialog-error")
213+
notification = Notify.Notification.new(f"⚠️ INTERNET IN VM: {vm_name}", message, "dialog-error")
138214
# Set highest priority
139215
notification.set_urgency(2)
140216
notification.show()
141-
print(f"{vm_name} network configuration not ok, sent notifaction")
142-
return
143-
else:
144-
print(f"{vm_name} network configuration is ok")
145-
return
146217

147218
except Exception as e:
148-
raise Exception("Failed to verify VM adapter configuration") from e
219+
print(f"VM {vm_uuid} {vm_name} ❌ Unable to verify network adapters: {e}")
149220

150221

151222
def main(argv=None):
@@ -160,7 +231,7 @@ def main(argv=None):
160231
parser.add_argument(
161232
"--do_not_modify",
162233
action="store_true",
163-
help="Only print the status of the internet adapters without modifying them.",
234+
help="Only print the status of the internet adapters without modifying them and without showing a notification.",
164235
)
165236
parser.add_argument(
166237
"--dynamic_only",
@@ -169,16 +240,15 @@ def main(argv=None):
169240
)
170241
args = parser.parse_args(args=argv)
171242

172-
try:
173-
hostonly_ifname = ensure_hostonlyif_exists()
174-
vm_uuids = get_vm_uuids(args.dynamic_only)
175-
if len(vm_uuids) > 0:
176-
for vm_name, vm_uuid in vm_uuids:
177-
change_network_adapters_to_hostonly(vm_uuid, vm_name, hostonly_ifname, args.do_not_modify)
178-
else:
179-
print("[Warning ⚠️] No VMs found")
180-
except Exception as e:
181-
print(f"Error verifying dynamic VM hostonly configuration: {e}")
243+
hostonly_ifname = ensure_hostonlyif_exists()
244+
vms = get_vms(args.dynamic_only)
245+
if len(vms) > 0:
246+
for vm_name, vm_uuid in vms:
247+
# Never modify VMs without DYNAMIC_VM_NAME in the name (only check the status)
248+
modify_and_notify = (DYNAMIC_VM_NAME in vm_name) and (not args.do_not_modify)
249+
verify_network_adapters(vm_uuid, vm_name, hostonly_ifname, modify_and_notify)
250+
else:
251+
print("⚠️ No VMs found!")
182252

183253

184254
if __name__ == "__main__":

0 commit comments

Comments
 (0)