Skip to content

Commit 365a471

Browse files
committed
[vbox] Use config file to short command
The command gets very long when exporting many snapshots. Pass the configuration using a JSON file instead of arguments, which also makes it easier to understand how to use the script. Provide several configuration example files in the `configs` directory.
1 parent 8747051 commit 365a471

File tree

5 files changed

+96
-29
lines changed

5 files changed

+96
-29
lines changed

virtualbox/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ It also generates a file with the SHA256 hash of the exported `.ova`.
99
This script is useful to export several versions of FLARE-VM after its installation consistently and with the internet disabled by default (desired for malware analysis).
1010
For example, you may want to export a VM with the default FLARE-VM configuration and another installing in addition the packages `visualstudio.vm` and `pdbs.pdbresym.vm`.
1111
These packages are useful for malware analysis but are not included in the default configuration because of the consequent increase in size.
12+
The scripts receives the path of the JSON configuration file as argument.
13+
See configuration example files in the [`configs`](configs/) directory.
1214

1315
### Example
1416

1517
```
16-
$ ./vbox-export-snapshots.py "FLARE-VM.testing" --snapshot "FLARE-VM,.dynamic,Windows 10 VM with FLARE-VM default configuration" --snapshot "FLARE-VM.full,.full.dynamic,Windows 10 VM with FLARE-VM default configuration + 'visualstudio.vm' + 'pdbs.pdbresym.vm'"
18+
$ ./vbox-export-snapshots.py configs/export_win10_flare-vm.json
1719
1820
Exporting snapshots from "FLARE-VM.testing" {2bc66f50-9ecb-4b10-a4dd-0cc329bc383d}
1921
Export directory: "/home/anamg/EXPORTED VMS"

virtualbox/configs/export_remnux.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"VM_NAME": "REMnux.testing",
3+
"EXPORTED_VM_NAME": "REMnux",
4+
"SNAPSHOTS": [
5+
[
6+
"DONE",
7+
".dynamic",
8+
"REMnux (based on Ubuntu) with improved configuration"
9+
]
10+
]
11+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"VM_NAME": "FLARE-VM.testing",
3+
"EXPORTED_VM_NAME": "FLARE-VM",
4+
"SNAPSHOTS": [
5+
[
6+
"FLARE-VM",
7+
".dynamic",
8+
"Windows 10 VM with FLARE-VM default configuration"
9+
],
10+
[
11+
"FLARE-VM.full",
12+
".full.dynamic",
13+
"Windows 10 VM with FLARE-VM default configuration + visualstudio.vm + pdbs.pdbresym.vm + microsoft-office.vm"
14+
],
15+
[
16+
"FLARE-VM.EDU",
17+
".EDU",
18+
"Windows 10 VM with FLARE-VM default configuration + FLARE-EDU materials"
19+
]
20+
]
21+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"VM_NAME": "FLARE-VM.Win11.testing",
3+
"EXPORTED_VM_NAME": "FLARE-VM",
4+
"SNAPSHOTS": [
5+
[
6+
"FLARE-VM",
7+
".win11.dynamic",
8+
"Windows 11 VM with FLARE-VM default configuration"
9+
],
10+
[
11+
"FLARE-VM.full",
12+
"win11.full.dynamic",
13+
"Windows 11 VM with FLARE-VM default configuration + visualstudio.vm + pdbs.pdbresym.vm + microsoft-office.vm"
14+
]
15+
]
16+
}

virtualbox/vbox-export-snapshots.py

Lines changed: 45 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,23 @@
1515

1616
import argparse
1717
import hashlib
18+
import json
1819
import os
1920
import re
2021
import sys
2122
import textwrap
2223
from datetime import datetime
2324

25+
import jsonschema
2426
from vboxcommon import *
2527

26-
2728
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)."
2829

2930
EPILOG = textwrap.dedent(
3031
"""
3132
Example usage:
32-
# Export the "FLARE-VM" snapshot from the "FLARE-VM.testing" VM
33-
vbox-export-snapshots.py "FLARE-VM.testing" --snapshot "FLARE-VM,.dynamic,Windows 10 VM with FLARE-VM default configuration"
34-
35-
# Export three snapshots from the "FLARE-VM.testing" VM
36-
vbox-export-snapshots.py "FLARE-VM.testing" --snapshot "FLARE-VM,.dynamic,Windows 10 VM with FLARE-VM default configuration" --snapshot "FLARE-VM.full,.full.dynamic,Windows 10 VM with FLARE-VM default configuration + 'visualstudio.vm' + 'pdbs.pdbresym.vm'" --snapshot "FLARE-VM.EDU,.EDU,Windows 10 VM with FLARE-VM default configuration + FLARE-EDU materials"
37-
38-
# Export the "DONE" snapshot from the "REMnux.testing" VM with name "REMnux"
39-
vbox-export-snapshots.py "REMnux.testing" --exported_vm_name "REMnux" --snapshot "DONE,.dynamic,REMnux (based on Ubuntu) with improved configuration"
33+
# Export snapshots using the information in the "configs/export_win10_flare-vm.json" config file
34+
./vbox-export-snapshots.py configs/export_win10_flare-vm.json
4035
"""
4136
)
4237

@@ -48,6 +43,23 @@
4843
LONG_WAIT = "... (it will take some time, go for an 🍦!)"
4944

5045

46+
# Format of snapshot information in the configuration file whose path is provided as argument
47+
snapshotsSchema = {
48+
"type": "object",
49+
"properties": {
50+
"VM_NAME": {"type": "string"},
51+
"EXPORTED_VM_NAME": {"type": "string"},
52+
"SNAPSHOTS": {
53+
"type": "array",
54+
"items": {"type": "array", "items": {"type": "string"}, "minItems": 3, "maxItems": 3},
55+
"minItems": 1,
56+
},
57+
"EXPORT_DIR_NAME": {"type": "string"},
58+
},
59+
"required": ["VM_NAME", "EXPORTED_VM_NAME", "SNAPSHOTS"],
60+
}
61+
62+
5163
def sha256_file(filename):
5264
with open(filename, "rb") as f:
5365
return hashlib.file_digest(f, "sha256").hexdigest()
@@ -116,7 +128,7 @@ def restore_snapshot(vm_uuid, snapshot_name):
116128
print(f'VM {vm_uuid} ✨ restored snapshot "{snapshot_name}"')
117129

118130

119-
def export_snapshots(vm_name, export_dir_name, exported_vm_name, snapshots):
131+
def export_snapshots(vm_name, exported_vm_name, snapshots, export_dir_name):
120132
date = datetime.today().strftime("%Y%m%d")
121133

122134
vm_uuid = get_vm_uuid(vm_name)
@@ -187,31 +199,36 @@ def main(argv=None):
187199
epilog=EPILOG,
188200
formatter_class=argparse.RawDescriptionHelpFormatter,
189201
)
190-
parser.add_argument("vm_name", help="name of the VM to export snapshots from.")
191-
parser.add_argument("--exported_vm_name", help="name of the exported VMs.", default="FLARE-VM")
192202
parser.add_argument(
193-
"--export_dir_name",
194-
help="name of the directory in HOME to export the VMs. The directory is created if it does not exist.",
195-
default="EXPORTED VMS",
196-
)
197-
parser.add_argument(
198-
"--snapshot",
199-
type=lambda s: s.split(","),
200-
action="append",
203+
"config_path",
201204
help=textwrap.dedent(
202205
"""
203-
comma-separated list of strings with the information of the snapshot to export: "<SNAPSHOT_NAME>,<EXPORTED_VM_EXTENSION>,<DESCRIPTION>".
204-
<SNAPSHOT_NAME> is the name of the snapshot to export.
205-
<EXPORTED_VM_EXTENSION> is appended to the exported VM name: "<EXPORTED_VM_NAME>.<DATE>.<EXPORTED_VM_EXTENSION>".
206-
<DESCRIPTION> is used as the appliance description.
207-
Example: "FLARE-VM,.dynamic,Windows 10 VM with FLARE-VM default configuration installed".
208-
This argument can be provided more than one to export several snapshots, every of them in a different appliance.
209-
"""
206+
path of the JSON configuration file.
207+
"VM_NAME" is the name of the VM to export snapshots from.
208+
Example: "FLARE-VM.testing".
209+
"EXPORTED_VM_NAME" is the name of the exported VMs.
210+
Example: "FLARE-VM".
211+
"SNAPSHOTS" is a list of lists with information of the snapshots to export: ["SNAPSHOT_NAME", "EXPORTED_VM_EXTENSION", "DESCRIPTION"].
212+
Example: ["FLARE-VM", ".dynamic", "Windows 10 VM with FLARE-VM default configuration"].
213+
"EXPORT_DIR_NAME" (optional) is the name of the directory in HOME to export the VMs.
214+
The directory is created if it does not exist.
215+
Default: "EXPORTED VMS".
216+
"""
210217
),
211218
)
212219
args = parser.parse_args(args=argv)
213220

214-
export_snapshots(args.vm_name, args.export_dir_name, args.exported_vm_name, args.snapshot)
221+
try:
222+
with open(args.config_path) as f:
223+
config = json.load(f)
224+
225+
jsonschema.validate(instance=config, schema=snapshotsSchema)
226+
except Exception as e:
227+
print(f'Invalid "{args.config_path}": {e}')
228+
exit()
229+
230+
export_dir_name = config.get("EXPORT_DIR_NAME", "EXPORTED VMS")
231+
export_snapshots(config["VM_NAME"], config["EXPORTED_VM_NAME"], config["SNAPSHOTS"], export_dir_name)
215232

216233

217234
if __name__ == "__main__":

0 commit comments

Comments
 (0)