Skip to content

Commit f87e636

Browse files
authored
Merge pull request #653 from Ana06/flake8
Add flake8 linter & fix conflict between isort and black
2 parents c02431e + 8a16a61 commit f87e636

File tree

5 files changed

+83
-58
lines changed

5 files changed

+83
-58
lines changed

.github/workflows/linter.yml

+6-2
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,13 @@ jobs:
1313
- name: Checkout code
1414
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
1515
- name: Install dependencies
16-
run: pip install black isort
16+
run: pip install black isort flake8
17+
# Different line limits: Black/isort (120), Flake8 (150).
18+
# Flake8 allows longer lines for better long string readability. Black doesn't enforce string length.
1719
- name: Run black
1820
run: black --line-length=120 --check --diff .
21+
- name: Run flake8
22+
run: flake8 --max-line-length 150
1923
- name: Run isort
20-
run: isort --check --diff .
24+
run: isort --check --diff --line-length 120 .
2125

virtualbox/vbox-adapter-check.py

+33-19
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,31 @@
2020
import textwrap
2121

2222
import gi
23+
from vboxcommon import ensure_hostonlyif_exists, get_vm_state, run_vboxmanage
2324

2425
gi.require_version("Notify", "0.7")
25-
from gi.repository import Notify
26-
from vboxcommon import *
26+
from gi.repository import Notify # noqa: E402
2727

2828
DYNAMIC_VM_NAME = ".dynamic"
2929
DISABLED_ADAPTER_TYPE = "hostonly"
3030
ALLOWED_ADAPTER_TYPES = ("hostonly", "intnet", "none")
3131

32+
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."""
36+
37+
EPILOG = textwrap.dedent(
38+
f"""
39+
Example usage:
40+
# Print status of all interfaces and disable internet access in VMs whose name contain {DYNAMIC_VM_NAME}
41+
vbox-adapter-check.vm
42+
43+
# Print status of all interfaces without modifying any of them
44+
vbox-adapter-check.vm --do_not_modify
45+
"""
46+
)
47+
3248

3349
def get_vm_uuids(dynamic_only):
3450
"""Gets the machine UUID(s) for a given VM name using 'VBoxManage list vms'."""
@@ -46,7 +62,7 @@ def get_vm_uuids(dynamic_only):
4662
if (not dynamic_only) or DYNAMIC_VM_NAME in vm_name:
4763
vm_uuids.append((vm_name, vm_uuid))
4864
except Exception as e:
49-
raise Exception(f"Error finding machines UUIDs") from e
65+
raise Exception("Error finding machines UUIDs") from e
5066
return vm_uuids
5167

5268

@@ -69,7 +85,7 @@ def change_network_adapters_to_hostonly(vm_uuid, vm_name, hostonly_ifname, do_no
6985
# nic8="none"
7086

7187
vminfo = run_vboxmanage(["showvminfo", vm_uuid, "--machinereadable"])
72-
for nic_number, nic_value in re.findall('^nic(\d+)="(\S+)"', vminfo, flags=re.M):
88+
for nic_number, nic_value in re.findall(r'^nic(\d+)="(\S+)"', vminfo, flags=re.M):
7389
if nic_value not in ALLOWED_ADAPTER_TYPES:
7490
nics_with_internet.append(f"nic{nic_number}")
7591
invalid_nics_msg += f"{nic_number} "
@@ -80,7 +96,11 @@ def change_network_adapters_to_hostonly(vm_uuid, vm_name, hostonly_ifname, do_no
8096
if do_not_modify:
8197
message = f"{vm_name} may be connected to the internet on adapter(s): {nic}. Please double check your VMs settings."
8298
else:
83-
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."
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+
)
84104
# different commands are necessary if the machine is running.
85105
if get_vm_state(vm_uuid) == "poweroff":
86106
run_vboxmanage(
@@ -106,7 +126,11 @@ def change_network_adapters_to_hostonly(vm_uuid, vm_name, hostonly_ifname, do_no
106126
if do_not_modify:
107127
message = f"{vm_name} may be connected to the internet on adapter(s): {invalid_nics_msg}. Please double check your VMs settings."
108128
else:
109-
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."
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+
)
110134

111135
# Show notification using PyGObject
112136
Notify.init("VirtualBox adapter check")
@@ -128,19 +152,9 @@ def main(argv=None):
128152
if argv is None:
129153
argv = sys.argv[1:]
130154

131-
epilog = textwrap.dedent(
132-
f"""
133-
Example usage:
134-
# Print status of all internet adapters and disable the adapters with internet access in VMs with {DYNAMIC_VM_NAME} in the name
135-
vbox-adapter-check.vm
136-
137-
# Print status of all internet adapters without modifying any of them
138-
vbox-adapter-check.vm --do_not_modify
139-
"""
140-
)
141155
parser = argparse.ArgumentParser(
142-
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.",
143-
epilog=epilog,
156+
description=DESCRIPTION,
157+
epilog=EPILOG,
144158
formatter_class=argparse.RawDescriptionHelpFormatter,
145159
)
146160
parser.add_argument(
@@ -162,7 +176,7 @@ def main(argv=None):
162176
for vm_name, vm_uuid in vm_uuids:
163177
change_network_adapters_to_hostonly(vm_uuid, vm_name, hostonly_ifname, args.do_not_modify)
164178
else:
165-
print(f"[Warning ⚠️] No VMs found")
179+
print("[Warning ⚠️] No VMs found")
166180
except Exception as e:
167181
print(f"Error verifying dynamic VM hostonly configuration: {e}")
168182

virtualbox/vbox-clean-snapshots.py

+33-26
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,30 @@
1919
import sys
2020
import textwrap
2121

22-
from vboxcommon import *
22+
from vboxcommon import get_vm_state, run_vboxmanage
23+
24+
DESCRIPTION = "Clean a VirtualBox VM up by deleting a snapshot and its children recursively skipping snapshots with a substring in the name."
25+
26+
EPILOG = textwrap.dedent(
27+
"""
28+
Example usage:
29+
# Delete all snapshots excluding the default protected ones (with 'clean' or 'done' in the name, case insensitive) in the 'FLARE-VM.20240604' VM
30+
vbox-clean-snapshots.py FLARE-VM.20240604
31+
32+
# Delete all snapshots that do not include 'clean', 'done', or 'important' (case insensitive) in the name in the 'FLARE-VM.20240604' VM
33+
vbox-clean-snapshots.py FLARE-VM.20240604 --protected_snapshots "clean,done,important"
34+
35+
# Delete the 'Snapshot 3' snapshot and its children recursively skipping the default protected ones in the 'FLARE-VM.20240604' VM
36+
vbox-clean-snapshots.py FLARE-VM.20240604 --root_snapshot "Snapshot 3"
37+
38+
# Delete the 'CLEAN with IDA 8.4"' children snapshots recursively skipping the default protected ones in the 'FLARE-VM.20240604' VM
39+
# NOTE: the 'CLEAN with IDA 8.4' root snapshot is skipped in this case
40+
vbox-clean-snapshots.py FLARE-VM.20240604 --root_snapshot "CLEAN with IDA 8.4"
41+
42+
# Delete all snapshots in the 'FLARE-VM.20240604' VM
43+
vbox-clean-snapshots.py FLARE-VM.20240604 --protected_snapshots ""
44+
"""
45+
)
2346

2447

2548
def is_protected(protected_snapshots, snapshot_name):
@@ -60,7 +83,7 @@ def get_snapshot_children(vm_name, root_snapshot_name, protected_snapshots):
6083
root_snapshot_index = ""
6184
if root_snapshot_name:
6285
# Find root snapshot: first snapshot with name root_snapshot_name (case sensitive)
63-
root_snapshot_regex = f'^SnapshotName(?P<index>(?:-\d+)*)="{root_snapshot_name}"\n'
86+
root_snapshot_regex = rf'^SnapshotName(?P<index>(?:-\d+)*)="{root_snapshot_name}"\n'
6487
root_snapshot = re.search(root_snapshot_regex, snapshots_info, flags=re.M)
6588
if root_snapshot:
6689
root_snapshot_index = root_snapshot["index"]
@@ -69,7 +92,7 @@ def get_snapshot_children(vm_name, root_snapshot_name, protected_snapshots):
6992

7093
# Find all root and child snapshots as (snapshot_name, snapshot_id)
7194
# Children of a snapshot share the same prefix index
72-
index_regex = f"{root_snapshot_index}(?:-\d+)*"
95+
index_regex = rf"{root_snapshot_index}(?:-\d+)*"
7396
snapshot_regex = f'^SnapshotName{index_regex}="(.*?)"\nSnapshotUUID{index_regex}="(.*?)"'
7497
snapshots = re.findall(snapshot_regex, snapshots_info, flags=re.M)
7598

@@ -112,41 +135,25 @@ def main(argv=None):
112135
if argv is None:
113136
argv = sys.argv[1:]
114137

115-
epilog = textwrap.dedent(
116-
"""
117-
Example usage:
118-
# Delete all snapshots that do not include 'clean' or 'done' in the name (case insensitive) in the 'FLARE-VM.20240604' VM
119-
vbox-clean-snapshots.py FLARE-VM.20240604
120-
121-
# Delete all snapshots that do not include 'clean', 'done', or 'important in the name in the 'FLARE-VM.20240604' VM
122-
vbox-clean-snapshots.py FLARE-VM.20240604 --protected_snapshots "clean,done,important"
123-
124-
# Delete the 'CLEAN with IDA 8.4' children snapshots recursively skipping the ones that include 'clean' or 'done' in the name (case insensitive) in the 'FLARE-VM.20240604' VM
125-
# NOTE: the 'CLEAN with IDA 8.4' root snapshot is skipped in this case
126-
vbox-clean-snapshots.py FLARE-VM.20240604 --root_snapshot CLEAN with IDA 8.4
127-
128-
# Delete the 'Snapshot 3' snapshot and its children recursively skipping the ones that include 'clean' or 'done' in the name (case insensitive) in the 'FLARE-VM.20240604' VM
129-
vbox-clean-snapshots.py FLARE-VM.20240604 --root_snapshot Snapshot 3
130-
131-
# Delete all snapshots in the 'FLARE-VM.20240604' VM
132-
vbox-clean-snapshots.py FLARE-VM.20240604 --protected_snapshots ""
133-
"""
134-
)
138+
epilog = EPILOG
135139
parser = argparse.ArgumentParser(
136-
description="Clean a VirtualBox VM up by deleting a snapshot and its children recursively skipping snapshots with a substring in the name.",
140+
description=DESCRIPTION,
137141
epilog=epilog,
138142
formatter_class=argparse.RawDescriptionHelpFormatter,
139143
)
140144
parser.add_argument("vm_name", help="Name of the VM to clean up")
141145
parser.add_argument(
142146
"--root_snapshot",
143-
help="Snapshot name (case sensitive) to delete (and its children recursively). Leave empty to clean all snapshots in the VM.",
147+
help="""Snapshot name (case sensitive) to delete (and its children recursively).
148+
Leave empty to clean all snapshots in the VM.""",
144149
)
145150
parser.add_argument(
146151
"--protected_snapshots",
147152
default="clean,done",
148153
type=lambda s: s.split(","),
149-
help='Comma-separated list of strings. Snapshots with any of the strings included in the name (case insensitive) are not deleted. Default: "clean,done"',
154+
help='''Comma-separated list of strings.
155+
Snapshots with any of the strings included in the name (case insensitive) are not deleted.
156+
Default: "clean,done"''',
150157
)
151158
args = parser.parse_args(args=argv)
152159

virtualbox/vbox-export-snapshots.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@
2020
import re
2121
import sys
2222
import textwrap
23+
import time
2324
from datetime import datetime
2425

2526
import jsonschema
26-
from vboxcommon import *
27+
from vboxcommon import ensure_hostonlyif_exists, ensure_vm_running, ensure_vm_shutdown, run_vboxmanage
2728

28-
DESCRIPTION = "Export one or more snapshots in the same VirtualBox VM as .ova, changing the network to a single Host-Only interface. Generate a file with the SHA256 of the exported OVA(s)."
29+
DESCRIPTION = """Export one or more snapshots in the same VirtualBox VM as .ova, changing the network to a single Host-Only interface.
30+
Generate a file with the SHA256 of the exported OVA(s)."""
2931

3032
EPILOG = textwrap.dedent(
3133
"""
@@ -201,20 +203,18 @@ def main(argv=None):
201203
)
202204
parser.add_argument(
203205
"config_path",
204-
help=textwrap.dedent(
205-
"""
206-
path of the JSON configuration file.
206+
help=""" path of the JSON configuration file.
207207
"VM_NAME" is the name of the VM to export snapshots from.
208208
Example: "FLARE-VM.testing".
209209
"EXPORTED_VM_NAME" is the name of the exported VMs.
210210
Example: "FLARE-VM".
211-
"SNAPSHOTS" is a list of lists with information of the snapshots to export: ["SNAPSHOT_NAME", "EXPORTED_VM_EXTENSION", "DESCRIPTION"].
211+
"SNAPSHOTS" is a list of lists with information of the snapshots to export:
212+
["SNAPSHOT_NAME", "EXPORTED_VM_EXTENSION", "DESCRIPTION"].
212213
Example: ["FLARE-VM", ".dynamic", "Windows 10 VM with FLARE-VM default configuration"].
213214
"EXPORT_DIR_NAME" (optional) is the name of the directory in HOME to export the VMs.
214215
The directory is created if it does not exist.
215216
Default: "EXPORTED VMS".
216-
"""
217-
),
217+
""",
218218
)
219219
args = parser.parse_args(args=argv)
220220

virtualbox/vboxcommon.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def get_hostonlyif_name():
5656
# Name: vboxnet0
5757
hostonlyifs_info = run_vboxmanage(["list", "hostonlyifs"])
5858

59-
match = re.search(f"^Name: *(?P<hostonlyif_name>\S+)", hostonlyifs_info, flags=re.M)
59+
match = re.search(r"^Name: *(?P<hostonlyif_name>\S+)", hostonlyifs_info, flags=re.M)
6060
if match:
6161
return match["hostonlyif_name"]
6262

@@ -73,7 +73,7 @@ def ensure_hostonlyif_exists():
7373
if not hostonlyif_name:
7474
raise RuntimeError("Failed to create new hostonly interface.")
7575

76-
print(f"VM {vm_uuid} Created hostonly interface: {hostonlyif_name}")
76+
print(f"Hostonly interface created: {hostonlyif_name}")
7777

7878
return hostonlyif_name
7979

@@ -84,7 +84,7 @@ def get_vm_state(vm_uuid):
8484
# VMState="poweroff"
8585
vm_info = run_vboxmanage(["showvminfo", vm_uuid, "--machinereadable"])
8686

87-
match = re.search(f'^VMState="(?P<state>\S+)"', vm_info, flags=re.M)
87+
match = re.search(r'^VMState="(?P<state>\S+)"', vm_info, flags=re.M)
8888
if match:
8989
return match["state"]
9090

0 commit comments

Comments
 (0)