diff --git a/artifacts/Windows.QEMU.ImgConvert.yaml b/artifacts/Windows.QEMU.ImgConvert.yaml new file mode 100644 index 0000000..781e686 --- /dev/null +++ b/artifacts/Windows.QEMU.ImgConvert.yaml @@ -0,0 +1,75 @@ +name: Windows.QEMU.ImgConvert + +description: | + Downloads **QEMU image converter** and its required dynamic libraries compressed inside a `qemu-img.zip` file. Unzips the archive then launches the `qemu-img.exe` executable to convert the source file to the desired format. + + This artifact requires you to upload a ZIP archive containing `qemu-img.exe` and its required DLLs on the Velociraptor server, in order to serve it locally to the endpoints. + + **HOW TO USE:** + + 1. Create the `qemu-img.zip` archive + 2. In the "*Tools*" section, click on "*QEMUImgZip*" + 3. Click on "*Select file*" to browse your file system and select the archive + 4. Click on "*Click to upload file*" + 5. Ensure the calculated hash is correct + +author: Synacktiv, Maxence Fossat - @cybiosity + +reference: + - https://www.qemu.org/download/#windows + - https://qemu.weilnetz.de/w64/ + +type: CLIENT + +tools: + - name: QEMUImgZip + serve_locally: true + +precondition: SELECT OS FROM info() WHERE OS = 'windows' + +parameters: + - name: Source + type: string + description: Absolute path to the source file that should be converted. + - name: Destination + type: string + description: Absolute path to the destination file, including filename. + - name: OutputFormat + type: string + description: Destination format for the conversion. + default: 'raw' + +required_permissions: + - FILESYSTEM_READ + - FILESYSTEM_WRITE + - EXECVE + +sources: + - query: | + // Get the path to the zipfile + LET zipfile <= SELECT * FROM Artifact.Generic.Utils.FetchBinary( + ToolName= 'QEMUImgZip', + IsExecutable=FALSE + ) + + // Build temporary folder to unzip + LET tempfolder <= tempdir() + + // Unzip qemu-img.zip + LET unzipping <= SELECT * FROM unzip( + filename=zipfile.OSPath[0], + accessor='auto', + output_directory=tempfolder, + type='zip' + ) + + // Call the binary and return its output in rows + SELECT Destination, * FROM execve( + argv=[ + path_join(components=[tempfolder, 'qemu-img.exe']), + 'convert', + Source, + '-O', OutputFormat, + Destination + ], sep='\n' + ) diff --git a/artifacts/Windows.Veeam.Extract.yaml b/artifacts/Windows.Veeam.Extract.yaml new file mode 100644 index 0000000..437768f --- /dev/null +++ b/artifacts/Windows.Veeam.Extract.yaml @@ -0,0 +1,69 @@ +name: Windows.Veeam.Extract + +description: | + Downloads and launches the **Veeam extract utility** on a Veeam full backup file (`.vbk`), incremental backup file (`.vib`) or reverse incremental backup file (`.vrb`). + + This artifact requires you to upload the CLI Veeam extract utility binary for Windows on the Velociraptor server, in order to serve it locally to the endpoints. + + **HOW TO USE:** + + 1. Download the CLI Veeam extract utility (`Extract.exe`) using a registered Veeam account + 2. In the "*Tools*" section, click on "*VeeamExtract*" + 3. Click on "*Select file*" to browse your file system and select the binary + 4. Click on "*Click to upload file*" + 5. Ensure the calculated hash is correct to make sure you will launch the right binary + +author: Synacktiv, Maxence Fossat - @cybiosity + +reference: + - https://helpcenter.veeam.com/docs/backup/vsphere/extract_utility.html + +type: CLIENT + +tools: + - name: VeeamExtract + serve_locally: true + +precondition: SELECT OS FROM info() WHERE OS = 'windows' + +parameters: + - name: BackupFilePath + type: string + description: Absolute path to the backup file that needs to be extracted. + - name: OutputFolderPath + type: string + description: Absolute path to the output folder where the extracted image will be placed. + - name: VMName + type: string + description: Name of the machine to extract from the backup. Can be found in "VMName" field from the "Windows.Veeam.RestorePoints" artifacts. + - name: HostName + type: string + description: Name of the backup host. Can be found in "HostName" field from the "Windows.Veeam.RestorePoints" artifacts. + +required_permissions: + - FILESYSTEM_READ + - FILESYSTEM_WRITE + - EXECVE + +sources: + - query: | + // Get the path to the binary + LET binary <= SELECT * FROM Artifact.Generic.Utils.FetchBinary( + ToolName= 'VeeamExtract') + + // Build argv array for execve + LET parameters <= (binary.OSPath[0], '-restore') + LET parameters <= if( + condition=VMName, + then=parameters + ('-vm', VMName), + else=parameters) + LET parameters <= if( + condition=HostName, + then=parameters + ('-host', HostName), + else=parameters) + LET parameters <= parameters + (BackupFilePath, OutputFolderPath) + + // Call the binary and return its output in rows + SELECT utf16(string=Stdout[1:]) AS Stdout, utf16(string=Stderr[1:]) AS Stderr + FROM execve(argv=parameters, sep='\n') + diff --git a/artifacts/Windows.Veeam.ProcessBackups.yaml b/artifacts/Windows.Veeam.ProcessBackups.yaml new file mode 100644 index 0000000..b83c047 --- /dev/null +++ b/artifacts/Windows.Veeam.ProcessBackups.yaml @@ -0,0 +1,133 @@ +name: Windows.Veeam.ProcessBackups + +description: | + **⚠️ USE WITH CAUTION ⚠️️** This artifact is not production-ready and should be considered as a proof-of-concept. It will write gigabytes of data inside the folder specified in `TemporaryFolderPath` parameter and then proceed to delete the files it has written. This artifact was tested on a very limited set of data and only aims to showcase the possibility of a remote Veeam forensics pipeline. + + Selects Veeam backups to extract based on filters provided in the `Filter` parameter. For each selected Veeam backup, extracts the data, converts it to raw disk image format if necessary, remaps paths to the correct partition in the disk image and launches a list of Velociraptor artifacts defined in `Windows.Veeam.ProcessDiskImage`. + +author: Synacktiv, Maxence Fossat - @cybiosity + +type: CLIENT + +parameters: + - name: UseMetadataFiles + description: Enable to list Restore Points using Veeam backup chain metadata files. Disable to use backup files directly (slower). + type: bool + default: TRUE + - name: BackupRepositories + description: List of Backup Repositories where ".vbm" or ".vbk", ".vib" and ".vrb" files should be looked for. + type: csv + default: | + BackupRepoPath + C:/BackupRepo1 + D:/BackupRepo2 + - name: Filter + type: string + description: VQL-format content of a "WHERE" clause to filter results from a "Windows.Veeam.RestorePoints" artifact. + default: '' + - name: TemporaryFolderPath + type: string + description: Absolute path to the temporary folder where the backups will be extracted. + default: 'E:/TempOutput' + +precondition: SELECT OS FROM info() WHERE OS = 'windows' + +required_permissions: + - FILESYSTEM_READ + - FILESYSTEM_WRITE + - EXECVE + +sources: + - query: | + // Hacky way to make sure the deaddisk image processing artifact is embedded + LET _ = SELECT * FROM Artifact.Exchange.Windows.Veeam.ProcessDiskImage() + + // Change RestorePoints artifact according to UseMetadataFiles parameter + LET restore_points_query <= if( + condition=UseMetadataFiles, + then='SELECT * FROM Artifact.Exchange.Windows.Veeam.RestorePoints.MetadataFiles(BackupRepositories=BackupRepositories)', + else='SELECT * FROM Artifact.Exchange.Windows.Veeam.RestorePoints.BackupFiles(BackupRepositories=BackupRepositories)' + ) + + // Apply WHERE clause to the query based on the Filter parameter + LET filtered_query <= if( + condition=Filter, + then=restore_points_query + ' WHERE ' + Filter, + else=restore_points_query + ) + + // Execute constructed query to list selected Restore Points + LET selected_restore_points = SELECT BackupFilePath, VMName, HostName, CreationTimeUTC, ExtractName, ExtractID + FROM query(query=filtered_query, inherit=TRUE) + + // Extract data from backup and check for statement of success + LET extract = SELECT BackupFilePath, VMName, HostName, CreationTimeUTC, ExtractName, ExtractID, TemporaryFolderPath, * + FROM Artifact.Exchange.Windows.Veeam.Extract( + BackupFilePath=BackupFilePath, + OutputFolderPath=TemporaryFolderPath, + VMName=VMName, + HostName=HostName + ) WHERE Stdout =~ VMName+' is restored' + + // List extracted disks in .vhdx, .vmdk and raw format + LET extracted_files = SELECT * FROM glob( + globs='/*', + root=path_join(components=[TemporaryFolderPath, ExtractName+'('+ExtractID+')']), + accessor='auto' + ) WHERE Name =~ '^(.*\.vhdx|.*-flat\.vmdk|[0-9A-F]{8})$' + + // Convert files to raw disk image format, when applicable, and check for success + LET converted_files = SELECT BackupFilePath, VMName, HostName, CreationTimeUTC, * FROM if( + condition= Name =~ '^(.*\.vhdx|.*-flat\.vmdk)$', + then={ + SELECT * FROM Artifact.Exchange.Windows.QEMU.ImgConvert( + Source=OSPath, + Destination=path_join(components=[TemporaryFolderPath, Name+'.raw']), + OutputFormat='raw' + ) WHERE Complete = TRUE AND ReturnCode = 0 + }, + else={ SELECT OSPath as Destination FROM scope() } + ) + + SELECT * FROM foreach(row=selected_restore_points, + query={ + SELECT * FROM foreach(row=extract, + query={ + SELECT * FROM chain(a={SELECT * FROM foreach(row=extracted_files, + query={ + SELECT * FROM foreach(row=converted_files, + query={ + SELECT * FROM chain(a={SELECT * FROM query( + query={ + SELECT * FROM Artifact.Exchange.Windows.Veeam.ProcessDiskImage( + DiskImagePath=Destination, + VMName=VMName, + HostName=HostName, + CreationTimeUTC=CreationTimeUTC, + BackupFilePath=BackupFilePath + ) + }, + env=dict( + Artifact=Artifact, + Destination=Destination, + VMName=VMName, + HostName=HostName, + CreationTimeUTC=CreationTimeUTC, + BackupFilePath=BackupFilePath + ))}, + b={ + SELECT 'POST_PROCESSING' AS ArtifactName, + rm(filename=Destination) AS DeletedDiskImage, + HostName, VMName, CreationTimeUTC, BackupFilePath + FROM scope() }) + }) + })}, + b={ + SELECT 'POST_PROCESSING' AS ArtifactName, + HostName, VMName, CreationTimeUTC, BackupFilePath, * + FROM Artifact.Windows.System.PowerShell( + Command='Remove-Item -Recurse -Force "'+str(str=path_join(components=(TemporaryFolderPath, ExtractName+'('+ExtractID+')')))+'"' + )} + ) + }) + }) diff --git a/artifacts/Windows.Veeam.ProcessDiskImage.yaml b/artifacts/Windows.Veeam.ProcessDiskImage.yaml new file mode 100644 index 0000000..302dad8 --- /dev/null +++ b/artifacts/Windows.Veeam.ProcessDiskImage.yaml @@ -0,0 +1,270 @@ +name: Windows.Veeam.ProcessDiskImage + +description: | + Launches a list of artifacts (**specified at the end of the query**) on a raw disk image. This artifact is meant to be launched as a dependency to `Windows.Veeam.ProcessBackups`. + + **⚠️ WARNING ⚠️️** Due to limitations in how this artifact is designed, you need to specify the list of artifacts to launch directly inside the artifact definition, effectively modifying the artifact. There is no way around this for now. + +author: Synacktiv, Maxence Fossat - @cybiosity + +type: CLIENT + +parameters: + - name: DiskImagePath + type: string + description: Absolute path to the raw disk image. + default: '' + - name: VMName + type: string + description: Hostname of the VM whose disk is analyzed. + default: 'DEFAULT_VMNAME' + - name: HostName + type: string + description: Name of the hypervisor which hosted the VM whose disk is analyzed. + default: 'DEFAULT_HOSTNAME' + - name: CreationTimeUTC + type: string + description: Creation time of the backup in UTC. + - name: BackupFilePath + type: string + description: Path to the backup file from which the disk was extracted. + +precondition: SELECT OS FROM info() WHERE OS = 'windows' + +sources: + - query: | + // Find a partition with a Windows directory at the top level + LET win_partition = SELECT StartOffset + FROM Artifact.Windows.Forensics.PartitionTable( + ImagePath=DiskImagePath, + Accessor='auto' + ) WHERE 'Windows' IN TopLevelDirectory + + LET escaped_path <= regex_replace( + source=DiskImagePath, + re='''\\''', + replace='/' + ) + + LET configuration <= format(format=''' + remappings: + - type: permissions + permissions: + - COLLECT_CLIENT + - FILESYSTEM_READ + - FILESYSTEM_WRITE + - READ_RESULTS + - MACHINE_STATE + - SERVER_ADMIN + - type: impersonation + os: windows + hostname: "%[1]v" + env: + - key: SystemRoot + value: C:\Windows + - key: WinDir + value: C:\Windows + disabled_functions: + - amsi + - lookupSID + - token + disabled_plugins: + - users + - certificates + - handles + - pslist + - interfaces + - modules + - netstat + - partitions + - proc_dump + - proc_yara + - vad + - winobj + - wmi + - type: mount + from: + accessor: raw_ntfs + prefix: | + { + "DelegateAccessor": "offset", + "Delegate": { + "DelegateAccessor": "file", + "DelegatePath": "%[2]v", + "Path": "%[3]v" + }, + "Path": "/" + } + "on": + accessor: ntfs + prefix: '\\.\C:' + path_type: ntfs + - type: mount + from: + accessor: raw_ntfs + prefix: | + { + "DelegateAccessor": "offset", + "Delegate": { + "DelegateAccessor": "file", + "DelegatePath": "%[2]v", + "Path": "%[3]v" + }, + "Path": "/" + } + "on": + accessor: file + prefix: 'C:' + path_type: windows + - type: mount + from: + accessor: raw_ntfs + prefix: | + { + "DelegateAccessor": "offset", + "Delegate": { + "DelegateAccessor": "file", + "DelegatePath": "%[2]v", + "Path": "%[3]v" + }, + "Path": "/" + } + "on": + accessor: auto + prefix: 'C:' + path_type: windows + - type: mount + from: + accessor: raw_reg + prefix: |- + { + "Path": "/", + "DelegateAccessor": "raw_ntfs", + "Delegate": { + "DelegateAccessor":"offset", + "Delegate": { + "DelegateAccessor": "file", + "DelegatePath": "%[2]v", + "Path": "%[3]v" + }, + "Path":"/Windows/System32/Config/SOFTWARE" + } + } + path_type: registry + "on": + accessor: registry + prefix: HKEY_LOCAL_MACHINE\Software + path_type: registry + - type: mount + from: + accessor: raw_reg + prefix: |- + { + "Path": "/", + "DelegateAccessor": "raw_ntfs", + "Delegate": { + "DelegateAccessor":"offset", + "Delegate": { + "DelegateAccessor": "file", + "DelegatePath": "%[2]v", + "Path": "%[3]v" + }, + "Path":"/Windows/System32/Config/SYSTEM" + } + } + path_type: registry + "on": + accessor: registry + prefix: HKEY_LOCAL_MACHINE\System + path_type: registry + - type: shadow + from: + accessor: zip + "on": + accessor: zip + - type: shadow + from: + accessor: raw_reg + "on": + accessor: raw_reg + - type: shadow + from: + accessor: data + "on": + accessor: data + ''', args=[ + VMName, + escaped_path, + win_partition.StartOffset[0] + ]) + + // Remap so that artifacts are launched on the deaddisk image + LET _ <= remap(config=configuration, clear=TRUE) + + // ======================================================== + // === ↓ CHANGE THIS SECTION ACCORDING TO YOUR NEEDS ↓ === + // ======================================================== + + SELECT * FROM chain( + a={ + SELECT HostName, VMName, CreationTimeUTC, BackupFilePath, 'Generic.Forensic.SQLiteHunter' AS ArtifactName, * + FROM Artifact.Generic.Forensic.SQLiteHunter() + }, + b={ + SELECT HostName, VMName, CreationTimeUTC, BackupFilePath, 'Windows.Forensics.Bam' AS ArtifactName, * + FROM Artifact.Windows.Forensics.Bam() + }, + c={ + SELECT HostName, VMName, CreationTimeUTC, BackupFilePath, 'Windows.Forensics.Shellbags' AS ArtifactName, * + FROM Artifact.Windows.Forensics.Shellbags() + }, + d={ + SELECT HostName, VMName, CreationTimeUTC, BackupFilePath, 'Windows.Forensics.SRUM' AS ArtifactName, * + FROM Artifact.Windows.Forensics.SRUM() + }, + e={ + SELECT HostName, VMName, CreationTimeUTC, BackupFilePath, 'Windows.Forensics.SAM' AS ArtifactName, * + FROM Artifact.Windows.Forensics.SAM() + }, + f={ + SELECT HostName, VMName, CreationTimeUTC, BackupFilePath, 'Windows.Registry.AppCompatCache' AS ArtifactName, * + FROM Artifact.Windows.Registry.AppCompatCache() + }, + g={ + SELECT HostName, VMName, CreationTimeUTC, BackupFilePath, 'Windows.Registry.UserAssist' AS ArtifactName, * + FROM Artifact.Windows.Registry.UserAssist() + }, + h={ + SELECT HostName, VMName, CreationTimeUTC, BackupFilePath, 'Windows.Registry.RecentDocs' AS ArtifactName, * + FROM Artifact.Windows.Registry.RecentDocs() + }, + i={ + SELECT HostName, VMName, CreationTimeUTC, BackupFilePath, 'Windows.Detection.Amcache' AS ArtifactName, * + FROM Artifact.Windows.Detection.Amcache() + }, + j={ + SELECT HostName, VMName, CreationTimeUTC, BackupFilePath, 'Windows.Timeline.Prefetch' AS ArtifactName, * + FROM Artifact.Windows.Timeline.Prefetch() + }, + k={ + SELECT HostName, VMName, CreationTimeUTC, BackupFilePath, 'Windows.Timeline.Registry.RunMRU' AS ArtifactName, * + FROM Artifact.Windows.Timeline.Registry.RunMRU() + }, + l={ + SELECT HostName, VMName, CreationTimeUTC, BackupFilePath, 'Windows.Applications.OfficeMacros' AS ArtifactName, * + FROM Artifact.Windows.Applications.OfficeMacros() + }, + m={ + SELECT HostName, VMName, CreationTimeUTC, BackupFilePath, 'Windows.Sys.StartupItems' AS ArtifactName, * + FROM Artifact.Windows.Sys.StartupItems() + }, + n={ + SELECT HostName, VMName, CreationTimeUTC, BackupFilePath, 'Windows.Sys.Users' AS ArtifactName, * + FROM Artifact.Windows.Sys.Users() + }, + o={ + SELECT HostName, VMName, CreationTimeUTC, BackupFilePath, 'Windows.EventLogs.RDPAuth' AS ArtifactName, * + FROM Artifact.Windows.EventLogs.RDPAuth() + } + ) + diff --git a/tools/qemu-img.zip b/tools/qemu-img.zip new file mode 100644 index 0000000..bc8565f Binary files /dev/null and b/tools/qemu-img.zip differ