· 6 years ago · May 28, 2019, 01:02 AM
1#requires -version 2
2function New-InMemoryModule
3{
4
5 Param
6 (
7 [Parameter(Position = 0)]
8 [ValidateNotNullOrEmpty()]
9 [String]
10 $ModuleName = [Guid]::NewGuid().ToString()
11 )
12
13 $LoadedAssemblies = [AppDomain]::CurrentDomain.GetAssemblies()
14
15 ForEach ($Assembly in $LoadedAssemblies) {
16 if ($Assembly.FullName -and ($Assembly.FullName.Split(',')[0] -eq $ModuleName)) {
17 return $Assembly
18 }
19 }
20
21 $DynAssembly = New-Object Reflection.AssemblyName($ModuleName)
22 $Domain = [AppDomain]::CurrentDomain
23 $AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, 'Run')
24 $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule($ModuleName, $False)
25
26 return $ModuleBuilder
27}
28
29
30function func
31{
32 Param
33 (
34 [Parameter(Position = 0, Mandatory = $True)]
35 [String]
36 $DllName,
37
38 [Parameter(Position = 1, Mandatory = $True)]
39 [String]
40 $FunctionName,
41
42 [Parameter(Position = 2, Mandatory = $True)]
43 [Type]
44 $ReturnType,
45
46 [Parameter(Position = 3)]
47 [Type[]]
48 $ParameterTypes,
49
50 [Parameter(Position = 4)]
51 [Runtime.InteropServices.CallingConvention]
52 $NativeCallingConvention,
53
54 [Parameter(Position = 5)]
55 [Runtime.InteropServices.CharSet]
56 $Charset,
57
58 [Switch]
59 $SetLastError
60 )
61
62 $Properties = @{
63 DllName = $DllName
64 FunctionName = $FunctionName
65 ReturnType = $ReturnType
66 }
67
68 if ($ParameterTypes) { $Properties['ParameterTypes'] = $ParameterTypes }
69 if ($NativeCallingConvention) { $Properties['NativeCallingConvention'] = $NativeCallingConvention }
70 if ($Charset) { $Properties['Charset'] = $Charset }
71 if ($SetLastError) { $Properties['SetLastError'] = $SetLastError }
72
73 New-Object PSObject -Property $Properties
74}
75
76
77function Add-Win32Type
78{
79
80
81 [OutputType([Hashtable])]
82 Param(
83 [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)]
84 [String]
85 $DllName,
86
87 [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)]
88 [String]
89 $FunctionName,
90
91 [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)]
92 [Type]
93 $ReturnType,
94
95 [Parameter(ValueFromPipelineByPropertyName = $True)]
96 [Type[]]
97 $ParameterTypes,
98
99 [Parameter(ValueFromPipelineByPropertyName = $True)]
100 [Runtime.InteropServices.CallingConvention]
101 $NativeCallingConvention = [Runtime.InteropServices.CallingConvention]::StdCall,
102
103 [Parameter(ValueFromPipelineByPropertyName = $True)]
104 [Runtime.InteropServices.CharSet]
105 $Charset = [Runtime.InteropServices.CharSet]::Auto,
106
107 [Parameter(ValueFromPipelineByPropertyName = $True)]
108 [Switch]
109 $SetLastError,
110
111 [Parameter(Mandatory = $True)]
112 [ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})]
113 $Module,
114
115 [ValidateNotNull()]
116 [String]
117 $Namespace = ''
118 )
119
120 BEGIN
121 {
122 $TypeHash = @{}
123 }
124
125 PROCESS
126 {
127 if ($Module -is [Reflection.Assembly])
128 {
129 if ($Namespace)
130 {
131 $TypeHash[$DllName] = $Module.GetType("$Namespace.$DllName")
132 }
133 else
134 {
135 $TypeHash[$DllName] = $Module.GetType($DllName)
136 }
137 }
138 else
139 {
140 # Define one type for each DLL
141 if (!$TypeHash.ContainsKey($DllName))
142 {
143 if ($Namespace)
144 {
145 $TypeHash[$DllName] = $Module.DefineType("$Namespace.$DllName", 'Public,BeforeFieldInit')
146 }
147 else
148 {
149 $TypeHash[$DllName] = $Module.DefineType($DllName, 'Public,BeforeFieldInit')
150 }
151 }
152
153 $Method = $TypeHash[$DllName].DefineMethod(
154 $FunctionName,
155 'Public,Static,PinvokeImpl',
156 $ReturnType,
157 $ParameterTypes)
158
159 # Make each ByRef parameter an Out parameter
160 $i = 1
161 ForEach($Parameter in $ParameterTypes)
162 {
163 if ($Parameter.IsByRef)
164 {
165 [void] $Method.DefineParameter($i, 'Out', $Null)
166 }
167
168 $i++
169 }
170
171 $DllImport = [Runtime.InteropServices.DllImportAttribute]
172 $SetLastErrorField = $DllImport.GetField('SetLastError')
173 $CallingConventionField = $DllImport.GetField('CallingConvention')
174 $CharsetField = $DllImport.GetField('CharSet')
175 if ($SetLastError) { $SLEValue = $True } else { $SLEValue = $False }
176
177 # Equivalent to C# version of [DllImport(DllName)]
178 $Constructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor([String])
179 $DllImportAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($Constructor,
180 $DllName, [Reflection.PropertyInfo[]] @(), [Object[]] @(),
181 [Reflection.FieldInfo[]] @($SetLastErrorField, $CallingConventionField, $CharsetField),
182 [Object[]] @($SLEValue, ([Runtime.InteropServices.CallingConvention] $NativeCallingConvention), ([Runtime.InteropServices.CharSet] $Charset)))
183
184 $Method.SetCustomAttribute($DllImportAttribute)
185 }
186 }
187
188 END
189 {
190 if ($Module -is [Reflection.Assembly])
191 {
192 return $TypeHash
193 }
194
195 $ReturnTypes = @{}
196
197 ForEach ($Key in $TypeHash.Keys)
198 {
199 $Type = $TypeHash[$Key].CreateType()
200
201 $ReturnTypes[$Key] = $Type
202 }
203
204 return $ReturnTypes
205 }
206}
207
208
209function psenum
210{
211<#
212 .SYNOPSIS
213
214 Creates an in-memory enumeration for use in your PowerShell session.
215
216 Author: Matthew Graeber (@mattifestation)
217 License: BSD 3-Clause
218 Required Dependencies: None
219 Optional Dependencies: None
220
221 .DESCRIPTION
222
223 The 'psenum' function facilitates the creation of enums entirely in
224 memory using as close to a "C style" as PowerShell will allow.
225
226 .PARAMETER Module
227
228 The in-memory module that will host the enum. Use
229 New-InMemoryModule to define an in-memory module.
230
231 .PARAMETER FullName
232
233 The fully-qualified name of the enum.
234
235 .PARAMETER Type
236
237 The type of each enum element.
238
239 .PARAMETER EnumElements
240
241 A hashtable of enum elements.
242
243 .PARAMETER Bitfield
244
245 Specifies that the enum should be treated as a bitfield.
246
247 .EXAMPLE
248
249 $Mod = New-InMemoryModule -ModuleName Win32
250
251 $ImageSubsystem = psenum $Mod PE.IMAGE_SUBSYSTEM UInt16 @{
252 UNKNOWN = 0
253 NATIVE = 1 # Image doesn't require a subsystem.
254 WINDOWS_GUI = 2 # Image runs in the Windows GUI subsystem.
255 WINDOWS_CUI = 3 # Image runs in the Windows character subsystem.
256 OS2_CUI = 5 # Image runs in the OS/2 character subsystem.
257 POSIX_CUI = 7 # Image runs in the Posix character subsystem.
258 NATIVE_WINDOWS = 8 # Image is a native Win9x driver.
259 WINDOWS_CE_GUI = 9 # Image runs in the Windows CE subsystem.
260 EFI_APPLICATION = 10
261 EFI_BOOT_SERVICE_DRIVER = 11
262 EFI_RUNTIME_DRIVER = 12
263 EFI_ROM = 13
264 XBOX = 14
265 WINDOWS_BOOT_APPLICATION = 16
266 }
267
268 .NOTES
269
270 PowerShell purists may disagree with the naming of this function but
271 again, this was developed in such a way so as to emulate a "C style"
272 definition as closely as possible. Sorry, I'm not going to name it
273 New-Enum. :P
274#>
275
276 [OutputType([Type])]
277 Param
278 (
279 [Parameter(Position = 0, Mandatory = $True)]
280 [ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})]
281 $Module,
282
283 [Parameter(Position = 1, Mandatory = $True)]
284 [ValidateNotNullOrEmpty()]
285 [String]
286 $FullName,
287
288 [Parameter(Position = 2, Mandatory = $True)]
289 [Type]
290 $Type,
291
292 [Parameter(Position = 3, Mandatory = $True)]
293 [ValidateNotNullOrEmpty()]
294 [Hashtable]
295 $EnumElements,
296
297 [Switch]
298 $Bitfield
299 )
300
301 if ($Module -is [Reflection.Assembly])
302 {
303 return ($Module.GetType($FullName))
304 }
305
306 $EnumType = $Type -as [Type]
307
308 $EnumBuilder = $Module.DefineEnum($FullName, 'Public', $EnumType)
309
310 if ($Bitfield)
311 {
312 $FlagsConstructor = [FlagsAttribute].GetConstructor(@())
313 $FlagsCustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($FlagsConstructor, @())
314 $EnumBuilder.SetCustomAttribute($FlagsCustomAttribute)
315 }
316
317 ForEach ($Key in $EnumElements.Keys)
318 {
319 # Apply the specified enum type to each element
320 $Null = $EnumBuilder.DefineLiteral($Key, $EnumElements[$Key] -as $EnumType)
321 }
322
323 $EnumBuilder.CreateType()
324}
325
326
327# A helper function used to reduce typing while defining struct
328# fields.
329function field
330{
331 Param
332 (
333 [Parameter(Position = 0, Mandatory = $True)]
334 [UInt16]
335 $Position,
336
337 [Parameter(Position = 1, Mandatory = $True)]
338 [Type]
339 $Type,
340
341 [Parameter(Position = 2)]
342 [UInt16]
343 $Offset,
344
345 [Object[]]
346 $MarshalAs
347 )
348
349 @{
350 Position = $Position
351 Type = $Type -as [Type]
352 Offset = $Offset
353 MarshalAs = $MarshalAs
354 }
355}
356
357
358function struct
359{
360<#
361 .SYNOPSIS
362
363 Creates an in-memory struct for use in your PowerShell session.
364
365 Author: Matthew Graeber (@mattifestation)
366 License: BSD 3-Clause
367 Required Dependencies: None
368 Optional Dependencies: field
369
370 .DESCRIPTION
371
372 The 'struct' function facilitates the creation of structs entirely in
373 memory using as close to a "C style" as PowerShell will allow. Struct
374 fields are specified using a hashtable where each field of the struct
375 is comprosed of the order in which it should be defined, its .NET
376 type, and optionally, its offset and special marshaling attributes.
377
378 One of the features of 'struct' is that after your struct is defined,
379 it will come with a built-in GetSize method as well as an explicit
380 converter so that you can easily cast an IntPtr to the struct without
381 relying upon calling SizeOf and/or PtrToStructure in the Marshal
382 class.
383
384 .PARAMETER Module
385
386 The in-memory module that will host the struct. Use
387 New-InMemoryModule to define an in-memory module.
388
389 .PARAMETER FullName
390
391 The fully-qualified name of the struct.
392
393 .PARAMETER StructFields
394
395 A hashtable of fields. Use the 'field' helper function to ease
396 defining each field.
397
398 .PARAMETER PackingSize
399
400 Specifies the memory alignment of fields.
401
402 .PARAMETER ExplicitLayout
403
404 Indicates that an explicit offset for each field will be specified.
405
406 .EXAMPLE
407
408 $Mod = New-InMemoryModule -ModuleName Win32
409
410 $ImageDosSignature = psenum $Mod PE.IMAGE_DOS_SIGNATURE UInt16 @{
411 DOS_SIGNATURE = 0x5A4D
412 OS2_SIGNATURE = 0x454E
413 OS2_SIGNATURE_LE = 0x454C
414 VXD_SIGNATURE = 0x454C
415 }
416
417 $ImageDosHeader = struct $Mod PE.IMAGE_DOS_HEADER @{
418 e_magic = field 0 $ImageDosSignature
419 e_cblp = field 1 UInt16
420 e_cp = field 2 UInt16
421 e_crlc = field 3 UInt16
422 e_cparhdr = field 4 UInt16
423 e_minalloc = field 5 UInt16
424 e_maxalloc = field 6 UInt16
425 e_ss = field 7 UInt16
426 e_sp = field 8 UInt16
427 e_csum = field 9 UInt16
428 e_ip = field 10 UInt16
429 e_cs = field 11 UInt16
430 e_lfarlc = field 12 UInt16
431 e_ovno = field 13 UInt16
432 e_res = field 14 UInt16[] -MarshalAs @('ByValArray', 4)
433 e_oemid = field 15 UInt16
434 e_oeminfo = field 16 UInt16
435 e_res2 = field 17 UInt16[] -MarshalAs @('ByValArray', 10)
436 e_lfanew = field 18 Int32
437 }
438
439 # Example of using an explicit layout in order to create a union.
440 $TestUnion = struct $Mod TestUnion @{
441 field1 = field 0 UInt32 0
442 field2 = field 1 IntPtr 0
443 } -ExplicitLayout
444
445 .NOTES
446
447 PowerShell purists may disagree with the naming of this function but
448 again, this was developed in such a way so as to emulate a "C style"
449 definition as closely as possible. Sorry, I'm not going to name it
450 New-Struct. :P
451#>
452
453 [OutputType([Type])]
454 Param
455 (
456 [Parameter(Position = 1, Mandatory = $True)]
457 [ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})]
458 $Module,
459
460 [Parameter(Position = 2, Mandatory = $True)]
461 [ValidateNotNullOrEmpty()]
462 [String]
463 $FullName,
464
465 [Parameter(Position = 3, Mandatory = $True)]
466 [ValidateNotNullOrEmpty()]
467 [Hashtable]
468 $StructFields,
469
470 [Reflection.Emit.PackingSize]
471 $PackingSize = [Reflection.Emit.PackingSize]::Unspecified,
472
473 [Switch]
474 $ExplicitLayout
475 )
476
477 if ($Module -is [Reflection.Assembly])
478 {
479 return ($Module.GetType($FullName))
480 }
481
482 [Reflection.TypeAttributes] $StructAttributes = 'AnsiClass,
483 Class,
484 Public,
485 Sealed,
486 BeforeFieldInit'
487
488 if ($ExplicitLayout)
489 {
490 $StructAttributes = $StructAttributes -bor [Reflection.TypeAttributes]::ExplicitLayout
491 }
492 else
493 {
494 $StructAttributes = $StructAttributes -bor [Reflection.TypeAttributes]::SequentialLayout
495 }
496
497 $StructBuilder = $Module.DefineType($FullName, $StructAttributes, [ValueType], $PackingSize)
498 $ConstructorInfo = [Runtime.InteropServices.MarshalAsAttribute].GetConstructors()[0]
499 $SizeConst = @([Runtime.InteropServices.MarshalAsAttribute].GetField('SizeConst'))
500
501 $Fields = New-Object Hashtable[]($StructFields.Count)
502
503 # Sort each field according to the orders specified
504 # Unfortunately, PSv2 doesn't have the luxury of the
505 # hashtable [Ordered] accelerator.
506 ForEach ($Field in $StructFields.Keys)
507 {
508 $Index = $StructFields[$Field]['Position']
509 $Fields[$Index] = @{FieldName = $Field; Properties = $StructFields[$Field]}
510 }
511
512 ForEach ($Field in $Fields)
513 {
514 $FieldName = $Field['FieldName']
515 $FieldProp = $Field['Properties']
516
517 $Offset = $FieldProp['Offset']
518 $Type = $FieldProp['Type']
519 $MarshalAs = $FieldProp['MarshalAs']
520
521 $NewField = $StructBuilder.DefineField($FieldName, $Type, 'Public')
522
523 if ($MarshalAs)
524 {
525 $UnmanagedType = $MarshalAs[0] -as ([Runtime.InteropServices.UnmanagedType])
526 if ($MarshalAs[1])
527 {
528 $Size = $MarshalAs[1]
529 $AttribBuilder = New-Object Reflection.Emit.CustomAttributeBuilder($ConstructorInfo,
530 $UnmanagedType, $SizeConst, @($Size))
531 }
532 else
533 {
534 $AttribBuilder = New-Object Reflection.Emit.CustomAttributeBuilder($ConstructorInfo, [Object[]] @($UnmanagedType))
535 }
536
537 $NewField.SetCustomAttribute($AttribBuilder)
538 }
539
540 if ($ExplicitLayout) { $NewField.SetOffset($Offset) }
541 }
542
543 # Make the struct aware of its own size.
544 # No more having to call [Runtime.InteropServices.Marshal]::SizeOf!
545 $SizeMethod = $StructBuilder.DefineMethod('GetSize',
546 'Public, Static',
547 [Int],
548 [Type[]] @())
549 $ILGenerator = $SizeMethod.GetILGenerator()
550 # Thanks for the help, Jason Shirk!
551 $ILGenerator.Emit([Reflection.Emit.OpCodes]::Ldtoken, $StructBuilder)
552 $ILGenerator.Emit([Reflection.Emit.OpCodes]::Call,
553 [Type].GetMethod('GetTypeFromHandle'))
554 $ILGenerator.Emit([Reflection.Emit.OpCodes]::Call,
555 [Runtime.InteropServices.Marshal].GetMethod('SizeOf', [Type[]] @([Type])))
556 $ILGenerator.Emit([Reflection.Emit.OpCodes]::Ret)
557
558 # Allow for explicit casting from an IntPtr
559 # No more having to call [Runtime.InteropServices.Marshal]::PtrToStructure!
560 $ImplicitConverter = $StructBuilder.DefineMethod('op_Implicit',
561 'PrivateScope, Public, Static, HideBySig, SpecialName',
562 $StructBuilder,
563 [Type[]] @([IntPtr]))
564 $ILGenerator2 = $ImplicitConverter.GetILGenerator()
565 $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Nop)
566 $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Ldarg_0)
567 $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Ldtoken, $StructBuilder)
568 $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Call,
569 [Type].GetMethod('GetTypeFromHandle'))
570 $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Call,
571 [Runtime.InteropServices.Marshal].GetMethod('PtrToStructure', [Type[]] @([IntPtr], [Type])))
572 $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Unbox_Any, $StructBuilder)
573 $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Ret)
574
575 $StructBuilder.CreateType()
576}
577
578
579########################################################
580#
581# Misc. helpers
582#
583########################################################
584
585filter Get-IniContent {
586<#
587 .SYNOPSIS
588
589 This helper parses an .ini file into a proper PowerShell object.
590
591 Author: 'The Scripting Guys'
592 Link: https://blogs.technet.microsoft.com/heyscriptingguy/2011/08/20/use-powershell-to-work-with-any-ini-file/
593
594 .LINK
595
596 https://blogs.technet.microsoft.com/heyscriptingguy/2011/08/20/use-powershell-to-work-with-any-ini-file/
597#>
598 [CmdletBinding()]
599 Param(
600 [Parameter(Mandatory=$True, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)]
601 [Alias('FullName')]
602 [ValidateScript({ Test-Path -Path $_ })]
603 [String[]]
604 $Path
605 )
606
607 ForEach($TargetPath in $Path) {
608 $IniObject = @{}
609 Switch -Regex -File $TargetPath {
610 "^\[(.+)\]" # Section
611 {
612 $Section = $matches[1].Trim()
613 $IniObject[$Section] = @{}
614 $CommentCount = 0
615 }
616 "^(;.*)$" # Comment
617 {
618 $Value = $matches[1].Trim()
619 $CommentCount = $CommentCount + 1
620 $Name = 'Comment' + $CommentCount
621 $IniObject[$Section][$Name] = $Value
622 }
623 "(.+?)\s*=(.*)" # Key
624 {
625 $Name, $Value = $matches[1..2]
626 $Name = $Name.Trim()
627 $Values = $Value.split(',') | ForEach-Object {$_.Trim()}
628 if($Values -isnot [System.Array]) {$Values = @($Values)}
629 $IniObject[$Section][$Name] = $Values
630 }
631 }
632 $IniObject
633 }
634}
635
636filter Export-PowerViewCSV {
637<#
638 .SYNOPSIS
639
640 This helper exports an -InputObject to a .csv in a thread-safe manner
641 using a mutex. This is so the various multi-threaded functions in
642 PowerView has a thread-safe way to export output to the same file.
643
644 Based partially on Dmitry Sotnikov's Export-CSV code
645 at http://poshcode.org/1590
646
647 .LINK
648
649 http://poshcode.org/1590
650 http://dmitrysotnikov.wordpress.com/2010/01/19/Export-Csv-append/
651#>
652 Param(
653 [Parameter(Mandatory=$True, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)]
654 [System.Management.Automation.PSObject[]]
655 $InputObject,
656
657 [Parameter(Mandatory=$True, Position=0)]
658 [String]
659 [ValidateNotNullOrEmpty()]
660 $OutFile
661 )
662
663 $ObjectCSV = $InputObject | ConvertTo-Csv -NoTypeInformation
664
665 # mutex so threaded code doesn't stomp on the output file
666 $Mutex = New-Object System.Threading.Mutex $False,'CSVMutex';
667 $Null = $Mutex.WaitOne()
668
669 if (Test-Path -Path $OutFile) {
670 # hack to skip the first line of output if the file already exists
671 $ObjectCSV | ForEach-Object { $Start=$True }{ if ($Start) {$Start=$False} else {$_} } | Out-File -Encoding 'ASCII' -Append -FilePath $OutFile
672 }
673 else {
674 $ObjectCSV | Out-File -Encoding 'ASCII' -Append -FilePath $OutFile
675 }
676
677 $Mutex.ReleaseMutex()
678}
679
680
681filter Get-IPAddress {
682<#
683 .SYNOPSIS
684
685 Resolves a given hostename to its associated IPv4 address.
686 If no hostname is provided, it defaults to returning
687 the IP address of the localhost.
688
689 .EXAMPLE
690
691 PS C:\> Get-IPAddress -ComputerName SERVER
692
693 Return the IPv4 address of 'SERVER'
694
695 .EXAMPLE
696
697 PS C:\> Get-Content .\hostnames.txt | Get-IPAddress
698
699 Get the IP addresses of all hostnames in an input file.
700#>
701
702 [CmdletBinding()]
703 param(
704 [Parameter(Position=0, ValueFromPipeline=$True)]
705 [Alias('HostName')]
706 [String]
707 $ComputerName = $Env:ComputerName
708 )
709
710 try {
711 # extract the computer name from whatever object was passed on the pipeline
712 $Computer = $ComputerName | Get-NameField
713
714 # get the IP resolution of this specified hostname
715 @(([Net.Dns]::GetHostEntry($Computer)).AddressList) | ForEach-Object {
716 if ($_.AddressFamily -eq 'InterNetwork') {
717 $Out = New-Object PSObject
718 $Out | Add-Member Noteproperty 'ComputerName' $Computer
719 $Out | Add-Member Noteproperty 'IPAddress' $_.IPAddressToString
720 $Out
721 }
722 }
723 }
724 catch {
725 Write-Verbose -Message 'Could not resolve host to an IP Address.'
726 }
727}
728
729
730filter Convert-NameToSid {
731<#
732 .SYNOPSIS
733
734 Converts a given user/group name to a security identifier (SID).
735
736 .PARAMETER ObjectName
737
738 The user/group name to convert, can be 'user' or 'DOMAIN\user' format.
739
740 .PARAMETER Domain
741
742 Specific domain for the given user account, defaults to the current domain.
743
744 .EXAMPLE
745
746 PS C:\> Convert-NameToSid 'DEV\dfm'
747#>
748 [CmdletBinding()]
749 param(
750 [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
751 [String]
752 [Alias('Name')]
753 $ObjectName,
754
755 [String]
756 $Domain
757 )
758
759 $ObjectName = $ObjectName -Replace "/","\"
760
761 if($ObjectName.Contains("\")) {
762 # if we get a DOMAIN\user format, auto convert it
763 $Domain = $ObjectName.Split("\")[0]
764 $ObjectName = $ObjectName.Split("\")[1]
765 }
766 elseif(-not $Domain) {
767 $Domain = (Get-NetDomain).Name
768 }
769
770 try {
771 $Obj = (New-Object System.Security.Principal.NTAccount($Domain, $ObjectName))
772 $SID = $Obj.Translate([System.Security.Principal.SecurityIdentifier]).Value
773
774 $Out = New-Object PSObject
775 $Out | Add-Member Noteproperty 'ObjectName' $ObjectName
776 $Out | Add-Member Noteproperty 'SID' $SID
777 $Out
778 }
779 catch {
780 Write-Verbose "Invalid object/name: $Domain\$ObjectName"
781 $Null
782 }
783}
784
785
786filter Convert-SidToName {
787<#
788 .SYNOPSIS
789
790 Converts a security identifier (SID) to a group/user name.
791
792 .PARAMETER SID
793
794 The SID to convert.
795
796 .EXAMPLE
797
798 PS C:\> Convert-SidToName S-1-5-21-2620891829-2411261497-1773853088-1105
799#>
800 [CmdletBinding()]
801 param(
802 [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
803 [String]
804 [ValidatePattern('^S-1-.*')]
805 $SID
806 )
807
808 try {
809 $SID2 = $SID.trim('*')
810
811 # try to resolve any built-in SIDs first
812 # from https://support.microsoft.com/en-us/kb/243330
813 Switch ($SID2) {
814 'S-1-0' { 'Null Authority' }
815 'S-1-0-0' { 'Nobody' }
816 'S-1-1' { 'World Authority' }
817 'S-1-1-0' { 'Everyone' }
818 'S-1-2' { 'Local Authority' }
819 'S-1-2-0' { 'Local' }
820 'S-1-2-1' { 'Console Logon ' }
821 'S-1-3' { 'Creator Authority' }
822 'S-1-3-0' { 'Creator Owner' }
823 'S-1-3-1' { 'Creator Group' }
824 'S-1-3-2' { 'Creator Owner Server' }
825 'S-1-3-3' { 'Creator Group Server' }
826 'S-1-3-4' { 'Owner Rights' }
827 'S-1-4' { 'Non-unique Authority' }
828 'S-1-5' { 'NT Authority' }
829 'S-1-5-1' { 'Dialup' }
830 'S-1-5-2' { 'Network' }
831 'S-1-5-3' { 'Batch' }
832 'S-1-5-4' { 'Interactive' }
833 'S-1-5-6' { 'Service' }
834 'S-1-5-7' { 'Anonymous' }
835 'S-1-5-8' { 'Proxy' }
836 'S-1-5-9' { 'Enterprise Domain Controllers' }
837 'S-1-5-10' { 'Principal Self' }
838 'S-1-5-11' { 'Authenticated Users' }
839 'S-1-5-12' { 'Restricted Code' }
840 'S-1-5-13' { 'Terminal Server Users' }
841 'S-1-5-14' { 'Remote Interactive Logon' }
842 'S-1-5-15' { 'This Organization ' }
843 'S-1-5-17' { 'This Organization ' }
844 'S-1-5-18' { 'Local System' }
845 'S-1-5-19' { 'NT Authority' }
846 'S-1-5-20' { 'NT Authority' }
847 'S-1-5-80-0' { 'All Services ' }
848 'S-1-5-32-544' { 'BUILTIN\Administrators' }
849 'S-1-5-32-545' { 'BUILTIN\Users' }
850 'S-1-5-32-546' { 'BUILTIN\Guests' }
851 'S-1-5-32-547' { 'BUILTIN\Power Users' }
852 'S-1-5-32-548' { 'BUILTIN\Account Operators' }
853 'S-1-5-32-549' { 'BUILTIN\Server Operators' }
854 'S-1-5-32-550' { 'BUILTIN\Print Operators' }
855 'S-1-5-32-551' { 'BUILTIN\Backup Operators' }
856 'S-1-5-32-552' { 'BUILTIN\Replicators' }
857 'S-1-5-32-554' { 'BUILTIN\Pre-Windows 2000 Compatible Access' }
858 'S-1-5-32-555' { 'BUILTIN\Remote Desktop Users' }
859 'S-1-5-32-556' { 'BUILTIN\Network Configuration Operators' }
860 'S-1-5-32-557' { 'BUILTIN\Incoming Forest Trust Builders' }
861 'S-1-5-32-558' { 'BUILTIN\Performance Monitor Users' }
862 'S-1-5-32-559' { 'BUILTIN\Performance Log Users' }
863 'S-1-5-32-560' { 'BUILTIN\Windows Authorization Access Group' }
864 'S-1-5-32-561' { 'BUILTIN\Terminal Server License Servers' }
865 'S-1-5-32-562' { 'BUILTIN\Distributed COM Users' }
866 'S-1-5-32-569' { 'BUILTIN\Cryptographic Operators' }
867 'S-1-5-32-573' { 'BUILTIN\Event Log Readers' }
868 'S-1-5-32-574' { 'BUILTIN\Certificate Service DCOM Access' }
869 'S-1-5-32-575' { 'BUILTIN\RDS Remote Access Servers' }
870 'S-1-5-32-576' { 'BUILTIN\RDS Endpoint Servers' }
871 'S-1-5-32-577' { 'BUILTIN\RDS Management Servers' }
872 'S-1-5-32-578' { 'BUILTIN\Hyper-V Administrators' }
873 'S-1-5-32-579' { 'BUILTIN\Access Control Assistance Operators' }
874 'S-1-5-32-580' { 'BUILTIN\Access Control Assistance Operators' }
875 Default {
876 $Obj = (New-Object System.Security.Principal.SecurityIdentifier($SID2))
877 $Obj.Translate( [System.Security.Principal.NTAccount]).Value
878 }
879 }
880 }
881 catch {
882 Write-Verbose "Invalid SID: $SID"
883 $SID
884 }
885}
886
887
888filter Convert-ADName {
889<#
890 .SYNOPSIS
891
892 Converts user/group names from NT4 (DOMAIN\user) or domainSimple (user@domain.com)
893 to canonical format (domain.com/Users/user) or NT4.
894
895 Based on Bill Stewart's code from this article:
896 http://windowsitpro.com/active-directory/translating-active-directory-object-names-between-formats
897
898 .PARAMETER ObjectName
899
900 The user/group name to convert.
901
902 .PARAMETER InputType
903
904 The InputType of the user/group name ("NT4","Simple","Canonical").
905
906 .PARAMETER OutputType
907
908 The OutputType of the user/group name ("NT4","Simple","Canonical").
909
910 .EXAMPLE
911
912 PS C:\> Convert-ADName -ObjectName "dev\dfm"
913
914 Returns "dev.testlab.local/Users/Dave"
915
916 .EXAMPLE
917
918 PS C:\> Convert-SidToName "S-..." | Convert-ADName
919
920 Returns the canonical name for the resolved SID.
921
922 .LINK
923
924 http://windowsitpro.com/active-directory/translating-active-directory-object-names-between-formats
925#>
926 [CmdletBinding()]
927 param(
928 [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
929 [String]
930 $ObjectName,
931
932 [String]
933 [ValidateSet("NT4","Simple","Canonical")]
934 $InputType,
935
936 [String]
937 [ValidateSet("NT4","Simple","Canonical")]
938 $OutputType
939 )
940
941 $NameTypes = @{
942 'Canonical' = 2
943 'NT4' = 3
944 'Simple' = 5
945 }
946
947 if(-not $PSBoundParameters['InputType']) {
948 if( ($ObjectName.split('/')).Count -eq 2 ) {
949 $ObjectName = $ObjectName.replace('/', '\')
950 }
951
952 if($ObjectName -match "^[A-Za-z]+\\[A-Za-z ]+") {
953 $InputType = 'NT4'
954 }
955 elseif($ObjectName -match "^[A-Za-z ]+@[A-Za-z\.]+") {
956 $InputType = 'Simple'
957 }
958 elseif($ObjectName -match "^[A-Za-z\.]+/[A-Za-z]+/[A-Za-z/ ]+") {
959 $InputType = 'Canonical'
960 }
961 else {
962 Write-Warning "Can not identify InType for $ObjectName"
963 return $ObjectName
964 }
965 }
966 elseif($InputType -eq 'NT4') {
967 $ObjectName = $ObjectName.replace('/', '\')
968 }
969
970 if(-not $PSBoundParameters['OutputType']) {
971 $OutputType = Switch($InputType) {
972 'NT4' {'Canonical'}
973 'Simple' {'NT4'}
974 'Canonical' {'NT4'}
975 }
976 }
977
978 # try to extract the domain from the given format
979 $Domain = Switch($InputType) {
980 'NT4' { $ObjectName.split("\")[0] }
981 'Simple' { $ObjectName.split("@")[1] }
982 'Canonical' { $ObjectName.split("/")[0] }
983 }
984
985 # Accessor functions to simplify calls to NameTranslate
986 function Invoke-Method([__ComObject] $Object, [String] $Method, $Parameters) {
987 $Output = $Object.GetType().InvokeMember($Method, "InvokeMethod", $Null, $Object, $Parameters)
988 if ( $Output ) { $Output }
989 }
990 function Set-Property([__ComObject] $Object, [String] $Property, $Parameters) {
991 [Void] $Object.GetType().InvokeMember($Property, "SetProperty", $Null, $Object, $Parameters)
992 }
993
994 $Translate = New-Object -ComObject NameTranslate
995
996 try {
997 Invoke-Method $Translate "Init" (1, $Domain)
998 }
999 catch [System.Management.Automation.MethodInvocationException] {
1000 Write-Verbose "Error with translate init in Convert-ADName: $_"
1001 }
1002
1003 Set-Property $Translate "ChaseReferral" (0x60)
1004
1005 try {
1006 Invoke-Method $Translate "Set" ($NameTypes[$InputType], $ObjectName)
1007 (Invoke-Method $Translate "Get" ($NameTypes[$OutputType]))
1008 }
1009 catch [System.Management.Automation.MethodInvocationException] {
1010 Write-Verbose "Error with translate Set/Get in Convert-ADName: $_"
1011 }
1012}
1013
1014
1015function ConvertFrom-UACValue {
1016<#
1017 .SYNOPSIS
1018
1019 Converts a UAC int value to human readable form.
1020
1021 .PARAMETER Value
1022
1023 The int UAC value to convert.
1024
1025 .PARAMETER ShowAll
1026
1027 Show all UAC values, with a + indicating the value is currently set.
1028
1029 .EXAMPLE
1030
1031 PS C:\> ConvertFrom-UACValue -Value 66176
1032
1033 Convert the UAC value 66176 to human readable format.
1034
1035 .EXAMPLE
1036
1037 PS C:\> Get-NetUser jason | select useraccountcontrol | ConvertFrom-UACValue
1038
1039 Convert the UAC value for 'jason' to human readable format.
1040
1041 .EXAMPLE
1042
1043 PS C:\> Get-NetUser jason | select useraccountcontrol | ConvertFrom-UACValue -ShowAll
1044
1045 Convert the UAC value for 'jason' to human readable format, showing all
1046 possible UAC values.
1047#>
1048
1049 [CmdletBinding()]
1050 param(
1051 [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
1052 $Value,
1053
1054 [Switch]
1055 $ShowAll
1056 )
1057
1058 begin {
1059 # values from https://support.microsoft.com/en-us/kb/305144
1060 $UACValues = New-Object System.Collections.Specialized.OrderedDictionary
1061 $UACValues.Add("SCRIPT", 1)
1062 $UACValues.Add("ACCOUNTDISABLE", 2)
1063 $UACValues.Add("HOMEDIR_REQUIRED", 8)
1064 $UACValues.Add("LOCKOUT", 16)
1065 $UACValues.Add("PASSWD_NOTREQD", 32)
1066 $UACValues.Add("PASSWD_CANT_CHANGE", 64)
1067 $UACValues.Add("ENCRYPTED_TEXT_PWD_ALLOWED", 128)
1068 $UACValues.Add("TEMP_DUPLICATE_ACCOUNT", 256)
1069 $UACValues.Add("NORMAL_ACCOUNT", 512)
1070 $UACValues.Add("INTERDOMAIN_TRUST_ACCOUNT", 2048)
1071 $UACValues.Add("WORKSTATION_TRUST_ACCOUNT", 4096)
1072 $UACValues.Add("SERVER_TRUST_ACCOUNT", 8192)
1073 $UACValues.Add("DONT_EXPIRE_PASSWORD", 65536)
1074 $UACValues.Add("MNS_LOGON_ACCOUNT", 131072)
1075 $UACValues.Add("SMARTCARD_REQUIRED", 262144)
1076 $UACValues.Add("TRUSTED_FOR_DELEGATION", 524288)
1077 $UACValues.Add("NOT_DELEGATED", 1048576)
1078 $UACValues.Add("USE_DES_KEY_ONLY", 2097152)
1079 $UACValues.Add("DONT_REQ_PREAUTH", 4194304)
1080 $UACValues.Add("PASSWORD_EXPIRED", 8388608)
1081 $UACValues.Add("TRUSTED_TO_AUTH_FOR_DELEGATION", 16777216)
1082 $UACValues.Add("PARTIAL_SECRETS_ACCOUNT", 67108864)
1083 }
1084
1085 process {
1086
1087 $ResultUACValues = New-Object System.Collections.Specialized.OrderedDictionary
1088
1089 if($Value -is [Int]) {
1090 $IntValue = $Value
1091 }
1092 elseif ($Value -is [PSCustomObject]) {
1093 if($Value.useraccountcontrol) {
1094 $IntValue = $Value.useraccountcontrol
1095 }
1096 }
1097 else {
1098 Write-Warning "Invalid object input for -Value : $Value"
1099 return $Null
1100 }
1101
1102 if($ShowAll) {
1103 foreach ($UACValue in $UACValues.GetEnumerator()) {
1104 if( ($IntValue -band $UACValue.Value) -eq $UACValue.Value) {
1105 $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)+")
1106 }
1107 else {
1108 $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)")
1109 }
1110 }
1111 }
1112 else {
1113 foreach ($UACValue in $UACValues.GetEnumerator()) {
1114 if( ($IntValue -band $UACValue.Value) -eq $UACValue.Value) {
1115 $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)")
1116 }
1117 }
1118 }
1119 $ResultUACValues
1120 }
1121}
1122
1123
1124filter Get-Proxy {
1125<#
1126 .SYNOPSIS
1127
1128 Enumerates the proxy server and WPAD conents for the current user.
1129
1130 .PARAMETER ComputerName
1131
1132 The computername to enumerate proxy settings on, defaults to local host.
1133
1134 .EXAMPLE
1135
1136 PS C:\> Get-Proxy
1137
1138 Returns the current proxy settings.
1139#>
1140 param(
1141 [Parameter(ValueFromPipeline=$True)]
1142 [ValidateNotNullOrEmpty()]
1143 [String]
1144 $ComputerName = $ENV:COMPUTERNAME
1145 )
1146
1147 try {
1148 $Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('CurrentUser', $ComputerName)
1149 $RegKey = $Reg.OpenSubkey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Internet Settings")
1150 $ProxyServer = $RegKey.GetValue('ProxyServer')
1151 $AutoConfigURL = $RegKey.GetValue('AutoConfigURL')
1152
1153 $Wpad = ""
1154 if($AutoConfigURL -and ($AutoConfigURL -ne "")) {
1155 try {
1156 $Wpad = (New-Object Net.Webclient).DownloadString($AutoConfigURL)
1157 }
1158 catch {
1159 Write-Warning "Error connecting to AutoConfigURL : $AutoConfigURL"
1160 }
1161 }
1162
1163 if($ProxyServer -or $AutoConfigUrl) {
1164
1165 $Properties = @{
1166 'ProxyServer' = $ProxyServer
1167 'AutoConfigURL' = $AutoConfigURL
1168 'Wpad' = $Wpad
1169 }
1170
1171 New-Object -TypeName PSObject -Property $Properties
1172 }
1173 else {
1174 Write-Warning "No proxy settings found for $ComputerName"
1175 }
1176 }
1177 catch {
1178 Write-Warning "Error enumerating proxy settings for $ComputerName : $_"
1179 }
1180}
1181
1182
1183function Request-SPNTicket {
1184<#
1185 .SYNOPSIS
1186
1187 Request the kerberos ticket for a specified service principal name (SPN).
1188
1189 .PARAMETER SPN
1190
1191 The service principal name to request the ticket for. Required.
1192
1193 .PARAMETER EncPart
1194
1195 Switch. Return the encrypted portion of the ticket (cipher).
1196
1197 .EXAMPLE
1198
1199 PS C:\> Request-SPNTicket -SPN "HTTP/web.testlab.local"
1200
1201 Request a kerberos service ticket for the specified SPN.
1202
1203 .EXAMPLE
1204
1205 PS C:\> Request-SPNTicket -SPN "HTTP/web.testlab.local" -EncPart
1206
1207 Request a kerberos service ticket for the specified SPN and return the encrypted portion of the ticket.
1208
1209 .EXAMPLE
1210
1211 PS C:\> "HTTP/web1.testlab.local","HTTP/web2.testlab.local" | Request-SPNTicket
1212
1213 Request kerberos service tickets for all SPNs passed on the pipeline.
1214
1215 .EXAMPLE
1216
1217 PS C:\> Get-NetUser -SPN | Request-SPNTicket
1218
1219 Request kerberos service tickets for all users with non-null SPNs.
1220#>
1221
1222 [CmdletBinding()]
1223 Param (
1224 [Parameter(Mandatory=$True, ValueFromPipelineByPropertyName = $True)]
1225 [Alias('ServicePrincipalName')]
1226 [String[]]
1227 $SPN,
1228
1229 [Alias('EncryptedPart')]
1230 [Switch]
1231 $EncPart
1232 )
1233
1234 begin {
1235 Add-Type -AssemblyName System.IdentityModel
1236 }
1237
1238 process {
1239 ForEach($UserSPN in $SPN) {
1240 Write-Verbose "Requesting ticket for: $UserSPN"
1241 if (!$EncPart) {
1242 New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $UserSPN
1243 }
1244 else {
1245 $Ticket = New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $UserSPN
1246 $TicketByteStream = $Ticket.GetRequest()
1247 if ($TicketByteStream)
1248 {
1249 $TicketHexStream = [System.BitConverter]::ToString($TicketByteStream) -replace "-"
1250 [System.Collections.ArrayList]$Parts = ($TicketHexStream -replace '^(.*?)04820...(.*)','$2') -Split "A48201"
1251 $Parts.RemoveAt($Parts.Count - 1)
1252 $Parts -join "A48201"
1253 break
1254 }
1255 }
1256 }
1257 }
1258}
1259
1260
1261function Get-PathAcl {
1262<#
1263 .SYNOPSIS
1264
1265 Enumerates the ACL for a given file path.
1266
1267 .PARAMETER Path
1268
1269 The local/remote path to enumerate the ACLs for.
1270
1271 .PARAMETER Recurse
1272
1273 If any ACL results are groups, recurse and retrieve user membership.
1274
1275 .EXAMPLE
1276
1277 PS C:\> Get-PathAcl "\\SERVER\Share\"
1278
1279 Returns ACLs for the given UNC share.
1280#>
1281 [CmdletBinding()]
1282 param(
1283 [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
1284 [String]
1285 $Path,
1286
1287 [Switch]
1288 $Recurse
1289 )
1290
1291 begin {
1292
1293 function Convert-FileRight {
1294
1295 # From http://stackoverflow.com/questions/28029872/retrieving-security-descriptor-and-getting-number-for-filesystemrights
1296
1297 [CmdletBinding()]
1298 param(
1299 [Int]
1300 $FSR
1301 )
1302
1303 $AccessMask = @{
1304 [uint32]'0x80000000' = 'GenericRead'
1305 [uint32]'0x40000000' = 'GenericWrite'
1306 [uint32]'0x20000000' = 'GenericExecute'
1307 [uint32]'0x10000000' = 'GenericAll'
1308 [uint32]'0x02000000' = 'MaximumAllowed'
1309 [uint32]'0x01000000' = 'AccessSystemSecurity'
1310 [uint32]'0x00100000' = 'Synchronize'
1311 [uint32]'0x00080000' = 'WriteOwner'
1312 [uint32]'0x00040000' = 'WriteDAC'
1313 [uint32]'0x00020000' = 'ReadControl'
1314 [uint32]'0x00010000' = 'Delete'
1315 [uint32]'0x00000100' = 'WriteAttributes'
1316 [uint32]'0x00000080' = 'ReadAttributes'
1317 [uint32]'0x00000040' = 'DeleteChild'
1318 [uint32]'0x00000020' = 'Execute/Traverse'
1319 [uint32]'0x00000010' = 'WriteExtendedAttributes'
1320 [uint32]'0x00000008' = 'ReadExtendedAttributes'
1321 [uint32]'0x00000004' = 'AppendData/AddSubdirectory'
1322 [uint32]'0x00000002' = 'WriteData/AddFile'
1323 [uint32]'0x00000001' = 'ReadData/ListDirectory'
1324 }
1325
1326 $SimplePermissions = @{
1327 [uint32]'0x1f01ff' = 'FullControl'
1328 [uint32]'0x0301bf' = 'Modify'
1329 [uint32]'0x0200a9' = 'ReadAndExecute'
1330 [uint32]'0x02019f' = 'ReadAndWrite'
1331 [uint32]'0x020089' = 'Read'
1332 [uint32]'0x000116' = 'Write'
1333 }
1334
1335 $Permissions = @()
1336
1337 # get simple permission
1338 $Permissions += $SimplePermissions.Keys | % {
1339 if (($FSR -band $_) -eq $_) {
1340 $SimplePermissions[$_]
1341 $FSR = $FSR -band (-not $_)
1342 }
1343 }
1344
1345 # get remaining extended permissions
1346 $Permissions += $AccessMask.Keys |
1347 ? { $FSR -band $_ } |
1348 % { $AccessMask[$_] }
1349
1350 ($Permissions | ?{$_}) -join ","
1351 }
1352 }
1353
1354 process {
1355
1356 try {
1357 $ACL = Get-Acl -Path $Path
1358
1359 $ACL.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier]) | ForEach-Object {
1360
1361 $Names = @()
1362 if ($_.IdentityReference -match '^S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+') {
1363 $Object = Get-ADObject -SID $_.IdentityReference
1364 $Names = @()
1365 $SIDs = @($Object.objectsid)
1366
1367 if ($Recurse -and (@('268435456','268435457','536870912','536870913') -contains $Object.samAccountType)) {
1368 $SIDs += Get-NetGroupMember -SID $Object.objectsid | Select-Object -ExpandProperty MemberSid
1369 }
1370
1371 $SIDs | ForEach-Object {
1372 $Names += ,@($_, (Convert-SidToName $_))
1373 }
1374 }
1375 else {
1376 $Names += ,@($_.IdentityReference.Value, (Convert-SidToName $_.IdentityReference.Value))
1377 }
1378
1379 ForEach($Name in $Names) {
1380 $Out = New-Object PSObject
1381 $Out | Add-Member Noteproperty 'Path' $Path
1382 $Out | Add-Member Noteproperty 'FileSystemRights' (Convert-FileRight -FSR $_.FileSystemRights.value__)
1383 $Out | Add-Member Noteproperty 'IdentityReference' $Name[1]
1384 $Out | Add-Member Noteproperty 'IdentitySID' $Name[0]
1385 $Out | Add-Member Noteproperty 'AccessControlType' $_.AccessControlType
1386 $Out
1387 }
1388 }
1389 }
1390 catch {
1391 Write-Warning $_
1392 }
1393 }
1394}
1395
1396
1397filter Get-NameField {
1398<#
1399 .SYNOPSIS
1400
1401 Helper that attempts to extract appropriate field names from
1402 passed computer objects.
1403
1404 .PARAMETER Object
1405
1406 The passed object to extract name fields from.
1407
1408 .PARAMETER DnsHostName
1409
1410 A DnsHostName to extract through ValueFromPipelineByPropertyName.
1411
1412 .PARAMETER Name
1413
1414 A Name to extract through ValueFromPipelineByPropertyName.
1415
1416 .EXAMPLE
1417
1418 PS C:\> Get-NetComputer -FullData | Get-NameField
1419#>
1420 [CmdletBinding()]
1421 param(
1422 [Parameter(ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
1423 [Object]
1424 $Object,
1425
1426 [Parameter(ValueFromPipelineByPropertyName = $True)]
1427 [String]
1428 $DnsHostName,
1429
1430 [Parameter(ValueFromPipelineByPropertyName = $True)]
1431 [String]
1432 $Name
1433 )
1434
1435 if($PSBoundParameters['DnsHostName']) {
1436 $DnsHostName
1437 }
1438 elseif($PSBoundParameters['Name']) {
1439 $Name
1440 }
1441 elseif($Object) {
1442 if ( [bool]($Object.PSobject.Properties.name -match "dnshostname") ) {
1443 # objects from Get-NetComputer
1444 $Object.dnshostname
1445 }
1446 elseif ( [bool]($Object.PSobject.Properties.name -match "name") ) {
1447 # objects from Get-NetDomainController
1448 $Object.name
1449 }
1450 else {
1451 # strings and catch alls
1452 $Object
1453 }
1454 }
1455 else {
1456 return $Null
1457 }
1458}
1459
1460
1461function Convert-LDAPProperty {
1462<#
1463 .SYNOPSIS
1464
1465 Helper that converts specific LDAP property result fields.
1466 Used by several of the Get-Net* function.
1467
1468 .PARAMETER Properties
1469
1470 Properties object to extract out LDAP fields for display.
1471#>
1472 param(
1473 [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
1474 [ValidateNotNullOrEmpty()]
1475 $Properties
1476 )
1477
1478 $ObjectProperties = @{}
1479
1480 $Properties.PropertyNames | ForEach-Object {
1481 if (($_ -eq "objectsid") -or ($_ -eq "sidhistory")) {
1482 # convert the SID to a string
1483 $ObjectProperties[$_] = (New-Object System.Security.Principal.SecurityIdentifier($Properties[$_][0],0)).Value
1484 }
1485 elseif($_ -eq "objectguid") {
1486 # convert the GUID to a string
1487 $ObjectProperties[$_] = (New-Object Guid (,$Properties[$_][0])).Guid
1488 }
1489 elseif( ($_ -eq "lastlogon") -or ($_ -eq "lastlogontimestamp") -or ($_ -eq "pwdlastset") -or ($_ -eq "lastlogoff") -or ($_ -eq "badPasswordTime") ) {
1490 # convert timestamps
1491 if ($Properties[$_][0] -is [System.MarshalByRefObject]) {
1492 # if we have a System.__ComObject
1493 $Temp = $Properties[$_][0]
1494 [Int32]$High = $Temp.GetType().InvokeMember("HighPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null)
1495 [Int32]$Low = $Temp.GetType().InvokeMember("LowPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null)
1496 $ObjectProperties[$_] = ([datetime]::FromFileTime([Int64]("0x{0:x8}{1:x8}" -f $High, $Low)))
1497 }
1498 else {
1499 $ObjectProperties[$_] = ([datetime]::FromFileTime(($Properties[$_][0])))
1500 }
1501 }
1502 elseif($Properties[$_][0] -is [System.MarshalByRefObject]) {
1503 # try to convert misc com objects
1504 $Prop = $Properties[$_]
1505 try {
1506 $Temp = $Prop[$_][0]
1507 Write-Verbose $_
1508 [Int32]$High = $Temp.GetType().InvokeMember("HighPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null)
1509 [Int32]$Low = $Temp.GetType().InvokeMember("LowPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null)
1510 $ObjectProperties[$_] = [Int64]("0x{0:x8}{1:x8}" -f $High, $Low)
1511 }
1512 catch {
1513 $ObjectProperties[$_] = $Prop[$_]
1514 }
1515 }
1516 elseif($Properties[$_].count -eq 1) {
1517 $ObjectProperties[$_] = $Properties[$_][0]
1518 }
1519 else {
1520 $ObjectProperties[$_] = $Properties[$_]
1521 }
1522 }
1523
1524 New-Object -TypeName PSObject -Property $ObjectProperties
1525}
1526
1527
1528
1529########################################################
1530#
1531# Domain info functions below.
1532#
1533########################################################
1534
1535filter Get-DomainSearcher {
1536<#
1537 .SYNOPSIS
1538
1539 Helper used by various functions that takes an ADSpath and
1540 domain specifier and builds the correct ADSI searcher object.
1541
1542 .PARAMETER Domain
1543
1544 The domain to use for the query, defaults to the current domain.
1545
1546 .PARAMETER DomainController
1547
1548 Domain controller to reflect LDAP queries through.
1549
1550 .PARAMETER ADSpath
1551
1552 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
1553 Useful for OU queries.
1554
1555 .PARAMETER ADSprefix
1556
1557 Prefix to set for the searcher (like "CN=Sites,CN=Configuration")
1558
1559 .PARAMETER PageSize
1560
1561 The PageSize to set for the LDAP searcher object.
1562
1563 .PARAMETER Credential
1564
1565 A [Management.Automation.PSCredential] object of alternate credentials
1566 for connection to the target domain.
1567
1568 .EXAMPLE
1569
1570 PS C:\> Get-DomainSearcher -Domain testlab.local
1571
1572 .EXAMPLE
1573
1574 PS C:\> Get-DomainSearcher -Domain testlab.local -DomainController SECONDARY.dev.testlab.local
1575#>
1576
1577 param(
1578 [Parameter(ValueFromPipeline=$True)]
1579 [String]
1580 $Domain,
1581
1582 [String]
1583 $DomainController,
1584
1585 [String]
1586 $ADSpath,
1587
1588 [String]
1589 $ADSprefix,
1590
1591 [ValidateRange(1,10000)]
1592 [Int]
1593 $PageSize = 200,
1594
1595 [Management.Automation.PSCredential]
1596 $Credential
1597 )
1598
1599 if(-not $Credential) {
1600 if(-not $Domain) {
1601 $Domain = (Get-NetDomain).name
1602 }
1603 elseif(-not $DomainController) {
1604 try {
1605 # if there's no -DomainController specified, try to pull the primary DC to reflect queries through
1606 $DomainController = ((Get-NetDomain).PdcRoleOwner).Name
1607 }
1608 catch {
1609 throw "Get-DomainSearcher: Error in retrieving PDC for current domain"
1610 }
1611 }
1612 }
1613 elseif (-not $DomainController) {
1614 # if a DC isn't specified
1615 try {
1616 $DomainController = ((Get-NetDomain -Credential $Credential).PdcRoleOwner).Name
1617 }
1618 catch {
1619 throw "Get-DomainSearcher: Error in retrieving PDC for current domain"
1620 }
1621
1622 if(!$DomainController) {
1623 throw "Get-DomainSearcher: Error in retrieving PDC for current domain"
1624 }
1625 }
1626
1627 $SearchString = "LDAP://"
1628
1629 if($DomainController) {
1630 $SearchString += $DomainController
1631 if($Domain){
1632 $SearchString += '/'
1633 }
1634 }
1635
1636 if($ADSprefix) {
1637 $SearchString += $ADSprefix + ','
1638 }
1639
1640 if($ADSpath) {
1641 if($ADSpath -Match '^GC://') {
1642 # if we're searching the global catalog
1643 $DN = $AdsPath.ToUpper().Trim('/')
1644 $SearchString = ''
1645 }
1646 else {
1647 if($ADSpath -match '^LDAP://') {
1648 if($ADSpath -match "LDAP://.+/.+") {
1649 $SearchString = ''
1650 }
1651 else {
1652 $ADSpath = $ADSpath.Substring(7)
1653 }
1654 }
1655 $DN = $ADSpath
1656 }
1657 }
1658 else {
1659 if($Domain -and ($Domain.Trim() -ne "")) {
1660 $DN = "DC=$($Domain.Replace('.', ',DC='))"
1661 }
1662 }
1663
1664 $SearchString += $DN
1665 Write-Verbose "Get-DomainSearcher search string: $SearchString"
1666
1667 if($Credential) {
1668 Write-Verbose "Using alternate credentials for LDAP connection"
1669 $DomainObject = New-Object DirectoryServices.DirectoryEntry($SearchString, $Credential.UserName, $Credential.GetNetworkCredential().Password)
1670 $Searcher = New-Object System.DirectoryServices.DirectorySearcher($DomainObject)
1671 }
1672 else {
1673 $Searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]$SearchString)
1674 }
1675
1676 $Searcher.PageSize = $PageSize
1677 $Searcher.CacheResults = $False
1678 $Searcher
1679}
1680
1681
1682filter Convert-DNSRecord {
1683<#
1684 .SYNOPSIS
1685
1686 Decodes a binary DNS record.
1687
1688 Adapted/ported from Michael B. Smith's code at https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1
1689
1690 .PARAMETER DNSRecord
1691
1692 The domain to query for zones, defaults to the current domain.
1693
1694 .LINK
1695
1696 https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1
1697#>
1698 param(
1699 [Parameter(Position=0, ValueFromPipelineByPropertyName=$True, Mandatory=$True)]
1700 [Byte[]]
1701 $DNSRecord
1702 )
1703
1704 function Get-Name {
1705 # modified decodeName from https://raw.githubusercontent.com/mmessano/PowerShell/master/dns-dump.ps1
1706 [CmdletBinding()]
1707 param(
1708 [Byte[]]
1709 $Raw
1710 )
1711
1712 [Int]$Length = $Raw[0]
1713 [Int]$Segments = $Raw[1]
1714 [Int]$Index = 2
1715 [String]$Name = ""
1716
1717 while ($Segments-- -gt 0)
1718 {
1719 [Int]$SegmentLength = $Raw[$Index++]
1720 while ($SegmentLength-- -gt 0) {
1721 $Name += [Char]$Raw[$Index++]
1722 }
1723 $Name += "."
1724 }
1725 $Name
1726 }
1727
1728 $RDataLen = [BitConverter]::ToUInt16($DNSRecord, 0)
1729 $RDataType = [BitConverter]::ToUInt16($DNSRecord, 2)
1730 $UpdatedAtSerial = [BitConverter]::ToUInt32($DNSRecord, 8)
1731
1732 $TTLRaw = $DNSRecord[12..15]
1733 # reverse for big endian
1734 $Null = [array]::Reverse($TTLRaw)
1735 $TTL = [BitConverter]::ToUInt32($TTLRaw, 0)
1736
1737 $Age = [BitConverter]::ToUInt32($DNSRecord, 20)
1738 if($Age -ne 0) {
1739 $TimeStamp = ((Get-Date -Year 1601 -Month 1 -Day 1 -Hour 0 -Minute 0 -Second 0).AddHours($age)).ToString()
1740 }
1741 else {
1742 $TimeStamp = "[static]"
1743 }
1744
1745 $DNSRecordObject = New-Object PSObject
1746
1747 if($RDataType -eq 1) {
1748 $IP = "{0}.{1}.{2}.{3}" -f $DNSRecord[24], $DNSRecord[25], $DNSRecord[26], $DNSRecord[27]
1749 $Data = $IP
1750 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'A'
1751 }
1752
1753 elseif($RDataType -eq 2) {
1754 $NSName = Get-Name $DNSRecord[24..$DNSRecord.length]
1755 $Data = $NSName
1756 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'NS'
1757 }
1758
1759 elseif($RDataType -eq 5) {
1760 $Alias = Get-Name $DNSRecord[24..$DNSRecord.length]
1761 $Data = $Alias
1762 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'CNAME'
1763 }
1764
1765 elseif($RDataType -eq 6) {
1766 # TODO: how to implement properly? nested object?
1767 $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
1768 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'SOA'
1769 }
1770
1771 elseif($RDataType -eq 12) {
1772 $Ptr = Get-Name $DNSRecord[24..$DNSRecord.length]
1773 $Data = $Ptr
1774 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'PTR'
1775 }
1776
1777 elseif($RDataType -eq 13) {
1778 # TODO: how to implement properly? nested object?
1779 $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
1780 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'HINFO'
1781 }
1782
1783 elseif($RDataType -eq 15) {
1784 # TODO: how to implement properly? nested object?
1785 $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
1786 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'MX'
1787 }
1788
1789 elseif($RDataType -eq 16) {
1790
1791 [string]$TXT = ""
1792 [int]$SegmentLength = $DNSRecord[24]
1793 $Index = 25
1794 while ($SegmentLength-- -gt 0) {
1795 $TXT += [char]$DNSRecord[$index++]
1796 }
1797
1798 $Data = $TXT
1799 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'TXT'
1800 }
1801
1802 elseif($RDataType -eq 28) {
1803 # TODO: how to implement properly? nested object?
1804 $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
1805 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'AAAA'
1806 }
1807
1808 elseif($RDataType -eq 33) {
1809 # TODO: how to implement properly? nested object?
1810 $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
1811 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'SRV'
1812 }
1813
1814 else {
1815 $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
1816 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'UNKNOWN'
1817 }
1818
1819 $DNSRecordObject | Add-Member Noteproperty 'UpdatedAtSerial' $UpdatedAtSerial
1820 $DNSRecordObject | Add-Member Noteproperty 'TTL' $TTL
1821 $DNSRecordObject | Add-Member Noteproperty 'Age' $Age
1822 $DNSRecordObject | Add-Member Noteproperty 'TimeStamp' $TimeStamp
1823 $DNSRecordObject | Add-Member Noteproperty 'Data' $Data
1824 $DNSRecordObject
1825}
1826
1827
1828filter Get-DNSZone {
1829<#
1830 .SYNOPSIS
1831
1832 Enumerates the Active Directory DNS zones for a given domain.
1833
1834 .PARAMETER Domain
1835
1836 The domain to query for zones, defaults to the current domain.
1837
1838 .PARAMETER DomainController
1839
1840 Domain controller to reflect LDAP queries through.
1841
1842 .PARAMETER PageSize
1843
1844 The PageSize to set for the LDAP searcher object.
1845
1846 .PARAMETER Credential
1847
1848 A [Management.Automation.PSCredential] object of alternate credentials
1849 for connection to the target domain.
1850
1851 .PARAMETER FullData
1852
1853 Switch. Return full computer objects instead of just system names (the default).
1854
1855 .EXAMPLE
1856
1857 PS C:\> Get-DNSZone
1858
1859 Retrieves the DNS zones for the current domain.
1860
1861 .EXAMPLE
1862
1863 PS C:\> Get-DNSZone -Domain dev.testlab.local -DomainController primary.testlab.local
1864
1865 Retrieves the DNS zones for the dev.testlab.local domain, reflecting the LDAP queries
1866 through the primary.testlab.local domain controller.
1867#>
1868
1869 param(
1870 [Parameter(Position=0, ValueFromPipeline=$True)]
1871 [String]
1872 $Domain,
1873
1874 [String]
1875 $DomainController,
1876
1877 [ValidateRange(1,10000)]
1878 [Int]
1879 $PageSize = 200,
1880
1881 [Management.Automation.PSCredential]
1882 $Credential,
1883
1884 [Switch]
1885 $FullData
1886 )
1887
1888 $DNSSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential
1889 $DNSSearcher.filter="(objectClass=dnsZone)"
1890
1891 if($DNSSearcher) {
1892 $Results = $DNSSearcher.FindAll()
1893 $Results | Where-Object {$_} | ForEach-Object {
1894 # convert/process the LDAP fields for each result
1895 $Properties = Convert-LDAPProperty -Properties $_.Properties
1896 $Properties | Add-Member NoteProperty 'ZoneName' $Properties.name
1897
1898 if ($FullData) {
1899 $Properties
1900 }
1901 else {
1902 $Properties | Select-Object ZoneName,distinguishedname,whencreated,whenchanged
1903 }
1904 }
1905 $Results.dispose()
1906 $DNSSearcher.dispose()
1907 }
1908
1909 $DNSSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential -ADSprefix "CN=MicrosoftDNS,DC=DomainDnsZones"
1910 $DNSSearcher.filter="(objectClass=dnsZone)"
1911
1912 if($DNSSearcher) {
1913 $Results = $DNSSearcher.FindAll()
1914 $Results | Where-Object {$_} | ForEach-Object {
1915 # convert/process the LDAP fields for each result
1916 $Properties = Convert-LDAPProperty -Properties $_.Properties
1917 $Properties | Add-Member NoteProperty 'ZoneName' $Properties.name
1918
1919 if ($FullData) {
1920 $Properties
1921 }
1922 else {
1923 $Properties | Select-Object ZoneName,distinguishedname,whencreated,whenchanged
1924 }
1925 }
1926 $Results.dispose()
1927 $DNSSearcher.dispose()
1928 }
1929}
1930
1931
1932filter Get-DNSRecord {
1933<#
1934 .SYNOPSIS
1935
1936 Enumerates the Active Directory DNS records for a given zone.
1937
1938 .PARAMETER ZoneName
1939
1940 The zone to query for records (which can be enumearted with Get-DNSZone). Required.
1941
1942 .PARAMETER Domain
1943
1944 The domain to query for zones, defaults to the current domain.
1945
1946 .PARAMETER DomainController
1947
1948 Domain controller to reflect LDAP queries through.
1949
1950 .PARAMETER PageSize
1951
1952 The PageSize to set for the LDAP searcher object.
1953
1954 .PARAMETER Credential
1955
1956 A [Management.Automation.PSCredential] object of alternate credentials
1957 for connection to the target domain.
1958
1959 .EXAMPLE
1960
1961 PS C:\> Get-DNSRecord -ZoneName testlab.local
1962
1963 Retrieve all records for the testlab.local zone.
1964
1965 .EXAMPLE
1966
1967 PS C:\> Get-DNSZone | Get-DNSRecord
1968
1969 Retrieve all records for all zones in the current domain.
1970
1971 .EXAMPLE
1972
1973 PS C:\> Get-DNSZone -Domain dev.testlab.local | Get-DNSRecord -Domain dev.testlab.local
1974
1975 Retrieve all records for all zones in the dev.testlab.local domain.
1976#>
1977
1978 param(
1979 [Parameter(Position=0, ValueFromPipelineByPropertyName=$True, Mandatory=$True)]
1980 [String]
1981 $ZoneName,
1982
1983 [String]
1984 $Domain,
1985
1986 [String]
1987 $DomainController,
1988
1989 [ValidateRange(1,10000)]
1990 [Int]
1991 $PageSize = 200,
1992
1993 [Management.Automation.PSCredential]
1994 $Credential
1995 )
1996
1997 $DNSSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential -ADSprefix "DC=$($ZoneName),CN=MicrosoftDNS,DC=DomainDnsZones"
1998 $DNSSearcher.filter="(objectClass=dnsNode)"
1999
2000 if($DNSSearcher) {
2001 $Results = $DNSSearcher.FindAll()
2002 $Results | Where-Object {$_} | ForEach-Object {
2003 try {
2004 # convert/process the LDAP fields for each result
2005 $Properties = Convert-LDAPProperty -Properties $_.Properties | Select-Object name,distinguishedname,dnsrecord,whencreated,whenchanged
2006 $Properties | Add-Member NoteProperty 'ZoneName' $ZoneName
2007
2008 # convert the record and extract the properties
2009 if ($Properties.dnsrecord -is [System.DirectoryServices.ResultPropertyValueCollection]) {
2010 # TODO: handle multiple nested records properly?
2011 $Record = Convert-DNSRecord -DNSRecord $Properties.dnsrecord[0]
2012 }
2013 else {
2014 $Record = Convert-DNSRecord -DNSRecord $Properties.dnsrecord
2015 }
2016
2017 if($Record) {
2018 $Record.psobject.properties | ForEach-Object {
2019 $Properties | Add-Member NoteProperty $_.Name $_.Value
2020 }
2021 }
2022
2023 $Properties
2024 }
2025 catch {
2026 Write-Warning "ERROR: $_"
2027 $Properties
2028 }
2029 }
2030 $Results.dispose()
2031 $DNSSearcher.dispose()
2032 }
2033}
2034
2035
2036filter Get-NetDomain {
2037<#
2038 .SYNOPSIS
2039
2040 Returns a given domain object.
2041
2042 .PARAMETER Domain
2043
2044 The domain name to query for, defaults to the current domain.
2045
2046 .PARAMETER Credential
2047
2048 A [Management.Automation.PSCredential] object of alternate credentials
2049 for connection to the target domain.
2050
2051 .EXAMPLE
2052
2053 PS C:\> Get-NetDomain -Domain testlab.local
2054
2055 .EXAMPLE
2056
2057 PS C:\> "testlab.local" | Get-NetDomain
2058
2059 .LINK
2060
2061 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
2062#>
2063
2064 param(
2065 [Parameter(ValueFromPipeline=$True)]
2066 [String]
2067 $Domain,
2068
2069 [Management.Automation.PSCredential]
2070 $Credential
2071 )
2072
2073 if($Credential) {
2074
2075 Write-Verbose "Using alternate credentials for Get-NetDomain"
2076
2077 if(!$Domain) {
2078 # if no domain is supplied, extract the logon domain from the PSCredential passed
2079 $Domain = $Credential.GetNetworkCredential().Domain
2080 Write-Verbose "Extracted domain '$Domain' from -Credential"
2081 }
2082
2083 $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain, $Credential.UserName, $Credential.GetNetworkCredential().Password)
2084
2085 try {
2086 [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
2087 }
2088 catch {
2089 Write-Verbose "The specified domain does '$Domain' not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid."
2090 $Null
2091 }
2092 }
2093 elseif($Domain) {
2094 $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain)
2095 try {
2096 [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
2097 }
2098 catch {
2099 Write-Verbose "The specified domain '$Domain' does not exist, could not be contacted, or there isn't an existing trust."
2100 $Null
2101 }
2102 }
2103 else {
2104 [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
2105 }
2106}
2107
2108
2109filter Get-NetForest {
2110<#
2111 .SYNOPSIS
2112
2113 Returns a given forest object.
2114
2115 .PARAMETER Forest
2116
2117 The forest name to query for, defaults to the current domain.
2118
2119 .PARAMETER Credential
2120
2121 A [Management.Automation.PSCredential] object of alternate credentials
2122 for connection to the target domain.
2123
2124 .EXAMPLE
2125
2126 PS C:\> Get-NetForest -Forest external.domain
2127
2128 .EXAMPLE
2129
2130 PS C:\> "external.domain" | Get-NetForest
2131#>
2132
2133 param(
2134 [Parameter(ValueFromPipeline=$True)]
2135 [String]
2136 $Forest,
2137
2138 [Management.Automation.PSCredential]
2139 $Credential
2140 )
2141
2142 if($Credential) {
2143
2144 Write-Verbose "Using alternate credentials for Get-NetForest"
2145
2146 if(!$Forest) {
2147 # if no domain is supplied, extract the logon domain from the PSCredential passed
2148 $Forest = $Credential.GetNetworkCredential().Domain
2149 Write-Verbose "Extracted domain '$Forest' from -Credential"
2150 }
2151
2152 $ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Forest, $Credential.UserName, $Credential.GetNetworkCredential().Password)
2153
2154 try {
2155 $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
2156 }
2157 catch {
2158 Write-Verbose "The specified forest '$Forest' does not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid."
2159 $Null
2160 }
2161 }
2162 elseif($Forest) {
2163 $ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Forest)
2164 try {
2165 $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
2166 }
2167 catch {
2168 Write-Verbose "The specified forest '$Forest' does not exist, could not be contacted, or there isn't an existing trust."
2169 return $Null
2170 }
2171 }
2172 else {
2173 # otherwise use the current forest
2174 $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
2175 }
2176
2177 if($ForestObject) {
2178 # get the SID of the forest root
2179 $ForestSid = (New-Object System.Security.Principal.NTAccount($ForestObject.RootDomain,"krbtgt")).Translate([System.Security.Principal.SecurityIdentifier]).Value
2180 $Parts = $ForestSid -Split "-"
2181 $ForestSid = $Parts[0..$($Parts.length-2)] -join "-"
2182 $ForestObject | Add-Member NoteProperty 'RootDomainSid' $ForestSid
2183 $ForestObject
2184 }
2185}
2186
2187
2188filter Get-NetForestDomain {
2189<#
2190 .SYNOPSIS
2191
2192 Return all domains for a given forest.
2193
2194 .PARAMETER Forest
2195
2196 The forest name to query domain for.
2197
2198 .PARAMETER Credential
2199
2200 A [Management.Automation.PSCredential] object of alternate credentials
2201 for connection to the target domain.
2202
2203 .EXAMPLE
2204
2205 PS C:\> Get-NetForestDomain
2206
2207 .EXAMPLE
2208
2209 PS C:\> Get-NetForestDomain -Forest external.local
2210#>
2211
2212 param(
2213 [Parameter(ValueFromPipeline=$True)]
2214 [String]
2215 $Forest,
2216
2217 [Management.Automation.PSCredential]
2218 $Credential
2219 )
2220
2221 $ForestObject = Get-NetForest -Forest $Forest -Credential $Credential
2222
2223 if($ForestObject) {
2224 $ForestObject.Domains
2225 }
2226}
2227
2228
2229filter Get-NetForestCatalog {
2230<#
2231 .SYNOPSIS
2232
2233 Return all global catalogs for a given forest.
2234
2235 .PARAMETER Forest
2236
2237 The forest name to query domain for.
2238
2239 .PARAMETER Credential
2240
2241 A [Management.Automation.PSCredential] object of alternate credentials
2242 for connection to the target domain.
2243
2244 .EXAMPLE
2245
2246 PS C:\> Get-NetForestCatalog
2247#>
2248
2249 param(
2250 [Parameter(ValueFromPipeline=$True)]
2251 [String]
2252 $Forest,
2253
2254 [Management.Automation.PSCredential]
2255 $Credential
2256 )
2257
2258 $ForestObject = Get-NetForest -Forest $Forest -Credential $Credential
2259
2260 if($ForestObject) {
2261 $ForestObject.FindAllGlobalCatalogs()
2262 }
2263}
2264
2265
2266filter Get-NetDomainController {
2267<#
2268 .SYNOPSIS
2269
2270 Return the current domain controllers for the active domain.
2271
2272 .PARAMETER Domain
2273
2274 The domain to query for domain controllers, defaults to the current domain.
2275
2276 .PARAMETER DomainController
2277
2278 Domain controller to reflect LDAP queries through.
2279
2280 .PARAMETER LDAP
2281
2282 Switch. Use LDAP queries to determine the domain controllers.
2283
2284 .PARAMETER Credential
2285
2286 A [Management.Automation.PSCredential] object of alternate credentials
2287 for connection to the target domain.
2288
2289 .EXAMPLE
2290
2291 PS C:\> Get-NetDomainController -Domain 'test.local'
2292
2293 Determine the domain controllers for 'test.local'.
2294
2295 .EXAMPLE
2296
2297 PS C:\> Get-NetDomainController -Domain 'test.local' -LDAP
2298
2299 Determine the domain controllers for 'test.local' using LDAP queries.
2300
2301 .EXAMPLE
2302
2303 PS C:\> 'test.local' | Get-NetDomainController
2304
2305 Determine the domain controllers for 'test.local'.
2306#>
2307
2308 [CmdletBinding()]
2309 param(
2310 [Parameter(ValueFromPipeline=$True)]
2311 [String]
2312 $Domain,
2313
2314 [String]
2315 $DomainController,
2316
2317 [Switch]
2318 $LDAP,
2319
2320 [Management.Automation.PSCredential]
2321 $Credential
2322 )
2323
2324 if($LDAP -or $DomainController) {
2325 # filter string to return all domain controllers
2326 Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -Filter '(userAccountControl:1.2.840.113556.1.4.803:=8192)'
2327 }
2328 else {
2329 $FoundDomain = Get-NetDomain -Domain $Domain -Credential $Credential
2330 if($FoundDomain) {
2331 $Founddomain.DomainControllers
2332 }
2333 }
2334}
2335
2336
2337########################################################
2338#
2339# "net *" replacements and other fun start below
2340#
2341########################################################
2342
2343function Get-NetUser {
2344<#
2345 .SYNOPSIS
2346
2347 Query information for a given user or users in the domain
2348 using ADSI and LDAP. Another -Domain can be specified to
2349 query for users across a trust.
2350 Replacement for "net users /domain"
2351
2352 .PARAMETER UserName
2353
2354 Username filter string, wildcards accepted.
2355
2356 .PARAMETER Domain
2357
2358 The domain to query for users, defaults to the current domain.
2359
2360 .PARAMETER DomainController
2361
2362 Domain controller to reflect LDAP queries through.
2363
2364 .PARAMETER ADSpath
2365
2366 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
2367 Useful for OU queries.
2368
2369 .PARAMETER Filter
2370
2371 A customized ldap filter string to use, e.g. "(description=*admin*)"
2372
2373 .PARAMETER AdminCount
2374
2375 Switch. Return users with adminCount=1.
2376
2377 .PARAMETER SPN
2378
2379 Switch. Only return user objects with non-null service principal names.
2380
2381 .PARAMETER Unconstrained
2382
2383 Switch. Return users that have unconstrained delegation.
2384
2385 .PARAMETER AllowDelegation
2386
2387 Switch. Return user accounts that are not marked as 'sensitive and not allowed for delegation'
2388
2389 .PARAMETER PageSize
2390
2391 The PageSize to set for the LDAP searcher object.
2392
2393 .PARAMETER Credential
2394
2395 A [Management.Automation.PSCredential] object of alternate credentials
2396 for connection to the target domain.
2397
2398 .EXAMPLE
2399
2400 PS C:\> Get-NetUser -Domain testing
2401
2402 .EXAMPLE
2403
2404 PS C:\> Get-NetUser -ADSpath "LDAP://OU=secret,DC=testlab,DC=local"
2405#>
2406
2407 param(
2408 [Parameter(Position=0, ValueFromPipeline=$True)]
2409 [String]
2410 $UserName,
2411
2412 [String]
2413 $Domain,
2414
2415 [String]
2416 $DomainController,
2417
2418 [String]
2419 $ADSpath,
2420
2421 [String]
2422 $Filter,
2423
2424 [Switch]
2425 $SPN,
2426
2427 [Switch]
2428 $AdminCount,
2429
2430 [Switch]
2431 $Unconstrained,
2432
2433 [Switch]
2434 $AllowDelegation,
2435
2436 [ValidateRange(1,10000)]
2437 [Int]
2438 $PageSize = 200,
2439
2440 [Management.Automation.PSCredential]
2441 $Credential
2442 )
2443
2444 begin {
2445 # so this isn't repeated if users are passed on the pipeline
2446 $UserSearcher = Get-DomainSearcher -Domain $Domain -ADSpath $ADSpath -DomainController $DomainController -PageSize $PageSize -Credential $Credential
2447 }
2448
2449 process {
2450 if($UserSearcher) {
2451
2452 # if we're checking for unconstrained delegation
2453 if($Unconstrained) {
2454 Write-Verbose "Checking for unconstrained delegation"
2455 $Filter += "(userAccountControl:1.2.840.113556.1.4.803:=524288)"
2456 }
2457 if($AllowDelegation) {
2458 Write-Verbose "Checking for users who can be delegated"
2459 # negation of "Accounts that are sensitive and not trusted for delegation"
2460 $Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=1048574))"
2461 }
2462 if($AdminCount) {
2463 Write-Verbose "Checking for adminCount=1"
2464 $Filter += "(admincount=1)"
2465 }
2466
2467 # check if we're using a username filter or not
2468 if($UserName) {
2469 # samAccountType=805306368 indicates user objects
2470 $UserSearcher.filter="(&(samAccountType=805306368)(samAccountName=$UserName)$Filter)"
2471 }
2472 elseif($SPN) {
2473 $UserSearcher.filter="(&(samAccountType=805306368)(servicePrincipalName=*)$Filter)"
2474 }
2475 else {
2476 # filter is something like "(samAccountName=*blah*)" if specified
2477 $UserSearcher.filter="(&(samAccountType=805306368)$Filter)"
2478 }
2479
2480 $Results = $UserSearcher.FindAll()
2481 $Results | Where-Object {$_} | ForEach-Object {
2482 # convert/process the LDAP fields for each result
2483 $User = Convert-LDAPProperty -Properties $_.Properties
2484 $User.PSObject.TypeNames.Add('PowerView.User')
2485 $User
2486 }
2487 $Results.dispose()
2488 $UserSearcher.dispose()
2489 }
2490 }
2491}
2492
2493
2494function Add-NetUser {
2495<#
2496 .SYNOPSIS
2497
2498 Adds a domain user or a local user to the current (or remote) machine,
2499 if permissions allow, utilizing the WinNT service provider and
2500 DirectoryServices.AccountManagement, respectively.
2501
2502 The default behavior is to add a user to the local machine.
2503 An optional group name to add the user to can be specified.
2504
2505 .PARAMETER UserName
2506
2507 The username to add. If not given, it defaults to 'backdoor'
2508
2509 .PARAMETER Password
2510
2511 The password to set for the added user. If not given, it defaults to 'Password123!'
2512
2513 .PARAMETER GroupName
2514
2515 Group to optionally add the user to.
2516
2517 .PARAMETER ComputerName
2518
2519 Hostname to add the local user to, defaults to 'localhost'
2520
2521 .PARAMETER Domain
2522
2523 Specified domain to add the user to.
2524
2525 .EXAMPLE
2526
2527 PS C:\> Add-NetUser -UserName john -Password 'Password123!'
2528
2529 Adds a localuser 'john' to the local machine with password of 'Password123!'
2530
2531 .EXAMPLE
2532
2533 PS C:\> Add-NetUser -UserName john -Password 'Password123!' -ComputerName server.testlab.local
2534
2535 Adds a localuser 'john' with password of 'Password123!' to server.testlab.local's local Administrators group.
2536
2537 .EXAMPLE
2538
2539 PS C:\> Add-NetUser -UserName john -Password password -GroupName "Domain Admins" -Domain ''
2540
2541 Adds the user "john" with password "password" to the current domain and adds
2542 the user to the domain group "Domain Admins"
2543
2544 .EXAMPLE
2545
2546 PS C:\> Add-NetUser -UserName john -Password password -GroupName "Domain Admins" -Domain 'testing'
2547
2548 Adds the user "john" with password "password" to the 'testing' domain and adds
2549 the user to the domain group "Domain Admins"
2550
2551 .Link
2552
2553 http://blogs.technet.com/b/heyscriptingguy/archive/2010/11/23/use-powershell-to-create-local-user-accounts.aspx
2554#>
2555
2556 [CmdletBinding()]
2557 Param (
2558 [ValidateNotNullOrEmpty()]
2559 [String]
2560 $UserName = 'backdoor',
2561
2562 [ValidateNotNullOrEmpty()]
2563 [String]
2564 $Password = 'Password123!',
2565
2566 [ValidateNotNullOrEmpty()]
2567 [String]
2568 $GroupName,
2569
2570 [ValidateNotNullOrEmpty()]
2571 [Alias('HostName')]
2572 [String]
2573 $ComputerName = 'localhost',
2574
2575 [ValidateNotNullOrEmpty()]
2576 [String]
2577 $Domain
2578 )
2579
2580 if ($Domain) {
2581
2582 $DomainObject = Get-NetDomain -Domain $Domain
2583 if(-not $DomainObject) {
2584 Write-Warning "Error in grabbing $Domain object"
2585 return $Null
2586 }
2587
2588 # add the assembly we need
2589 Add-Type -AssemblyName System.DirectoryServices.AccountManagement
2590
2591 # http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/
2592 # get the domain context
2593 $Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain), $DomainObject
2594
2595 # create the user object
2596 $User = New-Object -TypeName System.DirectoryServices.AccountManagement.UserPrincipal -ArgumentList $Context
2597
2598 # set user properties
2599 $User.Name = $UserName
2600 $User.SamAccountName = $UserName
2601 $User.PasswordNotRequired = $False
2602 $User.SetPassword($Password)
2603 $User.Enabled = $True
2604
2605 Write-Verbose "Creating user $UserName to with password '$Password' in domain $Domain"
2606
2607 try {
2608 # commit the user
2609 $User.Save()
2610 "[*] User $UserName successfully created in domain $Domain"
2611 }
2612 catch {
2613 Write-Warning '[!] User already exists!'
2614 return
2615 }
2616 }
2617 else {
2618
2619 Write-Verbose "Creating user $UserName to with password '$Password' on $ComputerName"
2620
2621 # if it's not a domain add, it's a local machine add
2622 $ObjOu = [ADSI]"WinNT://$ComputerName"
2623 $ObjUser = $ObjOu.Create('User', $UserName)
2624 $ObjUser.SetPassword($Password)
2625
2626 # commit the changes to the local machine
2627 try {
2628 $Null = $ObjUser.SetInfo()
2629 "[*] User $UserName successfully created on host $ComputerName"
2630 }
2631 catch {
2632 Write-Warning '[!] Account already exists!'
2633 return
2634 }
2635 }
2636
2637 # if a group is specified, invoke Add-NetGroupUser and return its value
2638 if ($GroupName) {
2639 # if we're adding the user to a domain
2640 if ($Domain) {
2641 Add-NetGroupUser -UserName $UserName -GroupName $GroupName -Domain $Domain
2642 "[*] User $UserName successfully added to group $GroupName in domain $Domain"
2643 }
2644 # otherwise, we're adding to a local group
2645 else {
2646 Add-NetGroupUser -UserName $UserName -GroupName $GroupName -ComputerName $ComputerName
2647 "[*] User $UserName successfully added to group $GroupName on host $ComputerName"
2648 }
2649 }
2650}
2651
2652
2653function Add-NetGroupUser {
2654<#
2655 .SYNOPSIS
2656
2657 Adds a user to a domain group or a local group on the current (or remote) machine,
2658 if permissions allow, utilizing the WinNT service provider and
2659 DirectoryServices.AccountManagement, respectively.
2660
2661 .PARAMETER UserName
2662
2663 The domain username to query for.
2664
2665 .PARAMETER GroupName
2666
2667 Group to add the user to.
2668
2669 .PARAMETER ComputerName
2670
2671 Hostname to add the user to, defaults to localhost.
2672
2673 .PARAMETER Domain
2674
2675 Domain to add the user to.
2676
2677 .EXAMPLE
2678
2679 PS C:\> Add-NetGroupUser -UserName john -GroupName Administrators
2680
2681 Adds a localuser "john" to the local group "Administrators"
2682
2683 .EXAMPLE
2684
2685 PS C:\> Add-NetGroupUser -UserName john -GroupName "Domain Admins" -Domain dev.local
2686
2687 Adds the existing user "john" to the domain group "Domain Admins" in "dev.local"
2688#>
2689
2690 [CmdletBinding()]
2691 param(
2692 [Parameter(Mandatory = $True)]
2693 [ValidateNotNullOrEmpty()]
2694 [String]
2695 $UserName,
2696
2697 [Parameter(Mandatory = $True)]
2698 [ValidateNotNullOrEmpty()]
2699 [String]
2700 $GroupName,
2701
2702 [ValidateNotNullOrEmpty()]
2703 [Alias('HostName')]
2704 [String]
2705 $ComputerName,
2706
2707 [String]
2708 $Domain
2709 )
2710
2711 # add the assembly if we need it
2712 Add-Type -AssemblyName System.DirectoryServices.AccountManagement
2713
2714 # if we're adding to a remote host's local group, use the WinNT provider
2715 if($ComputerName -and ($ComputerName -ne "localhost")) {
2716 try {
2717 Write-Verbose "Adding user $UserName to $GroupName on host $ComputerName"
2718 ([ADSI]"WinNT://$ComputerName/$GroupName,group").add("WinNT://$ComputerName/$UserName,user")
2719 "[*] User $UserName successfully added to group $GroupName on $ComputerName"
2720 }
2721 catch {
2722 Write-Warning "[!] Error adding user $UserName to group $GroupName on $ComputerName"
2723 return
2724 }
2725 }
2726
2727 # otherwise it's a local machine or domain add
2728 else {
2729 try {
2730 if ($Domain) {
2731 Write-Verbose "Adding user $UserName to $GroupName on domain $Domain"
2732 $CT = [System.DirectoryServices.AccountManagement.ContextType]::Domain
2733 $DomainObject = Get-NetDomain -Domain $Domain
2734 if(-not $DomainObject) {
2735 return $Null
2736 }
2737 # get the full principal context
2738 $Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList $CT, $DomainObject
2739 }
2740 else {
2741 # otherwise, get the local machine context
2742 Write-Verbose "Adding user $UserName to $GroupName on localhost"
2743 $Context = New-Object System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::Machine, $Env:ComputerName)
2744 }
2745
2746 # find the particular group
2747 $Group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($Context,$GroupName)
2748
2749 # add the particular user to the group
2750 $Group.Members.add($Context, [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName, $UserName)
2751
2752 # commit the changes
2753 $Group.Save()
2754 }
2755 catch {
2756 Write-Warning "Error adding $UserName to $GroupName : $_"
2757 }
2758 }
2759}
2760
2761
2762function Get-UserProperty {
2763<#
2764 .SYNOPSIS
2765
2766 Returns a list of all user object properties. If a property
2767 name is specified, it returns all [user:property] values.
2768
2769 Taken directly from @obscuresec's post:
2770 http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
2771
2772 .PARAMETER Properties
2773
2774 Property names to extract for users.
2775
2776 .PARAMETER Domain
2777
2778 The domain to query for user properties, defaults to the current domain.
2779
2780 .PARAMETER DomainController
2781
2782 Domain controller to reflect LDAP queries through.
2783
2784 .PARAMETER PageSize
2785
2786 The PageSize to set for the LDAP searcher object.
2787
2788 .PARAMETER Credential
2789
2790 A [Management.Automation.PSCredential] object of alternate credentials
2791 for connection to the target domain.
2792
2793 .EXAMPLE
2794
2795 PS C:\> Get-UserProperty -Domain testing
2796
2797 Returns all user properties for users in the 'testing' domain.
2798
2799 .EXAMPLE
2800
2801 PS C:\> Get-UserProperty -Properties ssn,lastlogon,location
2802
2803 Returns all an array of user/ssn/lastlogin/location combinations
2804 for users in the current domain.
2805
2806 .LINK
2807
2808 http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
2809#>
2810
2811 [CmdletBinding()]
2812 param(
2813 [String[]]
2814 $Properties,
2815
2816 [String]
2817 $Domain,
2818
2819 [String]
2820 $DomainController,
2821
2822 [ValidateRange(1,10000)]
2823 [Int]
2824 $PageSize = 200,
2825
2826 [Management.Automation.PSCredential]
2827 $Credential
2828 )
2829
2830 if($Properties) {
2831 # extract out the set of all properties for each object
2832 $Properties = ,"name" + $Properties
2833 Get-NetUser -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential | Select-Object -Property $Properties
2834 }
2835 else {
2836 # extract out just the property names
2837 Get-NetUser -Domain $Domain -DomainController $DomainController -PageSize $PageSize -Credential $Credential | Select-Object -First 1 | Get-Member -MemberType *Property | Select-Object -Property 'Name'
2838 }
2839}
2840
2841
2842filter Find-UserField {
2843<#
2844 .SYNOPSIS
2845
2846 Searches user object fields for a given word (default *pass*). Default
2847 field being searched is 'description'.
2848
2849 Taken directly from @obscuresec's post:
2850 http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
2851
2852 .PARAMETER SearchTerm
2853
2854 Term to search for, default of "pass".
2855
2856 .PARAMETER SearchField
2857
2858 User field to search, default of "description".
2859
2860 .PARAMETER ADSpath
2861
2862 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
2863 Useful for OU queries.
2864
2865 .PARAMETER Domain
2866
2867 Domain to search computer fields for, defaults to the current domain.
2868
2869 .PARAMETER DomainController
2870
2871 Domain controller to reflect LDAP queries through.
2872
2873 .PARAMETER PageSize
2874
2875 The PageSize to set for the LDAP searcher object.
2876
2877 .PARAMETER Credential
2878
2879 A [Management.Automation.PSCredential] object of alternate credentials
2880 for connection to the target domain.
2881
2882 .EXAMPLE
2883
2884 PS C:\> Find-UserField -SearchField info -SearchTerm backup
2885
2886 Find user accounts with "backup" in the "info" field.
2887#>
2888
2889 [CmdletBinding()]
2890 param(
2891 [Parameter(Position=0,ValueFromPipeline=$True)]
2892 [String]
2893 $SearchTerm = 'pass',
2894
2895 [String]
2896 $SearchField = 'description',
2897
2898 [String]
2899 $ADSpath,
2900
2901 [String]
2902 $Domain,
2903
2904 [String]
2905 $DomainController,
2906
2907 [ValidateRange(1,10000)]
2908 [Int]
2909 $PageSize = 200,
2910
2911 [Management.Automation.PSCredential]
2912 $Credential
2913 )
2914
2915 Get-NetUser -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Credential $Credential -Filter "($SearchField=*$SearchTerm*)" -PageSize $PageSize | Select-Object samaccountname,$SearchField
2916}
2917
2918
2919filter Get-UserEvent {
2920<#
2921 .SYNOPSIS
2922
2923 Dump and parse security events relating to an account logon (ID 4624)
2924 or a TGT request event (ID 4768). Intended to be used and tested on
2925 Windows 2008 Domain Controllers.
2926 Admin Reqd? YES
2927
2928 Author: @sixdub
2929
2930 .PARAMETER ComputerName
2931
2932 The computer to get events from. Default: Localhost
2933
2934 .PARAMETER EventType
2935
2936 Either 'logon', 'tgt', or 'all'. Defaults: 'logon'
2937
2938 .PARAMETER DateStart
2939
2940 Filter out all events before this date. Default: 5 days
2941
2942 .PARAMETER Credential
2943
2944 A [Management.Automation.PSCredential] object of alternate credentials
2945 for connection to the target domain.
2946
2947 .EXAMPLE
2948
2949 PS C:\> Get-UserEvent -ComputerName DomainController.testlab.local
2950
2951 .LINK
2952
2953 http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/
2954#>
2955
2956 Param(
2957 [Parameter(ValueFromPipeline=$True)]
2958 [String]
2959 $ComputerName = $Env:ComputerName,
2960
2961 [String]
2962 [ValidateSet("logon","tgt","all")]
2963 $EventType = "logon",
2964
2965 [DateTime]
2966 $DateStart = [DateTime]::Today.AddDays(-5),
2967
2968 [Management.Automation.PSCredential]
2969 $Credential
2970 )
2971
2972 if($EventType.ToLower() -like "logon") {
2973 [Int32[]]$ID = @(4624)
2974 }
2975 elseif($EventType.ToLower() -like "tgt") {
2976 [Int32[]]$ID = @(4768)
2977 }
2978 else {
2979 [Int32[]]$ID = @(4624, 4768)
2980 }
2981
2982 if($Credential) {
2983 Write-Verbose "Using alternative credentials"
2984 $Arguments = @{
2985 'ComputerName' = $ComputerName;
2986 'Credential' = $Credential;
2987 'FilterHashTable' = @{ LogName = 'Security'; ID=$ID; StartTime=$DateStart};
2988 'ErrorAction' = 'SilentlyContinue';
2989 }
2990 }
2991 else {
2992 $Arguments = @{
2993 'ComputerName' = $ComputerName;
2994 'FilterHashTable' = @{ LogName = 'Security'; ID=$ID; StartTime=$DateStart};
2995 'ErrorAction' = 'SilentlyContinue';
2996 }
2997 }
2998
2999 # grab all events matching our filter for the specified host
3000 Get-WinEvent @Arguments | ForEach-Object {
3001
3002 if($ID -contains 4624) {
3003 # first parse and check the logon event type. This could be later adapted and tested for RDP logons (type 10)
3004 if($_.message -match '(?s)(?<=Logon Type:).*?(?=(Impersonation Level:|New Logon:))') {
3005 if($Matches) {
3006 $LogonType = $Matches[0].trim()
3007 $Matches = $Null
3008 }
3009 }
3010 else {
3011 $LogonType = ""
3012 }
3013
3014 # interactive logons or domain logons
3015 if (($LogonType -eq 2) -or ($LogonType -eq 3)) {
3016 try {
3017 # parse and store the account used and the address they came from
3018 if($_.message -match '(?s)(?<=New Logon:).*?(?=Process Information:)') {
3019 if($Matches) {
3020 $UserName = $Matches[0].split("`n")[2].split(":")[1].trim()
3021 $Domain = $Matches[0].split("`n")[3].split(":")[1].trim()
3022 $Matches = $Null
3023 }
3024 }
3025 if($_.message -match '(?s)(?<=Network Information:).*?(?=Source Port:)') {
3026 if($Matches) {
3027 $Address = $Matches[0].split("`n")[2].split(":")[1].trim()
3028 $Matches = $Null
3029 }
3030 }
3031
3032 # only add if there was account information not for a machine or anonymous logon
3033 if ($UserName -and (-not $UserName.endsWith('$')) -and ($UserName -ne 'ANONYMOUS LOGON')) {
3034 $LogonEventProperties = @{
3035 'Domain' = $Domain
3036 'ComputerName' = $ComputerName
3037 'Username' = $UserName
3038 'Address' = $Address
3039 'ID' = '4624'
3040 'LogonType' = $LogonType
3041 'Time' = $_.TimeCreated
3042 }
3043 New-Object -TypeName PSObject -Property $LogonEventProperties
3044 }
3045 }
3046 catch {
3047 Write-Verbose "Error parsing event logs: $_"
3048 }
3049 }
3050 }
3051 if($ID -contains 4768) {
3052 # the TGT event type
3053 try {
3054 if($_.message -match '(?s)(?<=Account Information:).*?(?=Service Information:)') {
3055 if($Matches) {
3056 $Username = $Matches[0].split("`n")[1].split(":")[1].trim()
3057 $Domain = $Matches[0].split("`n")[2].split(":")[1].trim()
3058 $Matches = $Null
3059 }
3060 }
3061
3062 if($_.message -match '(?s)(?<=Network Information:).*?(?=Additional Information:)') {
3063 if($Matches) {
3064 $Address = $Matches[0].split("`n")[1].split(":")[-1].trim()
3065 $Matches = $Null
3066 }
3067 }
3068
3069 $LogonEventProperties = @{
3070 'Domain' = $Domain
3071 'ComputerName' = $ComputerName
3072 'Username' = $UserName
3073 'Address' = $Address
3074 'ID' = '4768'
3075 'LogonType' = ''
3076 'Time' = $_.TimeCreated
3077 }
3078
3079 New-Object -TypeName PSObject -Property $LogonEventProperties
3080 }
3081 catch {
3082 Write-Verbose "Error parsing event logs: $_"
3083 }
3084 }
3085 }
3086}
3087
3088
3089function Get-ObjectAcl {
3090<#
3091 .SYNOPSIS
3092 Returns the ACLs associated with a specific active directory object.
3093
3094 Thanks Sean Metcalf (@pyrotek3) for the idea and guidance.
3095
3096 .PARAMETER SamAccountName
3097
3098 Object name to filter for.
3099
3100 .PARAMETER Name
3101
3102 Object name to filter for.
3103
3104 .PARAMETER DistinguishedName
3105
3106 Object distinguished name to filter for.
3107
3108 .PARAMETER ResolveGUIDs
3109
3110 Switch. Resolve GUIDs to their display names.
3111
3112 .PARAMETER Filter
3113
3114 A customized ldap filter string to use, e.g. "(description=*admin*)"
3115
3116 .PARAMETER ADSpath
3117
3118 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
3119 Useful for OU queries.
3120
3121 .PARAMETER ADSprefix
3122
3123 Prefix to set for the searcher (like "CN=Sites,CN=Configuration")
3124
3125 .PARAMETER RightsFilter
3126
3127 Only return results with the associated rights, "All", "ResetPassword","WriteMembers"
3128
3129 .PARAMETER Domain
3130
3131 The domain to use for the query, defaults to the current domain.
3132
3133 .PARAMETER DomainController
3134
3135 Domain controller to reflect LDAP queries through.
3136
3137 .PARAMETER PageSize
3138
3139 The PageSize to set for the LDAP searcher object.
3140
3141 .EXAMPLE
3142
3143 PS C:\> Get-ObjectAcl -SamAccountName matt.admin -domain testlab.local
3144
3145 Get the ACLs for the matt.admin user in the testlab.local domain
3146
3147 .EXAMPLE
3148
3149 PS C:\> Get-ObjectAcl -SamAccountName matt.admin -domain testlab.local -ResolveGUIDs
3150
3151 Get the ACLs for the matt.admin user in the testlab.local domain and
3152 resolve relevant GUIDs to their display names.
3153
3154 .EXAMPLE
3155
3156 PS C:\> Get-NetOU -FullData | Get-ObjectAcl -ResolveGUIDs
3157
3158 Enumerate the ACL permissions for all OUs in the domain.
3159#>
3160
3161 [CmdletBinding()]
3162 Param (
3163 [Parameter(ValueFromPipelineByPropertyName=$True)]
3164 [String]
3165 $SamAccountName,
3166
3167 [Parameter(ValueFromPipelineByPropertyName=$True)]
3168 [String]
3169 $Name = "*",
3170
3171 [Parameter(ValueFromPipelineByPropertyName=$True)]
3172 [String]
3173 $DistinguishedName = "*",
3174
3175 [Switch]
3176 $ResolveGUIDs,
3177
3178 [String]
3179 $Filter,
3180
3181 [String]
3182 $ADSpath,
3183
3184 [String]
3185 $ADSprefix,
3186
3187 [String]
3188 [ValidateSet("All","ResetPassword","WriteMembers")]
3189 $RightsFilter,
3190
3191 [String]
3192 $Domain,
3193
3194 [String]
3195 $DomainController,
3196
3197 [ValidateRange(1,10000)]
3198 [Int]
3199 $PageSize = 200
3200 )
3201
3202 begin {
3203 $Searcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -ADSprefix $ADSprefix -PageSize $PageSize
3204
3205 # get a GUID -> name mapping
3206 if($ResolveGUIDs) {
3207 $GUIDs = Get-GUIDMap -Domain $Domain -DomainController $DomainController -PageSize $PageSize
3208 }
3209 }
3210
3211 process {
3212
3213 if ($Searcher) {
3214
3215 if($SamAccountName) {
3216 $Searcher.filter="(&(samaccountname=$SamAccountName)(name=$Name)(distinguishedname=$DistinguishedName)$Filter)"
3217 }
3218 else {
3219 $Searcher.filter="(&(name=$Name)(distinguishedname=$DistinguishedName)$Filter)"
3220 }
3221
3222 try {
3223 $Results = $Searcher.FindAll()
3224 $Results | Where-Object {$_} | ForEach-Object {
3225 $Object = [adsi]($_.path)
3226
3227 if($Object.distinguishedname) {
3228 $Access = $Object.PsBase.ObjectSecurity.access
3229 $Access | ForEach-Object {
3230 $_ | Add-Member NoteProperty 'ObjectDN' $Object.distinguishedname[0]
3231
3232 if($Object.objectsid[0]){
3233 $S = (New-Object System.Security.Principal.SecurityIdentifier($Object.objectsid[0],0)).Value
3234 }
3235 else {
3236 $S = $Null
3237 }
3238
3239 $_ | Add-Member NoteProperty 'ObjectSID' $S
3240 $_
3241 }
3242 }
3243 } | ForEach-Object {
3244 if($RightsFilter) {
3245 $GuidFilter = Switch ($RightsFilter) {
3246 "ResetPassword" { "00299570-246d-11d0-a768-00aa006e0529" }
3247 "WriteMembers" { "bf9679c0-0de6-11d0-a285-00aa003049e2" }
3248 Default { "00000000-0000-0000-0000-000000000000"}
3249 }
3250 if($_.ObjectType -eq $GuidFilter) { $_ }
3251 }
3252 else {
3253 $_
3254 }
3255 } | ForEach-Object {
3256 if($GUIDs) {
3257 # if we're resolving GUIDs, map them them to the resolved hash table
3258 $AclProperties = @{}
3259 $_.psobject.properties | ForEach-Object {
3260 if( ($_.Name -eq 'ObjectType') -or ($_.Name -eq 'InheritedObjectType') ) {
3261 try {
3262 $AclProperties[$_.Name] = $GUIDS[$_.Value.toString()]
3263 }
3264 catch {
3265 $AclProperties[$_.Name] = $_.Value
3266 }
3267 }
3268 else {
3269 $AclProperties[$_.Name] = $_.Value
3270 }
3271 }
3272 New-Object -TypeName PSObject -Property $AclProperties
3273 }
3274 else { $_ }
3275 }
3276 $Results.dispose()
3277 $Searcher.dispose()
3278 }
3279 catch {
3280 Write-Warning $_
3281 }
3282 }
3283 }
3284}
3285
3286
3287function Add-ObjectAcl {
3288<#
3289 .SYNOPSIS
3290
3291 Adds an ACL for a specific active directory object.
3292
3293 AdminSDHolder ACL approach from Sean Metcalf (@pyrotek3)
3294 https://adsecurity.org/?p=1906
3295
3296 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.
3297
3298 'ResetPassword' doesn't need to know the user's current password
3299 'WriteMembers' allows for the modification of group membership
3300
3301 .PARAMETER TargetSamAccountName
3302
3303 Target object name to filter for.
3304
3305 .PARAMETER TargetName
3306
3307 Target object name to filter for.
3308
3309 .PARAMETER TargetDistinguishedName
3310
3311 Target object distinguished name to filter for.
3312
3313 .PARAMETER TargetFilter
3314
3315 A customized ldap filter string to use to find a target, e.g. "(description=*admin*)"
3316
3317 .PARAMETER TargetADSpath
3318
3319 The LDAP source for the target, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
3320
3321 .PARAMETER TargetADSprefix
3322
3323 Prefix to set for the target searcher (like "CN=Sites,CN=Configuration")
3324
3325 .PARAMETER PrincipalSID
3326
3327 The SID of the principal object to add for access.
3328
3329 .PARAMETER PrincipalName
3330
3331 The name of the principal object to add for access.
3332
3333 .PARAMETER PrincipalSamAccountName
3334
3335 The samAccountName of the principal object to add for access.
3336
3337 .PARAMETER Rights
3338
3339 Rights to add for the principal, "All","ResetPassword","WriteMembers","DCSync"
3340
3341 .PARAMETER Domain
3342
3343 The domain to use for the target query, defaults to the current domain.
3344
3345 .PARAMETER DomainController
3346
3347 Domain controller to reflect LDAP queries through.
3348
3349 .PARAMETER PageSize
3350
3351 The PageSize to set for the LDAP searcher object.
3352
3353 .EXAMPLE
3354
3355 Add-ObjectAcl -TargetSamAccountName matt -PrincipalSamAccountName john
3356
3357 Grants 'john' all full access rights to the 'matt' account.
3358
3359 .EXAMPLE
3360
3361 Add-ObjectAcl -TargetSamAccountName matt -PrincipalSamAccountName john -Rights ResetPassword
3362
3363 Grants 'john' the right to reset the password for the 'matt' account.
3364
3365 .LINK
3366
3367 https://adsecurity.org/?p=1906
3368
3369 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
3370#>
3371
3372 [CmdletBinding()]
3373 Param (
3374 [String]
3375 $TargetSamAccountName,
3376
3377 [String]
3378 $TargetName = "*",
3379
3380 [Alias('DN')]
3381 [String]
3382 $TargetDistinguishedName = "*",
3383
3384 [String]
3385 $TargetFilter,
3386
3387 [String]
3388 $TargetADSpath,
3389
3390 [String]
3391 $TargetADSprefix,
3392
3393 [String]
3394 [ValidatePattern('^S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+')]
3395 $PrincipalSID,
3396
3397 [String]
3398 $PrincipalName,
3399
3400 [String]
3401 $PrincipalSamAccountName,
3402
3403 [String]
3404 [ValidateSet("All","ResetPassword","WriteMembers","DCSync")]
3405 $Rights = "All",
3406
3407 [String]
3408 $RightsGUID,
3409
3410 [String]
3411 $Domain,
3412
3413 [String]
3414 $DomainController,
3415
3416 [ValidateRange(1,10000)]
3417 [Int]
3418 $PageSize = 200
3419 )
3420
3421 begin {
3422 $Searcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $TargetADSpath -ADSprefix $TargetADSprefix -PageSize $PageSize
3423
3424 if($PrincipalSID) {
3425 $ResolvedPrincipalSID = $PrincipalSID
3426 }
3427 else {
3428 $Principal = Get-ADObject -Domain $Domain -DomainController $DomainController -Name $PrincipalName -SamAccountName $PrincipalSamAccountName -PageSize $PageSize
3429
3430 if(!$Principal) {
3431 throw "Error resolving principal"
3432 }
3433 $ResolvedPrincipalSID = $Principal.objectsid
3434 }
3435 if(!$ResolvedPrincipalSID) {
3436 throw "Error resolving principal"
3437 }
3438 }
3439
3440 process {
3441
3442 if ($Searcher) {
3443
3444 if($TargetSamAccountName) {
3445 $Searcher.filter="(&(samaccountname=$TargetSamAccountName)(name=$TargetName)(distinguishedname=$TargetDistinguishedName)$TargetFilter)"
3446 }
3447 else {
3448 $Searcher.filter="(&(name=$TargetName)(distinguishedname=$TargetDistinguishedName)$TargetFilter)"
3449 }
3450
3451 try {
3452 $Results = $Searcher.FindAll()
3453 $Results | Where-Object {$_} | ForEach-Object {
3454
3455 # 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
3456
3457 $TargetDN = $_.Properties.distinguishedname
3458
3459 $Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$ResolvedPrincipalSID)
3460 $InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] "None"
3461 $ControlType = [System.Security.AccessControl.AccessControlType] "Allow"
3462 $ACEs = @()
3463
3464 if($RightsGUID) {
3465 $GUIDs = @($RightsGUID)
3466 }
3467 else {
3468 $GUIDs = Switch ($Rights) {
3469 # ResetPassword doesn't need to know the user's current password
3470 "ResetPassword" { "00299570-246d-11d0-a768-00aa006e0529" }
3471 # allows for the modification of group membership
3472 "WriteMembers" { "bf9679c0-0de6-11d0-a285-00aa003049e2" }
3473 # 'DS-Replication-Get-Changes' = 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2
3474 # 'DS-Replication-Get-Changes-All' = 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2
3475 # 'DS-Replication-Get-Changes-In-Filtered-Set' = 89e95b76-444d-4c62-991a-0facbeda640c
3476 # when applied to a domain's ACL, allows for the use of DCSync
3477 "DCSync" { "1131f6aa-9c07-11d1-f79f-00c04fc2dcd2", "1131f6ad-9c07-11d1-f79f-00c04fc2dcd2", "89e95b76-444d-4c62-991a-0facbeda640c"}
3478 }
3479 }
3480
3481 if($GUIDs) {
3482 foreach($GUID in $GUIDs) {
3483 $NewGUID = New-Object Guid $GUID
3484 $ADRights = [System.DirectoryServices.ActiveDirectoryRights] "ExtendedRight"
3485 $ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity,$ADRights,$ControlType,$NewGUID,$InheritanceType
3486 }
3487 }
3488 else {
3489 # deault to GenericAll rights
3490 $ADRights = [System.DirectoryServices.ActiveDirectoryRights] "GenericAll"
3491 $ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity,$ADRights,$ControlType,$InheritanceType
3492 }
3493
3494 Write-Verbose "Granting principal $ResolvedPrincipalSID '$Rights' on $($_.Properties.distinguishedname)"
3495
3496 try {
3497 # add all the new ACEs to the specified object
3498 ForEach ($ACE in $ACEs) {
3499 Write-Verbose "Granting principal $ResolvedPrincipalSID '$($ACE.ObjectType)' rights on $($_.Properties.distinguishedname)"
3500 $Object = [adsi]($_.path)
3501 $Object.PsBase.ObjectSecurity.AddAccessRule($ACE)
3502 $Object.PsBase.commitchanges()
3503 }
3504 }
3505 catch {
3506 Write-Warning "Error granting principal $ResolvedPrincipalSID '$Rights' on $TargetDN : $_"
3507 }
3508 }
3509 $Results.dispose()
3510 $Searcher.dispose()
3511 }
3512 catch {
3513 Write-Warning "Error: $_"
3514 }
3515 }
3516 }
3517}
3518
3519
3520function Invoke-ACLScanner {
3521<#
3522 .SYNOPSIS
3523 Searches for ACLs for specifable AD objects (default to all domain objects)
3524 with a domain sid of > -1000, and have modifiable rights.
3525
3526 Thanks Sean Metcalf (@pyrotek3) for the idea and guidance.
3527
3528 .PARAMETER SamAccountName
3529
3530 Object name to filter for.
3531
3532 .PARAMETER Name
3533
3534 Object name to filter for.
3535
3536 .PARAMETER DistinguishedName
3537
3538 Object distinguished name to filter for.
3539
3540 .PARAMETER Filter
3541
3542 A customized ldap filter string to use, e.g. "(description=*admin*)"
3543
3544 .PARAMETER ADSpath
3545
3546 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
3547 Useful for OU queries.
3548
3549 .PARAMETER ADSprefix
3550
3551 Prefix to set for the searcher (like "CN=Sites,CN=Configuration")
3552
3553 .PARAMETER Domain
3554
3555 The domain to use for the query, defaults to the current domain.
3556
3557 .PARAMETER DomainController
3558
3559 Domain controller to reflect LDAP queries through.
3560
3561 .PARAMETER ResolveGUIDs
3562
3563 Switch. Resolve GUIDs to their display names.
3564
3565 .PARAMETER PageSize
3566
3567 The PageSize to set for the LDAP searcher object.
3568
3569 .EXAMPLE
3570
3571 PS C:\> Invoke-ACLScanner -ResolveGUIDs | Export-CSV -NoTypeInformation acls.csv
3572
3573 Enumerate all modifable ACLs in the current domain, resolving GUIDs to display
3574 names, and export everything to a .csv
3575#>
3576
3577 [CmdletBinding()]
3578 Param (
3579 [Parameter(ValueFromPipeline=$True)]
3580 [String]
3581 $SamAccountName,
3582
3583 [String]
3584 $Name = "*",
3585
3586 [Alias('DN')]
3587 [String]
3588 $DistinguishedName = "*",
3589
3590 [String]
3591 $Filter,
3592
3593 [String]
3594 $ADSpath,
3595
3596 [String]
3597 $ADSprefix,
3598
3599 [String]
3600 $Domain,
3601
3602 [String]
3603 $DomainController,
3604
3605 [Switch]
3606 $ResolveGUIDs,
3607
3608 [ValidateRange(1,10000)]
3609 [Int]
3610 $PageSize = 200
3611 )
3612
3613 # Get all domain ACLs with the appropriate parameters
3614 Get-ObjectACL @PSBoundParameters | ForEach-Object {
3615 # add in the translated SID for the object identity
3616 $_ | Add-Member Noteproperty 'IdentitySID' ($_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value)
3617 $_
3618 } | Where-Object {
3619 # check for any ACLs with SIDs > -1000
3620 try {
3621 # TODO: change this to a regex for speedup?
3622 [int]($_.IdentitySid.split("-")[-1]) -ge 1000
3623 }
3624 catch {}
3625 } | Where-Object {
3626 # filter for modifiable rights
3627 ($_.ActiveDirectoryRights -eq "GenericAll") -or ($_.ActiveDirectoryRights -match "Write") -or ($_.ActiveDirectoryRights -match "Create") -or ($_.ActiveDirectoryRights -match "Delete") -or (($_.ActiveDirectoryRights -match "ExtendedRight") -and ($_.AccessControlType -eq "Allow"))
3628 }
3629}
3630
3631
3632filter Get-GUIDMap {
3633<#
3634 .SYNOPSIS
3635
3636 Helper to build a hash table of [GUID] -> resolved names
3637
3638 Heavily adapted from http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou-permissions-report-free-powershell-script-download.aspx
3639
3640 .PARAMETER Domain
3641
3642 The domain to use for the query, defaults to the current domain.
3643
3644 .PARAMETER DomainController
3645
3646 Domain controller to reflect LDAP queries through.
3647
3648 .PARAMETER PageSize
3649
3650 The PageSize to set for the LDAP searcher object.
3651
3652 .LINK
3653
3654 http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou-permissions-report-free-powershell-script-download.aspx
3655#>
3656
3657 [CmdletBinding()]
3658 Param (
3659 [Parameter(ValueFromPipeline=$True)]
3660 [String]
3661 $Domain,
3662
3663 [String]
3664 $DomainController,
3665
3666 [ValidateRange(1,10000)]
3667 [Int]
3668 $PageSize = 200
3669 )
3670
3671 $GUIDs = @{'00000000-0000-0000-0000-000000000000' = 'All'}
3672
3673 $SchemaPath = (Get-NetForest).schema.name
3674
3675 $SchemaSearcher = Get-DomainSearcher -ADSpath $SchemaPath -DomainController $DomainController -PageSize $PageSize
3676 if($SchemaSearcher) {
3677 $SchemaSearcher.filter = "(schemaIDGUID=*)"
3678 try {
3679 $Results = $SchemaSearcher.FindAll()
3680 $Results | Where-Object {$_} | ForEach-Object {
3681 # convert the GUID
3682 $GUIDs[(New-Object Guid (,$_.properties.schemaidguid[0])).Guid] = $_.properties.name[0]
3683 }
3684 $Results.dispose()
3685 $SchemaSearcher.dispose()
3686 }
3687 catch {
3688 Write-Verbose "Error in building GUID map: $_"
3689 }
3690 }
3691
3692 $RightsSearcher = Get-DomainSearcher -ADSpath $SchemaPath.replace("Schema","Extended-Rights") -DomainController $DomainController -PageSize $PageSize -Credential $Credential
3693 if ($RightsSearcher) {
3694 $RightsSearcher.filter = "(objectClass=controlAccessRight)"
3695 try {
3696 $Results = $RightsSearcher.FindAll()
3697 $Results | Where-Object {$_} | ForEach-Object {
3698 # convert the GUID
3699 $GUIDs[$_.properties.rightsguid[0].toString()] = $_.properties.name[0]
3700 }
3701 $Results.dispose()
3702 $RightsSearcher.dispose()
3703 }
3704 catch {
3705 Write-Verbose "Error in building GUID map: $_"
3706 }
3707 }
3708
3709 $GUIDs
3710}
3711
3712
3713function Get-NetComputer {
3714<#
3715 .SYNOPSIS
3716
3717 This function utilizes adsisearcher to query the current AD context
3718 for current computer objects. Based off of Carlos Perez's Audit.psm1
3719 script in Posh-SecMod (link below).
3720
3721 .PARAMETER ComputerName
3722
3723 Return computers with a specific name, wildcards accepted.
3724
3725 .PARAMETER SPN
3726
3727 Return computers with a specific service principal name, wildcards accepted.
3728
3729 .PARAMETER OperatingSystem
3730
3731 Return computers with a specific operating system, wildcards accepted.
3732
3733 .PARAMETER ServicePack
3734
3735 Return computers with a specific service pack, wildcards accepted.
3736
3737 .PARAMETER Filter
3738
3739 A customized ldap filter string to use, e.g. "(description=*admin*)"
3740
3741 .PARAMETER Printers
3742
3743 Switch. Return only printers.
3744
3745 .PARAMETER Ping
3746
3747 Switch. Ping each host to ensure it's up before enumerating.
3748
3749 .PARAMETER FullData
3750
3751 Switch. Return full computer objects instead of just system names (the default).
3752
3753 .PARAMETER Domain
3754
3755 The domain to query for computers, defaults to the current domain.
3756
3757 .PARAMETER DomainController
3758
3759 Domain controller to reflect LDAP queries through.
3760
3761 .PARAMETER ADSpath
3762
3763 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
3764 Useful for OU queries.
3765
3766 .PARAMETER SiteName
3767
3768 The AD Site name to search for computers.
3769
3770 .PARAMETER Unconstrained
3771
3772 Switch. Return computer objects that have unconstrained delegation.
3773
3774 .PARAMETER PageSize
3775
3776 The PageSize to set for the LDAP searcher object.
3777
3778 .PARAMETER Credential
3779
3780 A [Management.Automation.PSCredential] object of alternate credentials
3781 for connection to the target domain.
3782
3783 .EXAMPLE
3784
3785 PS C:\> Get-NetComputer
3786
3787 Returns the current computers in current domain.
3788
3789 .EXAMPLE
3790
3791 PS C:\> Get-NetComputer -SPN mssql*
3792
3793 Returns all MS SQL servers on the domain.
3794
3795 .EXAMPLE
3796
3797 PS C:\> Get-NetComputer -Domain testing
3798
3799 Returns the current computers in 'testing' domain.
3800
3801 .EXAMPLE
3802
3803 PS C:\> Get-NetComputer -Domain testing -FullData
3804
3805 Returns full computer objects in the 'testing' domain.
3806
3807 .LINK
3808
3809 https://github.com/darkoperator/Posh-SecMod/blob/master/Audit/Audit.psm1
3810#>
3811
3812 [CmdletBinding()]
3813 Param (
3814 [Parameter(ValueFromPipeline=$True)]
3815 [Alias('HostName')]
3816 [String]
3817 $ComputerName = '*',
3818
3819 [String]
3820 $SPN,
3821
3822 [String]
3823 $OperatingSystem,
3824
3825 [String]
3826 $ServicePack,
3827
3828 [String]
3829 $Filter,
3830
3831 [Switch]
3832 $Printers,
3833
3834 [Switch]
3835 $Ping,
3836
3837 [Switch]
3838 $FullData,
3839
3840 [String]
3841 $Domain,
3842
3843 [String]
3844 $DomainController,
3845
3846 [String]
3847 $ADSpath,
3848
3849 [String]
3850 $SiteName,
3851
3852 [Switch]
3853 $Unconstrained,
3854
3855 [ValidateRange(1,10000)]
3856 [Int]
3857 $PageSize = 200,
3858
3859 [Management.Automation.PSCredential]
3860 $Credential
3861 )
3862
3863 begin {
3864 # so this isn't repeated if multiple computer names are passed on the pipeline
3865 $CompSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize -Credential $Credential
3866 }
3867
3868 process {
3869
3870 if ($CompSearcher) {
3871
3872 # if we're checking for unconstrained delegation
3873 if($Unconstrained) {
3874 Write-Verbose "Searching for computers with for unconstrained delegation"
3875 $Filter += "(userAccountControl:1.2.840.113556.1.4.803:=524288)"
3876 }
3877 # set the filters for the seracher if it exists
3878 if($Printers) {
3879 Write-Verbose "Searching for printers"
3880 # $CompSearcher.filter="(&(objectCategory=printQueue)$Filter)"
3881 $Filter += "(objectCategory=printQueue)"
3882 }
3883 if($SPN) {
3884 Write-Verbose "Searching for computers with SPN: $SPN"
3885 $Filter += "(servicePrincipalName=$SPN)"
3886 }
3887 if($OperatingSystem) {
3888 $Filter += "(operatingsystem=$OperatingSystem)"
3889 }
3890 if($ServicePack) {
3891 $Filter += "(operatingsystemservicepack=$ServicePack)"
3892 }
3893 if($SiteName) {
3894 $Filter += "(serverreferencebl=$SiteName)"
3895 }
3896
3897 $CompFilter = "(&(sAMAccountType=805306369)(dnshostname=$ComputerName)$Filter)"
3898 Write-Verbose "Get-NetComputer filter : '$CompFilter'"
3899 $CompSearcher.filter = $CompFilter
3900
3901 try {
3902 $Results = $CompSearcher.FindAll()
3903 $Results | Where-Object {$_} | ForEach-Object {
3904 $Up = $True
3905 if($Ping) {
3906 # TODO: how can these results be piped to ping for a speedup?
3907 $Up = Test-Connection -Count 1 -Quiet -ComputerName $_.properties.dnshostname
3908 }
3909 if($Up) {
3910 # return full data objects
3911 if ($FullData) {
3912 # convert/process the LDAP fields for each result
3913 $Computer = Convert-LDAPProperty -Properties $_.Properties
3914 $Computer.PSObject.TypeNames.Add('PowerView.Computer')
3915 $Computer
3916 }
3917 else {
3918 # otherwise we're just returning the DNS host name
3919 $_.properties.dnshostname
3920 }
3921 }
3922 }
3923 $Results.dispose()
3924 $CompSearcher.dispose()
3925 }
3926 catch {
3927 Write-Warning "Error: $_"
3928 }
3929 }
3930 }
3931}
3932
3933
3934function Get-ADObject {
3935<#
3936 .SYNOPSIS
3937
3938 Takes a domain SID and returns the user, group, or computer object
3939 associated with it.
3940
3941 .PARAMETER SID
3942
3943 The SID of the domain object you're querying for.
3944
3945 .PARAMETER Name
3946
3947 The Name of the domain object you're querying for.
3948
3949 .PARAMETER SamAccountName
3950
3951 The SamAccountName of the domain object you're querying for.
3952
3953 .PARAMETER Domain
3954
3955 The domain to query for objects, defaults to the current domain.
3956
3957 .PARAMETER DomainController
3958
3959 Domain controller to reflect LDAP queries through.
3960
3961 .PARAMETER ADSpath
3962
3963 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
3964 Useful for OU queries.
3965
3966 .PARAMETER Filter
3967
3968 Additional LDAP filter string for the query.
3969
3970 .PARAMETER ReturnRaw
3971
3972 Switch. Return the raw object instead of translating its properties.
3973 Used by Set-ADObject to modify object properties.
3974
3975 .PARAMETER PageSize
3976
3977 The PageSize to set for the LDAP searcher object.
3978
3979 .PARAMETER Credential
3980
3981 A [Management.Automation.PSCredential] object of alternate credentials
3982 for connection to the target domain.
3983
3984 .EXAMPLE
3985
3986 PS C:\> Get-ADObject -SID "S-1-5-21-2620891829-2411261497-1773853088-1110"
3987
3988 Get the domain object associated with the specified SID.
3989
3990 .EXAMPLE
3991
3992 PS C:\> Get-ADObject -ADSpath "CN=AdminSDHolder,CN=System,DC=testlab,DC=local"
3993
3994 Get the AdminSDHolder object for the testlab.local domain.
3995#>
3996
3997 [CmdletBinding()]
3998 Param (
3999 [Parameter(ValueFromPipeline=$True)]
4000 [String]
4001 $SID,
4002
4003 [String]
4004 $Name,
4005
4006 [String]
4007 $SamAccountName,
4008
4009 [String]
4010 $Domain,
4011
4012 [String]
4013 $DomainController,
4014
4015 [String]
4016 $ADSpath,
4017
4018 [String]
4019 $Filter,
4020
4021 [Switch]
4022 $ReturnRaw,
4023
4024 [ValidateRange(1,10000)]
4025 [Int]
4026 $PageSize = 200,
4027
4028 [Management.Automation.PSCredential]
4029 $Credential
4030 )
4031 process {
4032 if($SID) {
4033 # if a SID is passed, try to resolve it to a reachable domain name for the searcher
4034 try {
4035 $Name = Convert-SidToName $SID
4036 if($Name) {
4037 $Canonical = Convert-ADName -ObjectName $Name -InputType NT4 -OutputType Canonical
4038 if($Canonical) {
4039 $Domain = $Canonical.split("/")[0]
4040 }
4041 else {
4042 Write-Warning "Error resolving SID '$SID'"
4043 return $Null
4044 }
4045 }
4046 }
4047 catch {
4048 Write-Warning "Error resolving SID '$SID' : $_"
4049 return $Null
4050 }
4051 }
4052
4053 $ObjectSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
4054
4055 if($ObjectSearcher) {
4056 if($SID) {
4057 $ObjectSearcher.filter = "(&(objectsid=$SID)$Filter)"
4058 }
4059 elseif($Name) {
4060 $ObjectSearcher.filter = "(&(name=$Name)$Filter)"
4061 }
4062 elseif($SamAccountName) {
4063 $ObjectSearcher.filter = "(&(samAccountName=$SamAccountName)$Filter)"
4064 }
4065
4066 $Results = $ObjectSearcher.FindAll()
4067 $Results | Where-Object {$_} | ForEach-Object {
4068 if($ReturnRaw) {
4069 $_
4070 }
4071 else {
4072 # convert/process the LDAP fields for each result
4073 Convert-LDAPProperty -Properties $_.Properties
4074 }
4075 }
4076 $Results.dispose()
4077 $ObjectSearcher.dispose()
4078 }
4079 }
4080}
4081
4082
4083function Set-ADObject {
4084<#
4085 .SYNOPSIS
4086
4087 Takes a SID, name, or SamAccountName to query for a specified
4088 domain object, and then sets a specified 'PropertyName' to a
4089 specified 'PropertyValue'.
4090
4091 .PARAMETER SID
4092
4093 The SID of the domain object you're querying for.
4094
4095 .PARAMETER Name
4096
4097 The Name of the domain object you're querying for.
4098
4099 .PARAMETER SamAccountName
4100
4101 The SamAccountName of the domain object you're querying for.
4102
4103 .PARAMETER Domain
4104
4105 The domain to query for objects, defaults to the current domain.
4106
4107 .PARAMETER DomainController
4108
4109 Domain controller to reflect LDAP queries through.
4110
4111 .PARAMETER Filter
4112
4113 Additional LDAP filter string for the query.
4114
4115 .PARAMETER PropertyName
4116
4117 The property name to set.
4118
4119 .PARAMETER PropertyValue
4120
4121 The value to set for PropertyName
4122
4123 .PARAMETER PropertyXorValue
4124
4125 Integer value to binary xor (-bxor) with the current int value.
4126
4127 .PARAMETER ClearValue
4128
4129 Switch. Clear the value of PropertyName
4130
4131 .PARAMETER PageSize
4132
4133 The PageSize to set for the LDAP searcher object.
4134
4135 .PARAMETER Credential
4136
4137 A [Management.Automation.PSCredential] object of alternate credentials
4138 for connection to the target domain.
4139
4140 .EXAMPLE
4141
4142 PS C:\> Set-ADObject -SamAccountName matt.admin -PropertyName countrycode -PropertyValue 0
4143
4144 Set the countrycode for matt.admin to 0
4145
4146 .EXAMPLE
4147
4148 PS C:\> Set-ADObject -SamAccountName matt.admin -PropertyName useraccountcontrol -PropertyXorValue 65536
4149
4150 Set the password not to expire on matt.admin
4151#>
4152
4153 [CmdletBinding()]
4154 Param (
4155 [String]
4156 $SID,
4157
4158 [String]
4159 $Name,
4160
4161 [String]
4162 $SamAccountName,
4163
4164 [String]
4165 $Domain,
4166
4167 [String]
4168 $DomainController,
4169
4170 [String]
4171 $Filter,
4172
4173 [Parameter(Mandatory = $True)]
4174 [String]
4175 $PropertyName,
4176
4177 $PropertyValue,
4178
4179 [Int]
4180 $PropertyXorValue,
4181
4182 [Switch]
4183 $ClearValue,
4184
4185 [ValidateRange(1,10000)]
4186 [Int]
4187 $PageSize = 200,
4188
4189 [Management.Automation.PSCredential]
4190 $Credential
4191 )
4192
4193 $Arguments = @{
4194 'SID' = $SID
4195 'Name' = $Name
4196 'SamAccountName' = $SamAccountName
4197 'Domain' = $Domain
4198 'DomainController' = $DomainController
4199 'Filter' = $Filter
4200 'PageSize' = $PageSize
4201 'Credential' = $Credential
4202 }
4203 # splat the appropriate arguments to Get-ADObject
4204 $RawObject = Get-ADObject -ReturnRaw @Arguments
4205
4206 try {
4207 # get the modifiable object for this search result
4208 $Entry = $RawObject.GetDirectoryEntry()
4209
4210 if($ClearValue) {
4211 Write-Verbose "Clearing value"
4212 $Entry.$PropertyName.clear()
4213 $Entry.commitchanges()
4214 }
4215
4216 elseif($PropertyXorValue) {
4217 $TypeName = $Entry.$PropertyName[0].GetType().name
4218
4219 # UAC value references- https://support.microsoft.com/en-us/kb/305144
4220 $PropertyValue = $($Entry.$PropertyName) -bxor $PropertyXorValue
4221 $Entry.$PropertyName = $PropertyValue -as $TypeName
4222 $Entry.commitchanges()
4223 }
4224
4225 else {
4226 $Entry.put($PropertyName, $PropertyValue)
4227 $Entry.setinfo()
4228 }
4229 }
4230 catch {
4231 Write-Warning "Error setting property $PropertyName to value '$PropertyValue' for object $($RawObject.Properties.samaccountname) : $_"
4232 }
4233}
4234
4235
4236function Invoke-DowngradeAccount {
4237<#
4238 .SYNOPSIS
4239
4240 Set reversible encryption on a given account and then force the password
4241 to be set on next user login. To repair use "-Repair".
4242
4243 .PARAMETER SamAccountName
4244
4245 The SamAccountName of the domain object you're querying for.
4246
4247 .PARAMETER Name
4248
4249 The Name of the domain object you're querying for.
4250
4251 .PARAMETER Domain
4252
4253 The domain to query for objects, defaults to the current domain.
4254
4255 .PARAMETER DomainController
4256
4257 Domain controller to reflect LDAP queries through.
4258
4259 .PARAMETER Filter
4260
4261 Additional LDAP filter string for the query.
4262
4263 .PARAMETER Repair
4264
4265 Switch. Unset the reversible encryption flag and force password reset flag.
4266
4267 .PARAMETER Credential
4268
4269 A [Management.Automation.PSCredential] object of alternate credentials
4270 for connection to the target domain.
4271
4272 .EXAMPLE
4273
4274 PS> Invoke-DowngradeAccount -SamAccountName jason
4275
4276 Set reversible encryption on the 'jason' account and force the password to be changed.
4277
4278 .EXAMPLE
4279
4280 PS> Invoke-DowngradeAccount -SamAccountName jason -Repair
4281
4282 Unset reversible encryption on the 'jason' account and remove the forced password change.
4283#>
4284
4285 [CmdletBinding()]
4286 Param (
4287 [Parameter(ParameterSetName = 'SamAccountName', Position=0, ValueFromPipeline=$True)]
4288 [String]
4289 $SamAccountName,
4290
4291 [Parameter(ParameterSetName = 'Name')]
4292 [String]
4293 $Name,
4294
4295 [String]
4296 $Domain,
4297
4298 [String]
4299 $DomainController,
4300
4301 [String]
4302 $Filter,
4303
4304 [Switch]
4305 $Repair,
4306
4307 [Management.Automation.PSCredential]
4308 $Credential
4309 )
4310
4311 process {
4312 $Arguments = @{
4313 'SamAccountName' = $SamAccountName
4314 'Name' = $Name
4315 'Domain' = $Domain
4316 'DomainController' = $DomainController
4317 'Filter' = $Filter
4318 'Credential' = $Credential
4319 }
4320
4321 # splat the appropriate arguments to Get-ADObject
4322 $UACValues = Get-ADObject @Arguments | select useraccountcontrol | ConvertFrom-UACValue
4323
4324 if($Repair) {
4325
4326 if($UACValues.Keys -contains "ENCRYPTED_TEXT_PWD_ALLOWED") {
4327 # if reversible encryption is set, unset it
4328 Set-ADObject @Arguments -PropertyName useraccountcontrol -PropertyXorValue 128
4329 }
4330
4331 # unset the forced password change
4332 Set-ADObject @Arguments -PropertyName pwdlastset -PropertyValue -1
4333 }
4334
4335 else {
4336
4337 if($UACValues.Keys -contains "DONT_EXPIRE_PASSWORD") {
4338 # if the password is set to never expire, unset
4339 Set-ADObject @Arguments -PropertyName useraccountcontrol -PropertyXorValue 65536
4340 }
4341
4342 if($UACValues.Keys -notcontains "ENCRYPTED_TEXT_PWD_ALLOWED") {
4343 # if reversible encryption is not set, set it
4344 Set-ADObject @Arguments -PropertyName useraccountcontrol -PropertyXorValue 128
4345 }
4346
4347 # force the password to be changed on next login
4348 Set-ADObject @Arguments -PropertyName pwdlastset -PropertyValue 0
4349 }
4350 }
4351}
4352
4353
4354function Get-ComputerProperty {
4355<#
4356 .SYNOPSIS
4357
4358 Returns a list of all computer object properties. If a property
4359 name is specified, it returns all [computer:property] values.
4360
4361 Taken directly from @obscuresec's post:
4362 http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
4363
4364 .PARAMETER Properties
4365
4366 Return property names for computers.
4367
4368 .PARAMETER Domain
4369
4370 The domain to query for computer properties, defaults to the current domain.
4371
4372 .PARAMETER DomainController
4373
4374 Domain controller to reflect LDAP queries through.
4375
4376 .PARAMETER PageSize
4377
4378 The PageSize to set for the LDAP searcher object.
4379
4380 .PARAMETER Credential
4381
4382 A [Management.Automation.PSCredential] object of alternate credentials
4383 for connection to the target domain.
4384
4385 .EXAMPLE
4386
4387 PS C:\> Get-ComputerProperty -Domain testing
4388
4389 Returns all user properties for computers in the 'testing' domain.
4390
4391 .EXAMPLE
4392
4393 PS C:\> Get-ComputerProperty -Properties ssn,lastlogon,location
4394
4395 Returns all an array of computer/ssn/lastlogin/location combinations
4396 for computers in the current domain.
4397
4398 .LINK
4399
4400 http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
4401#>
4402
4403 [CmdletBinding()]
4404 param(
4405 [String[]]
4406 $Properties,
4407
4408 [String]
4409 $Domain,
4410
4411 [String]
4412 $DomainController,
4413
4414 [ValidateRange(1,10000)]
4415 [Int]
4416 $PageSize = 200,
4417
4418 [Management.Automation.PSCredential]
4419 $Credential
4420 )
4421
4422 if($Properties) {
4423 # extract out the set of all properties for each object
4424 $Properties = ,"name" + $Properties | Sort-Object -Unique
4425 Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -PageSize $PageSize | Select-Object -Property $Properties
4426 }
4427 else {
4428 # extract out just the property names
4429 Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -PageSize $PageSize | Select-Object -first 1 | Get-Member -MemberType *Property | Select-Object -Property "Name"
4430 }
4431}
4432
4433
4434function Find-ComputerField {
4435<#
4436 .SYNOPSIS
4437
4438 Searches computer object fields for a given word (default *pass*). Default
4439 field being searched is 'description'.
4440
4441 Taken directly from @obscuresec's post:
4442 http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
4443
4444 .PARAMETER SearchTerm
4445
4446 Term to search for, default of "pass".
4447
4448 .PARAMETER SearchField
4449
4450 User field to search in, default of "description".
4451
4452 .PARAMETER ADSpath
4453
4454 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
4455 Useful for OU queries.
4456
4457 .PARAMETER Domain
4458
4459 Domain to search computer fields for, defaults to the current domain.
4460
4461 .PARAMETER DomainController
4462
4463 Domain controller to reflect LDAP queries through.
4464
4465 .PARAMETER PageSize
4466
4467 The PageSize to set for the LDAP searcher object.
4468
4469 .PARAMETER Credential
4470
4471 A [Management.Automation.PSCredential] object of alternate credentials
4472 for connection to the target domain.
4473
4474 .EXAMPLE
4475
4476 PS C:\> Find-ComputerField -SearchTerm backup -SearchField info
4477
4478 Find computer accounts with "backup" in the "info" field.
4479#>
4480
4481 [CmdletBinding()]
4482 param(
4483 [Parameter(Position=0,ValueFromPipeline=$True)]
4484 [Alias('Term')]
4485 [String]
4486 $SearchTerm = 'pass',
4487
4488 [Alias('Field')]
4489 [String]
4490 $SearchField = 'description',
4491
4492 [String]
4493 $ADSpath,
4494
4495 [String]
4496 $Domain,
4497
4498 [String]
4499 $DomainController,
4500
4501 [ValidateRange(1,10000)]
4502 [Int]
4503 $PageSize = 200,
4504
4505 [Management.Automation.PSCredential]
4506 $Credential
4507 )
4508
4509 process {
4510 Get-NetComputer -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Credential $Credential -FullData -Filter "($SearchField=*$SearchTerm*)" -PageSize $PageSize | Select-Object samaccountname,$SearchField
4511 }
4512}
4513
4514
4515function Get-NetOU {
4516<#
4517 .SYNOPSIS
4518
4519 Gets a list of all current OUs in a domain.
4520
4521 .PARAMETER OUName
4522
4523 The OU name to query for, wildcards accepted.
4524
4525 .PARAMETER GUID
4526
4527 Only return OUs with the specified GUID in their gplink property.
4528
4529 .PARAMETER Domain
4530
4531 The domain to query for OUs, defaults to the current domain.
4532
4533 .PARAMETER DomainController
4534
4535 Domain controller to reflect LDAP queries through.
4536
4537 .PARAMETER ADSpath
4538
4539 The LDAP source to search through.
4540
4541 .PARAMETER FullData
4542
4543 Switch. Return full OU objects instead of just object names (the default).
4544
4545 .PARAMETER PageSize
4546
4547 The PageSize to set for the LDAP searcher object.
4548
4549 .PARAMETER Credential
4550
4551 A [Management.Automation.PSCredential] object of alternate credentials
4552 for connection to the target domain.
4553
4554 .EXAMPLE
4555
4556 PS C:\> Get-NetOU
4557
4558 Returns the current OUs in the domain.
4559
4560 .EXAMPLE
4561
4562 PS C:\> Get-NetOU -OUName *admin* -Domain testlab.local
4563
4564 Returns all OUs with "admin" in their name in the testlab.local domain.
4565
4566 .EXAMPLE
4567
4568 PS C:\> Get-NetOU -GUID 123-...
4569
4570 Returns all OUs with linked to the specified group policy object.
4571
4572 .EXAMPLE
4573
4574 PS C:\> "*admin*","*server*" | Get-NetOU
4575
4576 Get the full OU names for the given search terms piped on the pipeline.
4577#>
4578
4579 [CmdletBinding()]
4580 Param (
4581 [Parameter(ValueFromPipeline=$True)]
4582 [String]
4583 $OUName = '*',
4584
4585 [String]
4586 $GUID,
4587
4588 [String]
4589 $Domain,
4590
4591 [String]
4592 $DomainController,
4593
4594 [String]
4595 $ADSpath,
4596
4597 [Switch]
4598 $FullData,
4599
4600 [ValidateRange(1,10000)]
4601 [Int]
4602 $PageSize = 200,
4603
4604 [Management.Automation.PSCredential]
4605 $Credential
4606 )
4607
4608 begin {
4609 $OUSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
4610 }
4611 process {
4612 if ($OUSearcher) {
4613 if ($GUID) {
4614 # if we're filtering for a GUID in .gplink
4615 $OUSearcher.filter="(&(objectCategory=organizationalUnit)(name=$OUName)(gplink=*$GUID*))"
4616 }
4617 else {
4618 $OUSearcher.filter="(&(objectCategory=organizationalUnit)(name=$OUName))"
4619 }
4620
4621 try {
4622 $Results = $OUSearcher.FindAll()
4623 $Results | Where-Object {$_} | ForEach-Object {
4624 if ($FullData) {
4625 # convert/process the LDAP fields for each result
4626 $OU = Convert-LDAPProperty -Properties $_.Properties
4627 $OU.PSObject.TypeNames.Add('PowerView.OU')
4628 $OU
4629 }
4630 else {
4631 # otherwise just returning the ADS paths of the OUs
4632 $_.properties.adspath
4633 }
4634 }
4635 $Results.dispose()
4636 $OUSearcher.dispose()
4637 }
4638 catch {
4639 Write-Warning $_
4640 }
4641 }
4642 }
4643}
4644
4645
4646function Get-NetSite {
4647<#
4648 .SYNOPSIS
4649
4650 Gets a list of all current sites in a domain.
4651
4652 .PARAMETER SiteName
4653
4654 Site filter string, wildcards accepted.
4655
4656 .PARAMETER Domain
4657
4658 The domain to query for sites, defaults to the current domain.
4659
4660 .PARAMETER DomainController
4661
4662 Domain controller to reflect LDAP queries through.
4663
4664 .PARAMETER ADSpath
4665
4666 The LDAP source to search through.
4667
4668 .PARAMETER GUID
4669
4670 Only return site with the specified GUID in their gplink property.
4671
4672 .PARAMETER FullData
4673
4674 Switch. Return full site objects instead of just object names (the default).
4675
4676 .PARAMETER PageSize
4677
4678 The PageSize to set for the LDAP searcher object.
4679
4680 .PARAMETER Credential
4681
4682 A [Management.Automation.PSCredential] object of alternate credentials
4683 for connection to the target domain.
4684
4685 .EXAMPLE
4686
4687 PS C:\> Get-NetSite -Domain testlab.local -FullData
4688
4689 Returns the full data objects for all sites in testlab.local
4690#>
4691
4692 [CmdletBinding()]
4693 Param (
4694 [Parameter(ValueFromPipeline=$True)]
4695 [String]
4696 $SiteName = "*",
4697
4698 [String]
4699 $Domain,
4700
4701 [String]
4702 $DomainController,
4703
4704 [String]
4705 $ADSpath,
4706
4707 [String]
4708 $GUID,
4709
4710 [Switch]
4711 $FullData,
4712
4713 [ValidateRange(1,10000)]
4714 [Int]
4715 $PageSize = 200,
4716
4717 [Management.Automation.PSCredential]
4718 $Credential
4719 )
4720
4721 begin {
4722 $SiteSearcher = Get-DomainSearcher -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSprefix "CN=Sites,CN=Configuration" -PageSize $PageSize
4723 }
4724 process {
4725 if($SiteSearcher) {
4726
4727 if ($GUID) {
4728 # if we're filtering for a GUID in .gplink
4729 $SiteSearcher.filter="(&(objectCategory=site)(name=$SiteName)(gplink=*$GUID*))"
4730 }
4731 else {
4732 $SiteSearcher.filter="(&(objectCategory=site)(name=$SiteName))"
4733 }
4734
4735 try {
4736 $Results = $SiteSearcher.FindAll()
4737 $Results | Where-Object {$_} | ForEach-Object {
4738 if ($FullData) {
4739 # convert/process the LDAP fields for each result
4740 $Site = Convert-LDAPProperty -Properties $_.Properties
4741 $Site.PSObject.TypeNames.Add('PowerView.Site')
4742 $Site
4743 }
4744 else {
4745 # otherwise just return the site name
4746 $_.properties.name
4747 }
4748 }
4749 $Results.dispose()
4750 $SiteSearcher.dispose()
4751 }
4752 catch {
4753 Write-Verbose $_
4754 }
4755 }
4756 }
4757}
4758
4759
4760function Get-NetSubnet {
4761<#
4762 .SYNOPSIS
4763
4764 Gets a list of all current subnets in a domain.
4765
4766 .PARAMETER SiteName
4767
4768 Only return subnets from the specified SiteName.
4769
4770 .PARAMETER Domain
4771
4772 The domain to query for subnets, defaults to the current domain.
4773
4774 .PARAMETER DomainController
4775
4776 Domain controller to reflect LDAP queries through.
4777
4778 .PARAMETER ADSpath
4779
4780 The LDAP source to search through.
4781
4782 .PARAMETER FullData
4783
4784 Switch. Return full subnet objects instead of just object names (the default).
4785
4786 .PARAMETER PageSize
4787
4788 The PageSize to set for the LDAP searcher object.
4789
4790 .PARAMETER Credential
4791
4792 A [Management.Automation.PSCredential] object of alternate credentials
4793 for connection to the target domain.
4794
4795 .EXAMPLE
4796
4797 PS C:\> Get-NetSubnet
4798
4799 Returns all subnet names in the current domain.
4800
4801 .EXAMPLE
4802
4803 PS C:\> Get-NetSubnet -Domain testlab.local -FullData
4804
4805 Returns the full data objects for all subnets in testlab.local
4806#>
4807
4808 [CmdletBinding()]
4809 Param (
4810 [Parameter(ValueFromPipeline=$True)]
4811 [String]
4812 $SiteName = "*",
4813
4814 [String]
4815 $Domain,
4816
4817 [String]
4818 $ADSpath,
4819
4820 [String]
4821 $DomainController,
4822
4823 [Switch]
4824 $FullData,
4825
4826 [ValidateRange(1,10000)]
4827 [Int]
4828 $PageSize = 200,
4829
4830 [Management.Automation.PSCredential]
4831 $Credential
4832 )
4833
4834 begin {
4835 $SubnetSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -ADSprefix "CN=Subnets,CN=Sites,CN=Configuration" -PageSize $PageSize
4836 }
4837
4838 process {
4839 if($SubnetSearcher) {
4840
4841 $SubnetSearcher.filter="(&(objectCategory=subnet))"
4842
4843 try {
4844 $Results = $SubnetSearcher.FindAll()
4845 $Results | Where-Object {$_} | ForEach-Object {
4846 if ($FullData) {
4847 # convert/process the LDAP fields for each result
4848 Convert-LDAPProperty -Properties $_.Properties | Where-Object { $_.siteobject -match "CN=$SiteName" }
4849 }
4850 else {
4851 # otherwise just return the subnet name and site name
4852 if ( ($SiteName -and ($_.properties.siteobject -match "CN=$SiteName,")) -or ($SiteName -eq '*')) {
4853
4854 $SubnetProperties = @{
4855 'Subnet' = $_.properties.name[0]
4856 }
4857 try {
4858 $SubnetProperties['Site'] = ($_.properties.siteobject[0]).split(",")[0]
4859 }
4860 catch {
4861 $SubnetProperties['Site'] = 'Error'
4862 }
4863
4864 New-Object -TypeName PSObject -Property $SubnetProperties
4865 }
4866 }
4867 }
4868 $Results.dispose()
4869 $SubnetSearcher.dispose()
4870 }
4871 catch {
4872 Write-Warning $_
4873 }
4874 }
4875 }
4876}
4877
4878
4879function Get-DomainSID {
4880<#
4881 .SYNOPSIS
4882
4883 Gets the SID for the domain.
4884
4885 .PARAMETER Domain
4886
4887 The domain to query, defaults to the current domain.
4888
4889 .PARAMETER DomainController
4890
4891 Domain controller to reflect LDAP queries through.
4892
4893 .EXAMPLE
4894
4895 C:\> Get-DomainSID -Domain TEST
4896
4897 Returns SID for the domain 'TEST'
4898#>
4899
4900 param(
4901 [String]
4902 $Domain,
4903
4904 [String]
4905 $DomainController
4906 )
4907
4908 $DCSID = Get-NetComputer -Domain $Domain -DomainController $DomainController -FullData -Filter '(userAccountControl:1.2.840.113556.1.4.803:=8192)' | Select-Object -First 1 -ExpandProperty objectsid
4909 if($DCSID) {
4910 $DCSID.Substring(0, $DCSID.LastIndexOf('-'))
4911 }
4912 else {
4913 Write-Verbose "Error extracting domain SID for $Domain"
4914 }
4915}
4916
4917
4918function Get-NetGroup {
4919<#
4920 .SYNOPSIS
4921
4922 Gets a list of all current groups in a domain, or all
4923 the groups a given user/group object belongs to.
4924
4925 .PARAMETER GroupName
4926
4927 The group name to query for, wildcards accepted.
4928
4929 .PARAMETER SID
4930
4931 The group SID to query for.
4932
4933 .PARAMETER UserName
4934
4935 The user name (or group name) to query for all effective
4936 groups of.
4937
4938 .PARAMETER Filter
4939
4940 A customized ldap filter string to use, e.g. "(description=*admin*)"
4941
4942 .PARAMETER Domain
4943
4944 The domain to query for groups, defaults to the current domain.
4945
4946 .PARAMETER DomainController
4947
4948 Domain controller to reflect LDAP queries through.
4949
4950 .PARAMETER ADSpath
4951
4952 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
4953 Useful for OU queries.
4954
4955 .PARAMETER AdminCount
4956
4957 Switch. Return group with adminCount=1.
4958
4959 .PARAMETER FullData
4960
4961 Switch. Return full group objects instead of just object names (the default).
4962
4963 .PARAMETER RawSids
4964
4965 Switch. Return raw SIDs when using "Get-NetGroup -UserName X"
4966
4967 .PARAMETER PageSize
4968
4969 The PageSize to set for the LDAP searcher object.
4970
4971 .PARAMETER Credential
4972
4973 A [Management.Automation.PSCredential] object of alternate credentials
4974 for connection to the target domain.
4975
4976 .PARAMETER AllTypes
4977
4978 By default we will retrieve only Security, not Distribution Groups.
4979
4980 .EXAMPLE
4981
4982 PS C:\> Get-NetGroup
4983
4984 Returns the current security groups in the domain.
4985
4986 .EXAMPLE
4987
4988 PS C:\> Get-NetGroup -GroupName *admin*
4989
4990 Returns all groups with "admin" in their group name.
4991
4992 .EXAMPLE
4993
4994 PS C:\> Get-NetGroup -Domain testing -FullData
4995
4996 Returns full group data objects in the 'testing' domain
4997#>
4998
4999 [CmdletBinding()]
5000 param(
5001 [Parameter(ValueFromPipeline=$True)]
5002 [String]
5003 $GroupName = '*',
5004
5005 [String]
5006 $SID,
5007
5008 [String]
5009 $UserName,
5010
5011 [String]
5012 $Filter,
5013
5014 [String]
5015 $Domain,
5016
5017 [String]
5018 $DomainController,
5019
5020 [String]
5021 $ADSpath,
5022
5023 [Switch]
5024 $AdminCount,
5025
5026 [Switch]
5027 $FullData,
5028
5029 [Switch]
5030 $RawSids,
5031
5032 [Switch]
5033 $AllTypes,
5034
5035 [ValidateRange(1,10000)]
5036 [Int]
5037 $PageSize = 200,
5038
5039 [Management.Automation.PSCredential]
5040 $Credential
5041 )
5042
5043 begin {
5044 $GroupSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
5045 if (!$AllTypes)
5046 {
5047 $Filter += "(groupType:1.2.840.113556.1.4.803:=2147483648)"
5048 }
5049 }
5050
5051 process {
5052 if($GroupSearcher) {
5053
5054 if($AdminCount) {
5055 Write-Verbose "Checking for adminCount=1"
5056 $Filter += "(admincount=1)"
5057 }
5058
5059 if ($UserName) {
5060 # get the raw user object
5061 $User = Get-ADObject -SamAccountName $UserName -Domain $Domain -DomainController $DomainController -Credential $Credential -ReturnRaw -PageSize $PageSize | Select-Object -First 1
5062
5063 if($User) {
5064 # convert the user to a directory entry
5065 $UserDirectoryEntry = $User.GetDirectoryEntry()
5066
5067 # cause the cache to calculate the token groups for the user
5068 $UserDirectoryEntry.RefreshCache("tokenGroups")
5069
5070 $UserDirectoryEntry.TokenGroups | ForEach-Object {
5071 # convert the token group sid
5072 $GroupSid = (New-Object System.Security.Principal.SecurityIdentifier($_,0)).Value
5073
5074 # ignore the built in groups
5075 if($GroupSid -notmatch '^S-1-5-32-.*') {
5076 if($FullData) {
5077 $Group = Get-ADObject -SID $GroupSid -PageSize $PageSize -Domain $Domain -DomainController $DomainController -Credential $Credential
5078 $Group.PSObject.TypeNames.Add('PowerView.Group')
5079 $Group
5080 }
5081 else {
5082 if($RawSids) {
5083 $GroupSid
5084 }
5085 else {
5086 Convert-SidToName -SID $GroupSid
5087 }
5088 }
5089 }
5090 }
5091 }
5092 else {
5093 Write-Warning "UserName '$UserName' failed to resolve."
5094 }
5095 }
5096 else {
5097 if ($SID) {
5098 $GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)"
5099 }
5100 else {
5101 $GroupSearcher.filter = "(&(objectCategory=group)(samaccountname=$GroupName)$Filter)"
5102 }
5103
5104 $Results = $GroupSearcher.FindAll()
5105 $Results | Where-Object {$_} | ForEach-Object {
5106 # if we're returning full data objects
5107 if ($FullData) {
5108 # convert/process the LDAP fields for each result
5109 $Group = Convert-LDAPProperty -Properties $_.Properties
5110 $Group.PSObject.TypeNames.Add('PowerView.Group')
5111 $Group
5112 }
5113 else {
5114 # otherwise we're just returning the group name
5115 $_.properties.samaccountname
5116 }
5117 }
5118 $Results.dispose()
5119 $GroupSearcher.dispose()
5120 }
5121 }
5122 }
5123}
5124
5125
5126function Get-NetGroupMember {
5127<#
5128 .SYNOPSIS
5129
5130 This function users [ADSI] and LDAP to query the current AD context
5131 or trusted domain for users in a specified group. If no GroupName is
5132 specified, it defaults to querying the "Domain Admins" group.
5133 This is a replacement for "net group 'name' /domain"
5134
5135 .PARAMETER GroupName
5136
5137 The group name to query for users.
5138
5139 .PARAMETER SID
5140
5141 The Group SID to query for users. If not given, it defaults to 512 "Domain Admins"
5142
5143 .PARAMETER Filter
5144
5145 A customized ldap filter string to use, e.g. "(description=*admin*)"
5146
5147 .PARAMETER Domain
5148
5149 The domain to query for group users, defaults to the current domain.
5150
5151 .PARAMETER DomainController
5152
5153 Domain controller to reflect LDAP queries through.
5154
5155 .PARAMETER ADSpath
5156
5157 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
5158 Useful for OU queries.
5159
5160 .PARAMETER FullData
5161
5162 Switch. Returns full data objects instead of just group/users.
5163
5164 .PARAMETER Recurse
5165
5166 Switch. If the group member is a group, recursively try to query its members as well.
5167
5168 .PARAMETER UseMatchingRule
5169
5170 Switch. Use LDAP_MATCHING_RULE_IN_CHAIN in the LDAP search query when -Recurse is specified.
5171 Much faster than manual recursion, but doesn't reveal cross-domain groups.
5172
5173 .PARAMETER PageSize
5174
5175 The PageSize to set for the LDAP searcher object.
5176
5177 .PARAMETER Credential
5178
5179 A [Management.Automation.PSCredential] object of alternate credentials
5180 for connection to the target domain.
5181
5182 .EXAMPLE
5183
5184 PS C:\> Get-NetGroupMember
5185
5186 Returns the usernames that of members of the "Domain Admins" domain group.
5187
5188 .EXAMPLE
5189
5190 PS C:\> Get-NetGroupMember -Domain testing -GroupName "Power Users"
5191
5192 Returns the usernames that of members of the "Power Users" group in the 'testing' domain.
5193
5194 .LINK
5195
5196 http://www.powershellmagazine.com/2013/05/23/pstip-retrieve-group-membership-of-an-active-directory-group-recursively/
5197#>
5198
5199 [CmdletBinding()]
5200 param(
5201 [Parameter(ValueFromPipeline=$True)]
5202 [String]
5203 $GroupName,
5204
5205 [String]
5206 $SID,
5207
5208 [String]
5209 $Domain,
5210
5211 [String]
5212 $DomainController,
5213
5214 [String]
5215 $ADSpath,
5216
5217 [Switch]
5218 $FullData,
5219
5220 [Switch]
5221 $Recurse,
5222
5223 [Switch]
5224 $UseMatchingRule,
5225
5226 [ValidateRange(1,10000)]
5227 [Int]
5228 $PageSize = 200,
5229
5230 [Management.Automation.PSCredential]
5231 $Credential
5232 )
5233
5234 begin {
5235 if($DomainController) {
5236 $TargetDomainController = $DomainController
5237 }
5238 else {
5239 $TargetDomainController = ((Get-NetDomain -Credential $Credential).PdcRoleOwner).Name
5240 }
5241
5242 if($Domain) {
5243 $TargetDomain = $Domain
5244 }
5245 else {
5246 $TargetDomain = Get-NetDomain -Credential $Credential | Select-Object -ExpandProperty name
5247 }
5248
5249 # so this isn't repeated if users are passed on the pipeline
5250 $GroupSearcher = Get-DomainSearcher -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
5251 }
5252
5253 process {
5254 if ($GroupSearcher) {
5255 if ($Recurse -and $UseMatchingRule) {
5256 # resolve the group to a distinguishedname
5257 if ($GroupName) {
5258 $Group = Get-NetGroup -AllTypes -GroupName $GroupName -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -FullData -PageSize $PageSize
5259 }
5260 elseif ($SID) {
5261 $Group = Get-NetGroup -AllTypes -SID $SID -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -FullData -PageSize $PageSize
5262 }
5263 else {
5264 # default to domain admins
5265 $SID = (Get-DomainSID -Domain $TargetDomain -DomainController $TargetDomainController) + "-512"
5266 $Group = Get-NetGroup -AllTypes -SID $SID -Domain $TargetDomain -DomainController $TargetDomainController -Credential $Credential -FullData -PageSize $PageSize
5267 }
5268 $GroupDN = $Group.distinguishedname
5269 $GroupFoundName = $Group.samaccountname
5270
5271 if ($GroupDN) {
5272 $GroupSearcher.filter = "(&(samAccountType=805306368)(memberof:1.2.840.113556.1.4.1941:=$GroupDN)$Filter)"
5273 $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'))
5274
5275 $Members = $GroupSearcher.FindAll()
5276 $GroupFoundName = $GroupName
5277 }
5278 else {
5279 Write-Error "Unable to find Group"
5280 }
5281 }
5282 else {
5283 if ($GroupName) {
5284 $GroupSearcher.filter = "(&(objectCategory=group)(samaccountname=$GroupName)$Filter)"
5285 }
5286 elseif ($SID) {
5287 $GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)"
5288 }
5289 else {
5290 # default to domain admins
5291 $SID = (Get-DomainSID -Domain $TargetDomain -DomainController $TargetDomainController) + "-512"
5292 $GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)"
5293 }
5294
5295 try {
5296 $Result = $GroupSearcher.FindOne()
5297 }
5298 catch {
5299 $Members = @()
5300 }
5301
5302 $GroupFoundName = ''
5303
5304 if ($Result) {
5305 $Members = $Result.properties.item("member")
5306
5307 if($Members.count -eq 0) {
5308
5309 $Finished = $False
5310 $Bottom = 0
5311 $Top = 0
5312
5313 while(!$Finished) {
5314 $Top = $Bottom + 1499
5315 $MemberRange="member;range=$Bottom-$Top"
5316 $Bottom += 1500
5317
5318 $GroupSearcher.PropertiesToLoad.Clear()
5319 [void]$GroupSearcher.PropertiesToLoad.Add("$MemberRange")
5320 [void]$GroupSearcher.PropertiesToLoad.Add("samaccountname")
5321 try {
5322 $Result = $GroupSearcher.FindOne()
5323 $RangedProperty = $Result.Properties.PropertyNames -like "member;range=*"
5324 $Members += $Result.Properties.item($RangedProperty)
5325 $GroupFoundName = $Result.properties.item("samaccountname")[0]
5326
5327 if ($Members.count -eq 0) {
5328 $Finished = $True
5329 }
5330 }
5331 catch [System.Management.Automation.MethodInvocationException] {
5332 $Finished = $True
5333 }
5334 }
5335 }
5336 else {
5337 $GroupFoundName = $Result.properties.item("samaccountname")[0]
5338 $Members += $Result.Properties.item($RangedProperty)
5339 }
5340 }
5341 $GroupSearcher.dispose()
5342 }
5343
5344 $Members | Where-Object {$_} | ForEach-Object {
5345 # if we're doing the LDAP_MATCHING_RULE_IN_CHAIN recursion
5346 if ($Recurse -and $UseMatchingRule) {
5347 $Properties = $_.Properties
5348 }
5349 else {
5350 if($TargetDomainController) {
5351 $Result = [adsi]"LDAP://$TargetDomainController/$_"
5352 }
5353 else {
5354 $Result = [adsi]"LDAP://$_"
5355 }
5356 if($Result){
5357 $Properties = $Result.Properties
5358 }
5359 }
5360
5361 if($Properties) {
5362
5363 $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Properties.samaccounttype
5364
5365 if ($FullData) {
5366 $GroupMember = Convert-LDAPProperty -Properties $Properties
5367 }
5368 else {
5369 $GroupMember = New-Object PSObject
5370 }
5371
5372 $GroupMember | Add-Member Noteproperty 'GroupDomain' $TargetDomain
5373 $GroupMember | Add-Member Noteproperty 'GroupName' $GroupFoundName
5374
5375 if($Properties.objectSid) {
5376 $MemberSID = ((New-Object System.Security.Principal.SecurityIdentifier $Properties.objectSid[0],0).Value)
5377 }
5378 else {
5379 $MemberSID = $Null
5380 }
5381
5382 try {
5383 $MemberDN = $Properties.distinguishedname[0]
5384
5385 if (($MemberDN -match 'ForeignSecurityPrincipals') -and ($MemberDN -match 'S-1-5-21')) {
5386 try {
5387 if(-not $MemberSID) {
5388 $MemberSID = $Properties.cn[0]
5389 }
5390 $MemberSimpleName = Convert-SidToName -SID $MemberSID | Convert-ADName -InputType 'NT4' -OutputType 'Simple'
5391 if($MemberSimpleName) {
5392 $MemberDomain = $MemberSimpleName.Split('@')[1]
5393 }
5394 else {
5395 Write-Warning "Error converting $MemberDN"
5396 $MemberDomain = $Null
5397 }
5398 }
5399 catch {
5400 Write-Warning "Error converting $MemberDN"
5401 $MemberDomain = $Null
5402 }
5403 }
5404 else {
5405 # extract the FQDN from the Distinguished Name
5406 $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
5407 }
5408 }
5409 catch {
5410 $MemberDN = $Null
5411 $MemberDomain = $Null
5412 }
5413
5414 if ($Properties.samaccountname) {
5415 # forest users have the samAccountName set
5416 $MemberName = $Properties.samaccountname[0]
5417 }
5418 else {
5419 # external trust users have a SID, so convert it
5420 try {
5421 $MemberName = Convert-SidToName $Properties.cn[0]
5422 }
5423 catch {
5424 # if there's a problem contacting the domain to resolve the SID
5425 $MemberName = $Properties.cn
5426 }
5427 }
5428
5429 $GroupMember | Add-Member Noteproperty 'MemberDomain' $MemberDomain
5430 $GroupMember | Add-Member Noteproperty 'MemberName' $MemberName
5431 $GroupMember | Add-Member Noteproperty 'MemberSID' $MemberSID
5432 $GroupMember | Add-Member Noteproperty 'IsGroup' $IsGroup
5433 $GroupMember | Add-Member Noteproperty 'MemberDN' $MemberDN
5434 $GroupMember.PSObject.TypeNames.Add('PowerView.GroupMember')
5435 $GroupMember
5436
5437 # if we're doing manual recursion
5438 if ($Recurse -and !$UseMatchingRule -and $IsGroup -and $MemberName) {
5439 if($FullData) {
5440 Get-NetGroupMember -FullData -Domain $MemberDomain -DomainController $TargetDomainController -Credential $Credential -GroupName $MemberName -Recurse -PageSize $PageSize
5441 }
5442 else {
5443 Get-NetGroupMember -Domain $MemberDomain -DomainController $TargetDomainController -Credential $Credential -GroupName $MemberName -Recurse -PageSize $PageSize
5444 }
5445 }
5446 }
5447 }
5448 }
5449 }
5450}
5451
5452
5453function Get-NetFileServer {
5454<#
5455 .SYNOPSIS
5456
5457 Returns a list of all file servers extracted from user
5458 homedirectory, scriptpath, and profilepath fields.
5459
5460 .PARAMETER Domain
5461
5462 The domain to query for user file servers, defaults to the current domain.
5463
5464 .PARAMETER DomainController
5465
5466 Domain controller to reflect LDAP queries through.
5467
5468 .PARAMETER TargetUsers
5469
5470 An array of users to query for file servers.
5471
5472 .PARAMETER PageSize
5473
5474 The PageSize to set for the LDAP searcher object.
5475
5476 .PARAMETER Credential
5477
5478 A [Management.Automation.PSCredential] object of alternate credentials
5479 for connection to the target domain.
5480
5481 .EXAMPLE
5482
5483 PS C:\> Get-NetFileServer
5484
5485 Returns active file servers.
5486
5487 .EXAMPLE
5488
5489 PS C:\> Get-NetFileServer -Domain testing
5490
5491 Returns active file servers for the 'testing' domain.
5492#>
5493
5494 [CmdletBinding()]
5495 param(
5496 [String]
5497 $Domain,
5498
5499 [String]
5500 $DomainController,
5501
5502 [String[]]
5503 $TargetUsers,
5504
5505 [ValidateRange(1,10000)]
5506 [Int]
5507 $PageSize = 200,
5508
5509 [Management.Automation.PSCredential]
5510 $Credential
5511 )
5512
5513 function SplitPath {
5514 # short internal helper to split UNC server paths
5515 param([String]$Path)
5516
5517 if ($Path -and ($Path.split("\\").Count -ge 3)) {
5518 $Temp = $Path.split("\\")[2]
5519 if($Temp -and ($Temp -ne '')) {
5520 $Temp
5521 }
5522 }
5523 }
5524 $filter = "(!(userAccountControl:1.2.840.113556.1.4.803:=2))(|(scriptpath=*)(homedirectory=*)(profilepath=*))"
5525 Get-NetUser -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize -Filter $filter | Where-Object {$_} | Where-Object {
5526 # filter for any target users
5527 if($TargetUsers) {
5528 $TargetUsers -Match $_.samAccountName
5529 }
5530 else { $True }
5531 } | ForEach-Object {
5532 # split out every potential file server path
5533 if($_.homedirectory) {
5534 SplitPath($_.homedirectory)
5535 }
5536 if($_.scriptpath) {
5537 SplitPath($_.scriptpath)
5538 }
5539 if($_.profilepath) {
5540 SplitPath($_.profilepath)
5541 }
5542
5543 } | Where-Object {$_} | Sort-Object -Unique
5544}
5545
5546
5547function Get-DFSshare {
5548<#
5549 .SYNOPSIS
5550
5551 Returns a list of all fault-tolerant distributed file
5552 systems for a given domain.
5553
5554 .PARAMETER Version
5555
5556 The version of DFS to query for servers.
5557 1/v1, 2/v2, or all
5558
5559 .PARAMETER Domain
5560
5561 The domain to query for user DFS shares, defaults to the current domain.
5562
5563 .PARAMETER DomainController
5564
5565 Domain controller to reflect LDAP queries through.
5566
5567 .PARAMETER ADSpath
5568
5569 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
5570 Useful for OU queries.
5571
5572 .PARAMETER PageSize
5573
5574 The PageSize to set for the LDAP searcher object.
5575
5576 .PARAMETER Credential
5577
5578 A [Management.Automation.PSCredential] object of alternate credentials
5579 for connection to the target domain.
5580
5581 .EXAMPLE
5582
5583 PS C:\> Get-DFSshare
5584
5585 Returns all distributed file system shares for the current domain.
5586
5587 .EXAMPLE
5588
5589 PS C:\> Get-DFSshare -Domain test
5590
5591 Returns all distributed file system shares for the 'test' domain.
5592#>
5593
5594 [CmdletBinding()]
5595 param(
5596 [String]
5597 [ValidateSet("All","V1","1","V2","2")]
5598 $Version = "All",
5599
5600 [String]
5601 $Domain,
5602
5603 [String]
5604 $DomainController,
5605
5606 [String]
5607 $ADSpath,
5608
5609 [ValidateRange(1,10000)]
5610 [Int]
5611 $PageSize = 200,
5612
5613 [Management.Automation.PSCredential]
5614 $Credential
5615 )
5616
5617 function Parse-Pkt {
5618 [CmdletBinding()]
5619 param(
5620 [byte[]]
5621 $Pkt
5622 )
5623
5624 $bin = $Pkt
5625 $blob_version = [bitconverter]::ToUInt32($bin[0..3],0)
5626 $blob_element_count = [bitconverter]::ToUInt32($bin[4..7],0)
5627 $offset = 8
5628 #https://msdn.microsoft.com/en-us/library/cc227147.aspx
5629 $object_list = @()
5630 for($i=1; $i -le $blob_element_count; $i++){
5631 $blob_name_size_start = $offset
5632 $blob_name_size_end = $offset + 1
5633 $blob_name_size = [bitconverter]::ToUInt16($bin[$blob_name_size_start..$blob_name_size_end],0)
5634
5635 $blob_name_start = $blob_name_size_end + 1
5636 $blob_name_end = $blob_name_start + $blob_name_size - 1
5637 $blob_name = [System.Text.Encoding]::Unicode.GetString($bin[$blob_name_start..$blob_name_end])
5638
5639 $blob_data_size_start = $blob_name_end + 1
5640 $blob_data_size_end = $blob_data_size_start + 3
5641 $blob_data_size = [bitconverter]::ToUInt32($bin[$blob_data_size_start..$blob_data_size_end],0)
5642
5643 $blob_data_start = $blob_data_size_end + 1
5644 $blob_data_end = $blob_data_start + $blob_data_size - 1
5645 $blob_data = $bin[$blob_data_start..$blob_data_end]
5646 switch -wildcard ($blob_name) {
5647 "\siteroot" { }
5648 "\domainroot*" {
5649 # Parse DFSNamespaceRootOrLinkBlob object. Starts with variable length DFSRootOrLinkIDBlob which we parse first...
5650 # DFSRootOrLinkIDBlob
5651 $root_or_link_guid_start = 0
5652 $root_or_link_guid_end = 15
5653 $root_or_link_guid = [byte[]]$blob_data[$root_or_link_guid_start..$root_or_link_guid_end]
5654 $guid = New-Object Guid(,$root_or_link_guid) # should match $guid_str
5655 $prefix_size_start = $root_or_link_guid_end + 1
5656 $prefix_size_end = $prefix_size_start + 1
5657 $prefix_size = [bitconverter]::ToUInt16($blob_data[$prefix_size_start..$prefix_size_end],0)
5658 $prefix_start = $prefix_size_end + 1
5659 $prefix_end = $prefix_start + $prefix_size - 1
5660 $prefix = [System.Text.Encoding]::Unicode.GetString($blob_data[$prefix_start..$prefix_end])
5661
5662 $short_prefix_size_start = $prefix_end + 1
5663 $short_prefix_size_end = $short_prefix_size_start + 1
5664 $short_prefix_size = [bitconverter]::ToUInt16($blob_data[$short_prefix_size_start..$short_prefix_size_end],0)
5665 $short_prefix_start = $short_prefix_size_end + 1
5666 $short_prefix_end = $short_prefix_start + $short_prefix_size - 1
5667 $short_prefix = [System.Text.Encoding]::Unicode.GetString($blob_data[$short_prefix_start..$short_prefix_end])
5668
5669 $type_start = $short_prefix_end + 1
5670 $type_end = $type_start + 3
5671 $type = [bitconverter]::ToUInt32($blob_data[$type_start..$type_end],0)
5672
5673 $state_start = $type_end + 1
5674 $state_end = $state_start + 3
5675 $state = [bitconverter]::ToUInt32($blob_data[$state_start..$state_end],0)
5676
5677 $comment_size_start = $state_end + 1
5678 $comment_size_end = $comment_size_start + 1
5679 $comment_size = [bitconverter]::ToUInt16($blob_data[$comment_size_start..$comment_size_end],0)
5680 $comment_start = $comment_size_end + 1
5681 $comment_end = $comment_start + $comment_size - 1
5682 if ($comment_size -gt 0) {
5683 $comment = [System.Text.Encoding]::Unicode.GetString($blob_data[$comment_start..$comment_end])
5684 }
5685 $prefix_timestamp_start = $comment_end + 1
5686 $prefix_timestamp_end = $prefix_timestamp_start + 7
5687 # https://msdn.microsoft.com/en-us/library/cc230324.aspx FILETIME
5688 $prefix_timestamp = $blob_data[$prefix_timestamp_start..$prefix_timestamp_end] #dword lowDateTime #dword highdatetime
5689 $state_timestamp_start = $prefix_timestamp_end + 1
5690 $state_timestamp_end = $state_timestamp_start + 7
5691 $state_timestamp = $blob_data[$state_timestamp_start..$state_timestamp_end]
5692 $comment_timestamp_start = $state_timestamp_end + 1
5693 $comment_timestamp_end = $comment_timestamp_start + 7
5694 $comment_timestamp = $blob_data[$comment_timestamp_start..$comment_timestamp_end]
5695 $version_start = $comment_timestamp_end + 1
5696 $version_end = $version_start + 3
5697 $version = [bitconverter]::ToUInt32($blob_data[$version_start..$version_end],0)
5698
5699 # Parse rest of DFSNamespaceRootOrLinkBlob here
5700 $dfs_targetlist_blob_size_start = $version_end + 1
5701 $dfs_targetlist_blob_size_end = $dfs_targetlist_blob_size_start + 3
5702 $dfs_targetlist_blob_size = [bitconverter]::ToUInt32($blob_data[$dfs_targetlist_blob_size_start..$dfs_targetlist_blob_size_end],0)
5703
5704 $dfs_targetlist_blob_start = $dfs_targetlist_blob_size_end + 1
5705 $dfs_targetlist_blob_end = $dfs_targetlist_blob_start + $dfs_targetlist_blob_size - 1
5706 $dfs_targetlist_blob = $blob_data[$dfs_targetlist_blob_start..$dfs_targetlist_blob_end]
5707 $reserved_blob_size_start = $dfs_targetlist_blob_end + 1
5708 $reserved_blob_size_end = $reserved_blob_size_start + 3
5709 $reserved_blob_size = [bitconverter]::ToUInt32($blob_data[$reserved_blob_size_start..$reserved_blob_size_end],0)
5710
5711 $reserved_blob_start = $reserved_blob_size_end + 1
5712 $reserved_blob_end = $reserved_blob_start + $reserved_blob_size - 1
5713 $reserved_blob = $blob_data[$reserved_blob_start..$reserved_blob_end]
5714 $referral_ttl_start = $reserved_blob_end + 1
5715 $referral_ttl_end = $referral_ttl_start + 3
5716 $referral_ttl = [bitconverter]::ToUInt32($blob_data[$referral_ttl_start..$referral_ttl_end],0)
5717
5718 #Parse DFSTargetListBlob
5719 $target_count_start = 0
5720 $target_count_end = $target_count_start + 3
5721 $target_count = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_count_start..$target_count_end],0)
5722 $t_offset = $target_count_end + 1
5723
5724 for($j=1; $j -le $target_count; $j++){
5725 $target_entry_size_start = $t_offset
5726 $target_entry_size_end = $target_entry_size_start + 3
5727 $target_entry_size = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_entry_size_start..$target_entry_size_end],0)
5728 $target_time_stamp_start = $target_entry_size_end + 1
5729 $target_time_stamp_end = $target_time_stamp_start + 7
5730 # FILETIME again or special if priority rank and priority class 0
5731 $target_time_stamp = $dfs_targetlist_blob[$target_time_stamp_start..$target_time_stamp_end]
5732 $target_state_start = $target_time_stamp_end + 1
5733 $target_state_end = $target_state_start + 3
5734 $target_state = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_state_start..$target_state_end],0)
5735
5736 $target_type_start = $target_state_end + 1
5737 $target_type_end = $target_type_start + 3
5738 $target_type = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_type_start..$target_type_end],0)
5739
5740 $server_name_size_start = $target_type_end + 1
5741 $server_name_size_end = $server_name_size_start + 1
5742 $server_name_size = [bitconverter]::ToUInt16($dfs_targetlist_blob[$server_name_size_start..$server_name_size_end],0)
5743
5744 $server_name_start = $server_name_size_end + 1
5745 $server_name_end = $server_name_start + $server_name_size - 1
5746 $server_name = [System.Text.Encoding]::Unicode.GetString($dfs_targetlist_blob[$server_name_start..$server_name_end])
5747
5748 $share_name_size_start = $server_name_end + 1
5749 $share_name_size_end = $share_name_size_start + 1
5750 $share_name_size = [bitconverter]::ToUInt16($dfs_targetlist_blob[$share_name_size_start..$share_name_size_end],0)
5751 $share_name_start = $share_name_size_end + 1
5752 $share_name_end = $share_name_start + $share_name_size - 1
5753 $share_name = [System.Text.Encoding]::Unicode.GetString($dfs_targetlist_blob[$share_name_start..$share_name_end])
5754
5755 $target_list += "\\$server_name\$share_name"
5756 $t_offset = $share_name_end + 1
5757 }
5758 }
5759 }
5760 $offset = $blob_data_end + 1
5761 $dfs_pkt_properties = @{
5762 'Name' = $blob_name
5763 'Prefix' = $prefix
5764 'TargetList' = $target_list
5765 }
5766 $object_list += New-Object -TypeName PSObject -Property $dfs_pkt_properties
5767 $prefix = $null
5768 $blob_name = $null
5769 $target_list = $null
5770 }
5771
5772 $servers = @()
5773 $object_list | ForEach-Object {
5774 if ($_.TargetList) {
5775 $_.TargetList | ForEach-Object {
5776 $servers += $_.split("\")[2]
5777 }
5778 }
5779 }
5780
5781 $servers
5782 }
5783
5784 function Get-DFSshareV1 {
5785 [CmdletBinding()]
5786 param(
5787 [String]
5788 $Domain,
5789
5790 [String]
5791 $DomainController,
5792
5793 [String]
5794 $ADSpath,
5795
5796 [ValidateRange(1,10000)]
5797 [Int]
5798 $PageSize = 200,
5799
5800 [Management.Automation.PSCredential]
5801 $Credential
5802 )
5803
5804 $DFSsearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
5805
5806 if($DFSsearcher) {
5807 $DFSshares = @()
5808 $DFSsearcher.filter = "(&(objectClass=fTDfs))"
5809
5810 try {
5811 $Results = $DFSSearcher.FindAll()
5812 $Results | Where-Object {$_} | ForEach-Object {
5813 $Properties = $_.Properties
5814 $RemoteNames = $Properties.remoteservername
5815 $Pkt = $Properties.pkt
5816
5817 $DFSshares += $RemoteNames | ForEach-Object {
5818 try {
5819 if ( $_.Contains('\') ) {
5820 New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'RemoteServerName'=$_.split("\")[2]}
5821 }
5822 }
5823 catch {
5824 Write-Verbose "Error in parsing DFS share : $_"
5825 }
5826 }
5827 }
5828 $Results.dispose()
5829 $DFSSearcher.dispose()
5830
5831 if($pkt -and $pkt[0]) {
5832 Parse-Pkt $pkt[0] | ForEach-Object {
5833 # If a folder doesn't have a redirection it will
5834 # have a target like
5835 # \\null\TestNameSpace\folder\.DFSFolderLink so we
5836 # do actually want to match on "null" rather than
5837 # $null
5838 if ($_ -ne "null") {
5839 New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'RemoteServerName'=$_}
5840 }
5841 }
5842 }
5843 }
5844 catch {
5845 Write-Warning "Get-DFSshareV1 error : $_"
5846 }
5847 $DFSshares | Sort-Object -Property "RemoteServerName"
5848 }
5849 }
5850
5851 function Get-DFSshareV2 {
5852 [CmdletBinding()]
5853 param(
5854 [String]
5855 $Domain,
5856
5857 [String]
5858 $DomainController,
5859
5860 [String]
5861 $ADSpath,
5862
5863 [ValidateRange(1,10000)]
5864 [Int]
5865 $PageSize = 200,
5866
5867 [Management.Automation.PSCredential]
5868 $Credential
5869 )
5870
5871 $DFSsearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
5872
5873 if($DFSsearcher) {
5874 $DFSshares = @()
5875 $DFSsearcher.filter = "(&(objectClass=msDFS-Linkv2))"
5876 $DFSSearcher.PropertiesToLoad.AddRange(('msdfs-linkpathv2','msDFS-TargetListv2'))
5877
5878 try {
5879 $Results = $DFSSearcher.FindAll()
5880 $Results | Where-Object {$_} | ForEach-Object {
5881 $Properties = $_.Properties
5882 $target_list = $Properties.'msdfs-targetlistv2'[0]
5883 $xml = [xml][System.Text.Encoding]::Unicode.GetString($target_list[2..($target_list.Length-1)])
5884 $DFSshares += $xml.targets.ChildNodes | ForEach-Object {
5885 try {
5886 $Target = $_.InnerText
5887 if ( $Target.Contains('\') ) {
5888 $DFSroot = $Target.split("\")[3]
5889 $ShareName = $Properties.'msdfs-linkpathv2'[0]
5890 New-Object -TypeName PSObject -Property @{'Name'="$DFSroot$ShareName";'RemoteServerName'=$Target.split("\")[2]}
5891 }
5892 }
5893 catch {
5894 Write-Verbose "Error in parsing target : $_"
5895 }
5896 }
5897 }
5898 $Results.dispose()
5899 $DFSSearcher.dispose()
5900 }
5901 catch {
5902 Write-Warning "Get-DFSshareV2 error : $_"
5903 }
5904 $DFSshares | Sort-Object -Unique -Property "RemoteServerName"
5905 }
5906 }
5907
5908 $DFSshares = @()
5909
5910 if ( ($Version -eq "all") -or ($Version.endsWith("1")) ) {
5911 $DFSshares += Get-DFSshareV1 -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
5912 }
5913 if ( ($Version -eq "all") -or ($Version.endsWith("2")) ) {
5914 $DFSshares += Get-DFSshareV2 -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
5915 }
5916
5917 $DFSshares | Sort-Object -Property ("RemoteServerName","Name") -Unique
5918}
5919
5920
5921########################################################
5922#
5923# GPO related functions.
5924#
5925########################################################
5926
5927
5928filter Get-GptTmpl {
5929<#
5930 .SYNOPSIS
5931
5932 Helper to parse a GptTmpl.inf policy file path into a custom object.
5933
5934 .PARAMETER GptTmplPath
5935
5936 The GptTmpl.inf file path name to parse.
5937
5938 .PARAMETER UsePSDrive
5939
5940 Switch. Mount the target GptTmpl folder path as a temporary PSDrive.
5941
5942 .EXAMPLE
5943
5944 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"
5945
5946 Parse the default domain policy .inf for dev.testlab.local
5947#>
5948
5949 [CmdletBinding()]
5950 Param (
5951 [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
5952 [String]
5953 $GptTmplPath,
5954
5955 [Switch]
5956 $UsePSDrive
5957 )
5958
5959 if($UsePSDrive) {
5960 # if we're PSDrives, create a temporary mount point
5961 $Parts = $GptTmplPath.split('\')
5962 $FolderPath = $Parts[0..($Parts.length-2)] -join '\'
5963 $FilePath = $Parts[-1]
5964 $RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join ''
5965
5966 Write-Verbose "Mounting path $GptTmplPath using a temp PSDrive at $RandDrive"
5967
5968 try {
5969 $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop
5970 }
5971 catch {
5972 Write-Verbose "Error mounting path $GptTmplPath : $_"
5973 return $Null
5974 }
5975
5976 # so we can cd/dir the new drive
5977 $TargetGptTmplPath = $RandDrive + ":\" + $FilePath
5978 }
5979 else {
5980 $TargetGptTmplPath = $GptTmplPath
5981 }
5982
5983 Write-Verbose "GptTmplPath: $GptTmplPath"
5984
5985 try {
5986 Write-Verbose "Parsing $TargetGptTmplPath"
5987 $TargetGptTmplPath | Get-IniContent -ErrorAction SilentlyContinue
5988 }
5989 catch {
5990 Write-Verbose "Error parsing $TargetGptTmplPath : $_"
5991 }
5992
5993 if($UsePSDrive -and $RandDrive) {
5994 Write-Verbose "Removing temp PSDrive $RandDrive"
5995 Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force
5996 }
5997}
5998
5999
6000filter Get-GroupsXML {
6001<#
6002 .SYNOPSIS
6003
6004 Helper to parse a groups.xml file path into a custom object.
6005
6006 .PARAMETER GroupsXMLpath
6007
6008 The groups.xml file path name to parse.
6009
6010 .PARAMETER UsePSDrive
6011
6012 Switch. Mount the target groups.xml folder path as a temporary PSDrive.
6013#>
6014
6015 [CmdletBinding()]
6016 Param (
6017 [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
6018 [String]
6019 $GroupsXMLPath,
6020
6021 [Switch]
6022 $UsePSDrive
6023 )
6024
6025 if($UsePSDrive) {
6026 # if we're PSDrives, create a temporary mount point
6027 $Parts = $GroupsXMLPath.split('\')
6028 $FolderPath = $Parts[0..($Parts.length-2)] -join '\'
6029 $FilePath = $Parts[-1]
6030 $RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join ''
6031
6032 Write-Verbose "Mounting path $GroupsXMLPath using a temp PSDrive at $RandDrive"
6033
6034 try {
6035 $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop
6036 }
6037 catch {
6038 Write-Verbose "Error mounting path $GroupsXMLPath : $_"
6039 return $Null
6040 }
6041
6042 # so we can cd/dir the new drive
6043 $TargetGroupsXMLPath = $RandDrive + ":\" + $FilePath
6044 }
6045 else {
6046 $TargetGroupsXMLPath = $GroupsXMLPath
6047 }
6048
6049 try {
6050 [XML]$GroupsXMLcontent = Get-Content $TargetGroupsXMLPath -ErrorAction Stop
6051
6052 # process all group properties in the XML
6053 $GroupsXMLcontent | Select-Xml "/Groups/Group" | Select-Object -ExpandProperty node | ForEach-Object {
6054
6055 $Groupname = $_.Properties.groupName
6056
6057 # extract the localgroup sid for memberof
6058 $GroupSID = $_.Properties.groupSid
6059 if(-not $GroupSID) {
6060 if($Groupname -match 'Administrators') {
6061 $GroupSID = 'S-1-5-32-544'
6062 }
6063 elseif($Groupname -match 'Remote Desktop') {
6064 $GroupSID = 'S-1-5-32-555'
6065 }
6066 elseif($Groupname -match 'Guests') {
6067 $GroupSID = 'S-1-5-32-546'
6068 }
6069 else {
6070 $GroupSID = Convert-NameToSid -ObjectName $Groupname | Select-Object -ExpandProperty SID
6071 }
6072 }
6073
6074 # extract out members added to this group
6075 $Members = $_.Properties.members | Select-Object -ExpandProperty Member | Where-Object { $_.action -match 'ADD' } | ForEach-Object {
6076 if($_.sid) { $_.sid }
6077 else { $_.name }
6078 }
6079
6080 if ($Members) {
6081
6082 # extract out any/all filters...I hate you GPP
6083 if($_.filters) {
6084 $Filters = $_.filters.GetEnumerator() | ForEach-Object {
6085 New-Object -TypeName PSObject -Property @{'Type' = $_.LocalName;'Value' = $_.name}
6086 }
6087 }
6088 else {
6089 $Filters = $Null
6090 }
6091
6092 if($Members -isnot [System.Array]) { $Members = @($Members) }
6093
6094 $GPOGroup = New-Object PSObject
6095 $GPOGroup | Add-Member Noteproperty 'GPOPath' $TargetGroupsXMLPath
6096 $GPOGroup | Add-Member Noteproperty 'Filters' $Filters
6097 $GPOGroup | Add-Member Noteproperty 'GroupName' $GroupName
6098 $GPOGroup | Add-Member Noteproperty 'GroupSID' $GroupSID
6099 $GPOGroup | Add-Member Noteproperty 'GroupMemberOf' $Null
6100 $GPOGroup | Add-Member Noteproperty 'GroupMembers' $Members
6101 $GPOGroup
6102 }
6103 }
6104 }
6105 catch {
6106 Write-Verbose "Error parsing $TargetGroupsXMLPath : $_"
6107 }
6108
6109 if($UsePSDrive -and $RandDrive) {
6110 Write-Verbose "Removing temp PSDrive $RandDrive"
6111 Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force
6112 }
6113}
6114
6115
6116function Get-NetGPO {
6117<#
6118 .SYNOPSIS
6119
6120 Gets a list of all current GPOs in a domain.
6121
6122 .PARAMETER GPOname
6123
6124 The GPO name to query for, wildcards accepted.
6125
6126 .PARAMETER DisplayName
6127
6128 The GPO display name to query for, wildcards accepted.
6129
6130 .PARAMETER ComputerName
6131
6132 Return all GPO objects applied to a given computer (FQDN).
6133
6134 .PARAMETER Domain
6135
6136 The domain to query for GPOs, defaults to the current domain.
6137
6138 .PARAMETER DomainController
6139
6140 Domain controller to reflect LDAP queries through.
6141
6142 .PARAMETER ADSpath
6143
6144 The LDAP source to search through
6145 e.g. "LDAP://cn={8FF59D28-15D7-422A-BCB7-2AE45724125A},cn=policies,cn=system,DC=dev,DC=testlab,DC=local"
6146
6147 .PARAMETER PageSize
6148
6149 The PageSize to set for the LDAP searcher object.
6150
6151 .PARAMETER Credential
6152
6153 A [Management.Automation.PSCredential] object of alternate credentials
6154 for connection to the target domain.
6155
6156 .EXAMPLE
6157
6158 PS C:\> Get-NetGPO -Domain testlab.local
6159
6160 Returns the GPOs in the 'testlab.local' domain.
6161#>
6162 [CmdletBinding()]
6163 Param (
6164 [Parameter(ValueFromPipeline=$True)]
6165 [String]
6166 $GPOname = '*',
6167
6168 [String]
6169 $DisplayName,
6170
6171 [String]
6172 $ComputerName,
6173
6174 [String]
6175 $Domain,
6176
6177 [String]
6178 $DomainController,
6179
6180 [String]
6181 $ADSpath,
6182
6183 [ValidateRange(1,10000)]
6184 [Int]
6185 $PageSize = 200,
6186
6187 [Management.Automation.PSCredential]
6188 $Credential
6189 )
6190
6191 begin {
6192 $GPOSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $ADSpath -PageSize $PageSize
6193 }
6194
6195 process {
6196 if ($GPOSearcher) {
6197
6198 if($ComputerName) {
6199 $GPONames = @()
6200 $Computers = Get-NetComputer -ComputerName $ComputerName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize
6201
6202 if(!$Computers) {
6203 throw "Computer $ComputerName in domain '$Domain' not found! Try a fully qualified host name"
6204 }
6205
6206 # get the given computer's OU
6207 $ComputerOUs = @()
6208 ForEach($Computer in $Computers) {
6209 # extract all OUs a computer is a part of
6210 $DN = $Computer.distinguishedname
6211
6212 $ComputerOUs += $DN.split(",") | ForEach-Object {
6213 if($_.startswith("OU=")) {
6214 $DN.substring($DN.indexof($_))
6215 }
6216 }
6217 }
6218
6219 Write-Verbose "ComputerOUs: $ComputerOUs"
6220
6221 # find all the GPOs linked to the computer's OU
6222 ForEach($ComputerOU in $ComputerOUs) {
6223 $GPONames += Get-NetOU -Domain $Domain -DomainController $DomainController -ADSpath $ComputerOU -FullData -PageSize $PageSize | ForEach-Object {
6224 # get any GPO links
6225 write-verbose "blah: $($_.name)"
6226 $_.gplink.split("][") | ForEach-Object {
6227 if ($_.startswith("LDAP")) {
6228 $_.split(";")[0]
6229 }
6230 }
6231 }
6232 }
6233
6234 Write-Verbose "GPONames: $GPONames"
6235
6236 # find any GPOs linked to the site for the given computer
6237 $ComputerSite = (Get-SiteName -ComputerName $ComputerName).SiteName
6238 if($ComputerSite -and ($ComputerSite -notlike 'Error*')) {
6239 $GPONames += Get-NetSite -SiteName $ComputerSite -FullData | ForEach-Object {
6240 if($_.gplink) {
6241 $_.gplink.split("][") | ForEach-Object {
6242 if ($_.startswith("LDAP")) {
6243 $_.split(";")[0]
6244 }
6245 }
6246 }
6247 }
6248 }
6249
6250 $GPONames | Where-Object{$_ -and ($_ -ne '')} | ForEach-Object {
6251
6252 # use the gplink as an ADS path to enumerate all GPOs for the computer
6253 $GPOSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_ -PageSize $PageSize
6254 $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(name=$GPOname))"
6255
6256 try {
6257 $Results = $GPOSearcher.FindAll()
6258 $Results | Where-Object {$_} | ForEach-Object {
6259 $Out = Convert-LDAPProperty -Properties $_.Properties
6260 $Out | Add-Member Noteproperty 'ComputerName' $ComputerName
6261 $Out
6262 }
6263 $Results.dispose()
6264 $GPOSearcher.dispose()
6265 }
6266 catch {
6267 Write-Warning $_
6268 }
6269 }
6270 }
6271
6272 else {
6273 if($DisplayName) {
6274 $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(displayname=$DisplayName))"
6275 }
6276 else {
6277 $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(name=$GPOname))"
6278 }
6279
6280 try {
6281 $Results = $GPOSearcher.FindAll()
6282 $Results | Where-Object {$_} | ForEach-Object {
6283 if($ADSPath -and ($ADSpath -Match '^GC://')) {
6284 $Properties = Convert-LDAPProperty -Properties $_.Properties
6285 try {
6286 $GPODN = $Properties.distinguishedname
6287 $GPODomain = $GPODN.subString($GPODN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
6288 $gpcfilesyspath = "\\$GPODomain\SysVol\$GPODomain\Policies\$($Properties.cn)"
6289 $Properties | Add-Member Noteproperty 'gpcfilesyspath' $gpcfilesyspath
6290 $Properties
6291 }
6292 catch {
6293 $Properties
6294 }
6295 }
6296 else {
6297 # convert/process the LDAP fields for each result
6298 Convert-LDAPProperty -Properties $_.Properties
6299 }
6300 }
6301 $Results.dispose()
6302 $GPOSearcher.dispose()
6303 }
6304 catch {
6305 Write-Warning $_
6306 }
6307 }
6308 }
6309 }
6310}
6311
6312
6313function New-GPOImmediateTask {
6314<#
6315 .SYNOPSIS
6316
6317 Builds an 'Immediate' schtask to push out through a specified GPO.
6318
6319 .PARAMETER TaskName
6320
6321 Name for the schtask to recreate. Required.
6322
6323 .PARAMETER Command
6324
6325 The command to execute with the task, defaults to 'powershell'
6326
6327 .PARAMETER CommandArguments
6328
6329 The arguments to supply to the -Command being launched.
6330
6331 .PARAMETER TaskDescription
6332
6333 An optional description for the task.
6334
6335 .PARAMETER TaskAuthor
6336
6337 The displayed author of the task, defaults to ''NT AUTHORITY\System'
6338
6339 .PARAMETER TaskModifiedDate
6340
6341 The displayed modified date for the task, defaults to 30 days ago.
6342
6343 .PARAMETER GPOname
6344
6345 The GPO name to build the task for.
6346
6347 .PARAMETER GPODisplayName
6348
6349 The GPO display name to build the task for.
6350
6351 .PARAMETER Domain
6352
6353 The domain to query for the GPOs, defaults to the current domain.
6354
6355 .PARAMETER DomainController
6356
6357 Domain controller to reflect LDAP queries through.
6358
6359 .PARAMETER ADSpath
6360
6361 The LDAP source to search through
6362 e.g. "LDAP://cn={8FF59D28-15D7-422A-BCB7-2AE45724125A},cn=policies,cn=system,DC=dev,DC=testlab,DC=local"
6363
6364 .PARAMETER Credential
6365
6366 A [Management.Automation.PSCredential] object of alternate credentials
6367 for connection to the target.
6368
6369 .EXAMPLE
6370
6371 PS> New-GPOImmediateTask -TaskName Debugging -GPODisplayName SecurePolicy -CommandArguments '-c "123 | Out-File C:\Temp\debug.txt"' -Force
6372
6373 Create an immediate schtask that executes the specified PowerShell arguments and
6374 push it out to the 'SecurePolicy' GPO, skipping the confirmation prompt.
6375
6376 .EXAMPLE
6377
6378 PS> New-GPOImmediateTask -GPODisplayName SecurePolicy -Remove -Force
6379
6380 Remove all schtasks from the 'SecurePolicy' GPO, skipping the confirmation prompt.
6381#>
6382 [CmdletBinding(DefaultParameterSetName = 'Create')]
6383 Param (
6384 [Parameter(ParameterSetName = 'Create', Mandatory = $True)]
6385 [String]
6386 [ValidateNotNullOrEmpty()]
6387 $TaskName,
6388
6389 [Parameter(ParameterSetName = 'Create')]
6390 [String]
6391 [ValidateNotNullOrEmpty()]
6392 $Command = 'powershell',
6393
6394 [Parameter(ParameterSetName = 'Create')]
6395 [String]
6396 [ValidateNotNullOrEmpty()]
6397 $CommandArguments,
6398
6399 [Parameter(ParameterSetName = 'Create')]
6400 [String]
6401 [ValidateNotNullOrEmpty()]
6402 $TaskDescription = '',
6403
6404 [Parameter(ParameterSetName = 'Create')]
6405 [String]
6406 [ValidateNotNullOrEmpty()]
6407 $TaskAuthor = 'NT AUTHORITY\System',
6408
6409 [Parameter(ParameterSetName = 'Create')]
6410 [String]
6411 [ValidateNotNullOrEmpty()]
6412 $TaskModifiedDate = (Get-Date (Get-Date).AddDays(-30) -Format u).trim("Z"),
6413
6414 [Parameter(ParameterSetName = 'Create')]
6415 [Parameter(ParameterSetName = 'Remove')]
6416 [String]
6417 $GPOname,
6418
6419 [Parameter(ParameterSetName = 'Create')]
6420 [Parameter(ParameterSetName = 'Remove')]
6421 [String]
6422 $GPODisplayName,
6423
6424 [Parameter(ParameterSetName = 'Create')]
6425 [Parameter(ParameterSetName = 'Remove')]
6426 [String]
6427 $Domain,
6428
6429 [Parameter(ParameterSetName = 'Create')]
6430 [Parameter(ParameterSetName = 'Remove')]
6431 [String]
6432 $DomainController,
6433
6434 [Parameter(ParameterSetName = 'Create')]
6435 [Parameter(ParameterSetName = 'Remove')]
6436 [String]
6437 $ADSpath,
6438
6439 [Parameter(ParameterSetName = 'Create')]
6440 [Parameter(ParameterSetName = 'Remove')]
6441 [Switch]
6442 $Force,
6443
6444 [Parameter(ParameterSetName = 'Remove')]
6445 [Switch]
6446 $Remove,
6447
6448 [Parameter(ParameterSetName = 'Create')]
6449 [Parameter(ParameterSetName = 'Remove')]
6450 [Management.Automation.PSCredential]
6451 $Credential
6452 )
6453
6454 # build the XML spec for our 'immediate' scheduled task
6455 $TaskXML = '<?xml version="1.0" encoding="utf-8"?><ScheduledTasks clsid="{CC63F200-7309-4ba0-B154-A71CD118DBCC}"><ImmediateTaskV2 clsid="{9756B581-76EC-4169-9AFC-0CA8D43ADB5F}" name="'+$TaskName+'" image="0" changed="'+$TaskModifiedDate+'" uid="{'+$([guid]::NewGuid())+'}" userContext="0" removePolicy="0"><Properties action="C" name="'+$TaskName+'" runAs="NT AUTHORITY\System" logonType="S4U"><Task version="1.3"><RegistrationInfo><Author>'+$TaskAuthor+'</Author><Description>'+$TaskDescription+'</Description></RegistrationInfo><Principals><Principal id="Author"><UserId>NT AUTHORITY\System</UserId><RunLevel>HighestAvailable</RunLevel><LogonType>S4U</LogonType></Principal></Principals><Settings><IdleSettings><Duration>PT10M</Duration><WaitTimeout>PT1H</WaitTimeout><StopOnIdleEnd>true</StopOnIdleEnd><RestartOnIdle>false</RestartOnIdle></IdleSettings><MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy><DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries><StopIfGoingOnBatteries>true</StopIfGoingOnBatteries><AllowHardTerminate>false</AllowHardTerminate><StartWhenAvailable>true</StartWhenAvailable><AllowStartOnDemand>false</AllowStartOnDemand><Enabled>true</Enabled><Hidden>true</Hidden><ExecutionTimeLimit>PT0S</ExecutionTimeLimit><Priority>7</Priority><DeleteExpiredTaskAfter>PT0S</DeleteExpiredTaskAfter><RestartOnFailure><Interval>PT15M</Interval><Count>3</Count></RestartOnFailure></Settings><Actions Context="Author"><Exec><Command>'+$Command+'</Command><Arguments>'+$CommandArguments+'</Arguments></Exec></Actions><Triggers><TimeTrigger><StartBoundary>%LocalTimeXmlEx%</StartBoundary><EndBoundary>%LocalTimeXmlEx%</EndBoundary><Enabled>true</Enabled></TimeTrigger></Triggers></Task></Properties></ImmediateTaskV2></ScheduledTasks>'
6456
6457 if (!$PSBoundParameters['GPOname'] -and !$PSBoundParameters['GPODisplayName']) {
6458 Write-Warning 'Either -GPOName or -GPODisplayName must be specified'
6459 return
6460 }
6461
6462 # eunmerate the specified GPO(s)
6463 $GPOs = Get-NetGPO -GPOname $GPOname -DisplayName $GPODisplayName -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -Credential $Credential
6464
6465 if(!$GPOs) {
6466 Write-Warning 'No GPO found.'
6467 return
6468 }
6469
6470 $GPOs | ForEach-Object {
6471 $ProcessedGPOName = $_.Name
6472 try {
6473 Write-Verbose "Trying to weaponize GPO: $ProcessedGPOName"
6474
6475 # map a network drive as New-PSDrive/New-Item/etc. don't accept -Credential properly :(
6476 if($Credential) {
6477 Write-Verbose "Mapping '$($_.gpcfilesyspath)' to network drive N:\"
6478 $Path = $_.gpcfilesyspath.TrimEnd('\')
6479 $Net = New-Object -ComObject WScript.Network
6480 $Net.MapNetworkDrive("N:", $Path, $False, $Credential.UserName, $Credential.GetNetworkCredential().Password)
6481 $TaskPath = "N:\Machine\Preferences\ScheduledTasks\"
6482 }
6483 else {
6484 $TaskPath = $_.gpcfilesyspath + "\Machine\Preferences\ScheduledTasks\"
6485 }
6486
6487 if($Remove) {
6488 if(!(Test-Path "$TaskPath\ScheduledTasks.xml")) {
6489 Throw "Scheduled task doesn't exist at $TaskPath\ScheduledTasks.xml"
6490 }
6491
6492 if (!$Force -and !$psCmdlet.ShouldContinue('Do you want to continue?',"Removing schtask at $TaskPath\ScheduledTasks.xml")) {
6493 return
6494 }
6495
6496 Remove-Item -Path "$TaskPath\ScheduledTasks.xml" -Force
6497 }
6498 else {
6499 if (!$Force -and !$psCmdlet.ShouldContinue('Do you want to continue?',"Creating schtask at $TaskPath\ScheduledTasks.xml")) {
6500 return
6501 }
6502
6503 # create the folder if it doesn't exist
6504 $Null = New-Item -ItemType Directory -Force -Path $TaskPath
6505
6506 if(Test-Path "$TaskPath\ScheduledTasks.xml") {
6507 Throw "Scheduled task already exists at $TaskPath\ScheduledTasks.xml !"
6508 }
6509
6510 $TaskXML | Set-Content -Encoding ASCII -Path "$TaskPath\ScheduledTasks.xml"
6511 }
6512
6513 if($Credential) {
6514 Write-Verbose "Removing mounted drive at N:\"
6515 $Net = New-Object -ComObject WScript.Network
6516 $Net.RemoveNetworkDrive("N:")
6517 }
6518 }
6519 catch {
6520 Write-Warning "Error for GPO $ProcessedGPOName : $_"
6521 if($Credential) {
6522 Write-Verbose "Removing mounted drive at N:\"
6523 $Net = New-Object -ComObject WScript.Network
6524 $Net.RemoveNetworkDrive("N:")
6525 }
6526 }
6527 }
6528}
6529
6530
6531function Get-NetGPOGroup {
6532<#
6533 .SYNOPSIS
6534
6535 Returns all GPOs in a domain that set "Restricted Groups" or use groups.xml on on target machines.
6536
6537 Author: @harmj0y
6538 License: BSD 3-Clause
6539 Required Dependencies: Get-NetGPO, Get-GptTmpl, Get-GroupsXML, Convert-NameToSid, Convert-SidToName
6540 Optional Dependencies: None
6541
6542 .DESCRIPTION
6543
6544 First enumerates all GPOs in the current/target domain using Get-NetGPO with passed
6545 arguments, and for each GPO checks if 'Restricted Groups' are set with GptTmpl.inf or
6546 group membership is set through Group Policy Preferences groups.xml files. For any
6547 GptTmpl.inf files found, the file is parsed with Get-GptTmpl and any 'Group Membership'
6548 section data is processed if present. Any found Groups.xml files are parsed with
6549 Get-GroupsXML and those memberships are returned as well.
6550
6551 .PARAMETER GPOname
6552
6553 The GPO name (GUID) to query for, wildcards accepted.
6554
6555 .PARAMETER DisplayName
6556
6557 The GPO display name to query for, wildcards accepted.
6558
6559 .PARAMETER Domain
6560
6561 The domain to query for GPOs, defaults to the current domain.
6562
6563 .PARAMETER DomainController
6564
6565 Domain controller to reflect LDAP queries through.
6566
6567 .PARAMETER ADSpath
6568
6569 The LDAP source to search through for GPOs.
6570 e.g. "LDAP://cn={8FF59D28-15D7-422A-BCB7-2AE45724125A},cn=policies,cn=system,DC=dev,DC=testlab,DC=local"
6571
6572 .PARAMETER ResolveMemberSIDs
6573
6574 Switch. Try to resolve the SIDs of all found group members.
6575
6576 .PARAMETER UsePSDrive
6577
6578 Switch. Mount any found policy files with temporary PSDrives.
6579
6580 .PARAMETER PageSize
6581
6582 The PageSize to set for the LDAP searcher object.
6583
6584 .EXAMPLE
6585
6586 PS C:\> Get-NetGPOGroup
6587
6588 Returns all local groups set by GPO along with their members and memberof.
6589
6590 .EXAMPLE
6591
6592 PS C:\> Get-NetGPOGroup -ResolveMemberSIDs
6593
6594 Returns all local groups set by GPO along with their members and memberof,
6595 and resolve any members to their domain SIDs.
6596
6597 .EXAMPLE
6598
6599 PS C:\> Get-NetGPOGroup -GPOName '{0847C615-6C4E-4D45-A064-6001040CC21C}'
6600
6601 Return any GPO-set groups for the GPO with the given name/GUID.
6602
6603 .EXAMPLE
6604
6605 PS C:\> Get-NetGPOGroup -DisplayName 'Desktops'
6606
6607 Return any GPO-set groups for the GPO with the given display name.
6608
6609 .LINK
6610
6611 https://morgansimonsenblog.azurewebsites.net/tag/groups/
6612#>
6613
6614 [CmdletBinding()]
6615 Param (
6616 [String]
6617 $GPOname = '*',
6618
6619 [String]
6620 $DisplayName,
6621
6622 [String]
6623 $Domain,
6624
6625 [String]
6626 $DomainController,
6627
6628 [String]
6629 $ADSpath,
6630
6631 [Switch]
6632 $ResolveMemberSIDs,
6633
6634 [Switch]
6635 $UsePSDrive,
6636
6637 [ValidateRange(1,10000)]
6638 [Int]
6639 $PageSize = 200
6640 )
6641
6642 $Option = [System.StringSplitOptions]::RemoveEmptyEntries
6643
6644 # get every GPO from the specified domain with restricted groups set
6645 Get-NetGPO -GPOName $GPOname -DisplayName $DisplayName -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize | ForEach-Object {
6646
6647 $GPOdisplayName = $_.displayname
6648 $GPOname = $_.name
6649 $GPOPath = $_.gpcfilesyspath
6650
6651 $ParseArgs = @{
6652 'GptTmplPath' = "$GPOPath\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
6653 'UsePSDrive' = $UsePSDrive
6654 }
6655
6656 # parse the GptTmpl.inf 'Restricted Groups' file if it exists
6657 $Inf = Get-GptTmpl @ParseArgs
6658
6659 if($Inf -and ($Inf.psbase.Keys -contains 'Group Membership')) {
6660
6661 $Memberships = @{}
6662
6663 # group the members/memberof fields for each entry
6664 ForEach ($Membership in $Inf.'Group Membership'.GetEnumerator()) {
6665 $Group, $Relation = $Membership.Key.Split('__', $Option) | ForEach-Object {$_.Trim()}
6666
6667 # extract out ALL members
6668 $MembershipValue = $Membership.Value | Where-Object {$_} | ForEach-Object { $_.Trim('*') } | Where-Object {$_}
6669
6670 if($ResolveMemberSIDs) {
6671 # if the resulting member is username and not a SID, attempt to resolve it
6672 $GroupMembers = @()
6673 ForEach($Member in $MembershipValue) {
6674 if($Member -and ($Member.Trim() -ne '')) {
6675 if($Member -notmatch '^S-1-.*') {
6676 $MemberSID = Convert-NameToSid -Domain $Domain -ObjectName $Member | Select-Object -ExpandProperty SID
6677 if($MemberSID) {
6678 $GroupMembers += $MemberSID
6679 }
6680 else {
6681 $GroupMembers += $Member
6682 }
6683 }
6684 else {
6685 $GroupMembers += $Member
6686 }
6687 }
6688 }
6689 $MembershipValue = $GroupMembers
6690 }
6691
6692 if(-not $Memberships[$Group]) {
6693 $Memberships[$Group] = @{}
6694 }
6695 if($MembershipValue -isnot [System.Array]) {$MembershipValue = @($MembershipValue)}
6696 $Memberships[$Group].Add($Relation, $MembershipValue)
6697 }
6698
6699 ForEach ($Membership in $Memberships.GetEnumerator()) {
6700 if($Membership -and $Membership.Key -and ($Membership.Key -match '^\*')) {
6701 # if the SID is already resolved (i.e. begins with *) try to resolve SID to a name
6702 $GroupSID = $Membership.Key.Trim('*')
6703 if($GroupSID -and ($GroupSID.Trim() -ne '')) {
6704 $GroupName = Convert-SidToName -SID $GroupSID
6705 }
6706 else {
6707 $GroupName = $False
6708 }
6709 }
6710 else {
6711 $GroupName = $Membership.Key
6712
6713 if($GroupName -and ($GroupName.Trim() -ne '')) {
6714 if($Groupname -match 'Administrators') {
6715 $GroupSID = 'S-1-5-32-544'
6716 }
6717 elseif($Groupname -match 'Remote Desktop') {
6718 $GroupSID = 'S-1-5-32-555'
6719 }
6720 elseif($Groupname -match 'Guests') {
6721 $GroupSID = 'S-1-5-32-546'
6722 }
6723 elseif($GroupName.Trim() -ne '') {
6724 $GroupSID = Convert-NameToSid -Domain $Domain -ObjectName $Groupname | Select-Object -ExpandProperty SID
6725 }
6726 else {
6727 $GroupSID = $Null
6728 }
6729 }
6730 }
6731
6732 $GPOGroup = New-Object PSObject
6733 $GPOGroup | Add-Member Noteproperty 'GPODisplayName' $GPODisplayName
6734 $GPOGroup | Add-Member Noteproperty 'GPOName' $GPOName
6735 $GPOGroup | Add-Member Noteproperty 'GPOPath' $GPOPath
6736 $GPOGroup | Add-Member Noteproperty 'GPOType' 'RestrictedGroups'
6737 $GPOGroup | Add-Member Noteproperty 'Filters' $Null
6738 $GPOGroup | Add-Member Noteproperty 'GroupName' $GroupName
6739 $GPOGroup | Add-Member Noteproperty 'GroupSID' $GroupSID
6740 $GPOGroup | Add-Member Noteproperty 'GroupMemberOf' $Membership.Value.Memberof
6741 $GPOGroup | Add-Member Noteproperty 'GroupMembers' $Membership.Value.Members
6742 $GPOGroup
6743 }
6744 }
6745
6746 $ParseArgs = @{
6747 'GroupsXMLpath' = "$GPOPath\MACHINE\Preferences\Groups\Groups.xml"
6748 'UsePSDrive' = $UsePSDrive
6749 }
6750
6751 Get-GroupsXML @ParseArgs | ForEach-Object {
6752 if($ResolveMemberSIDs) {
6753 $GroupMembers = @()
6754 ForEach($Member in $_.GroupMembers) {
6755 if($Member -and ($Member.Trim() -ne '')) {
6756 if($Member -notmatch '^S-1-.*') {
6757 # if the resulting member is username and not a SID, attempt to resolve it
6758 $MemberSID = Convert-NameToSid -Domain $Domain -ObjectName $Member | Select-Object -ExpandProperty SID
6759 if($MemberSID) {
6760 $GroupMembers += $MemberSID
6761 }
6762 else {
6763 $GroupMembers += $Member
6764 }
6765 }
6766 else {
6767 $GroupMembers += $Member
6768 }
6769 }
6770 }
6771 $_.GroupMembers = $GroupMembers
6772 }
6773
6774 $_ | Add-Member Noteproperty 'GPODisplayName' $GPODisplayName
6775 $_ | Add-Member Noteproperty 'GPOName' $GPOName
6776 $_ | Add-Member Noteproperty 'GPOType' 'GroupPolicyPreferences'
6777 $_
6778 }
6779 }
6780}
6781
6782
6783function Find-GPOLocation {
6784<#
6785 .SYNOPSIS
6786
6787 Enumerates the machines where a specific user/group is a member of a specific
6788 local group, all through GPO correlation.
6789
6790 Author: @harmj0y
6791 License: BSD 3-Clause
6792 Required Dependencies: Get-NetUser, Get-NetGroup, Get-NetGPOGroup, Get-NetOU, Get-NetComputer, Get-ADObject, Get-NetSite
6793 Optional Dependencies: None
6794
6795 .DESCRIPTION
6796
6797 Takes a user/group name and optional domain, and determines the computers in the domain
6798 the user/group has local admin (or RDP) rights to.
6799
6800 It does this by:
6801 1. resolving the user/group to its proper SID
6802 2. enumerating all groups the user/group is a current part of
6803 and extracting all target SIDs to build a target SID list
6804 3. pulling all GPOs that set 'Restricted Groups' or Groups.xml by calling
6805 Get-NetGPOGroup
6806 4. matching the target SID list to the queried GPO SID list
6807 to enumerate all GPO the user is effectively applied with
6808 5. enumerating all OUs and sites and applicable GPO GUIs are
6809 applied to through gplink enumerating
6810 6. querying for all computers under the given OUs or sites
6811
6812 If no user/group is specified, all user/group -> machine mappings discovered through
6813 GPO relationships are returned.
6814
6815 .PARAMETER UserName
6816
6817 A (single) user name name to query for access.
6818
6819 .PARAMETER GroupName
6820
6821 A (single) group name name to query for access.
6822
6823 .PARAMETER Domain
6824
6825 Optional domain the user exists in for querying, defaults to the current domain.
6826
6827 .PARAMETER DomainController
6828
6829 Domain controller to reflect LDAP queries through.
6830
6831 .PARAMETER LocalGroup
6832
6833 The local group to check access against.
6834 Can be "Administrators" (S-1-5-32-544), "RDP/Remote Desktop Users" (S-1-5-32-555),
6835 or a custom local SID. Defaults to local 'Administrators'.
6836
6837 .PARAMETER UsePSDrive
6838
6839 Switch. Mount any found policy files with temporary PSDrives.
6840
6841 .PARAMETER PageSize
6842
6843 The PageSize to set for the LDAP searcher object.
6844
6845 .EXAMPLE
6846
6847 PS C:\> Find-GPOLocation
6848
6849 Find all user/group -> machine relationships where the user/group is a member
6850 of the local administrators group on target machines.
6851
6852 .EXAMPLE
6853
6854 PS C:\> Find-GPOLocation -UserName dfm
6855
6856 Find all computers that dfm user has local administrator rights to in
6857 the current domain.
6858
6859 .EXAMPLE
6860
6861 PS C:\> Find-GPOLocation -UserName dfm -Domain dev.testlab.local
6862
6863 Find all computers that dfm user has local administrator rights to in
6864 the dev.testlab.local domain.
6865
6866 .EXAMPLE
6867
6868 PS C:\> Find-GPOLocation -UserName jason -LocalGroup RDP
6869
6870 Find all computers that jason has local RDP access rights to in the domain.
6871#>
6872
6873 [CmdletBinding()]
6874 Param (
6875 [String]
6876 $UserName,
6877
6878 [String]
6879 $GroupName,
6880
6881 [String]
6882 $Domain,
6883
6884 [String]
6885 $DomainController,
6886
6887 [String]
6888 $LocalGroup = 'Administrators',
6889
6890 [Switch]
6891 $UsePSDrive,
6892
6893 [ValidateRange(1,10000)]
6894 [Int]
6895 $PageSize = 200
6896 )
6897
6898 if($UserName) {
6899 # if a group name is specified, get that user object so we can extract the target SID
6900 $User = Get-NetUser -UserName $UserName -Domain $Domain -DomainController $DomainController -PageSize $PageSize | Select-Object -First 1
6901 $UserSid = $User.objectsid
6902
6903 if(-not $UserSid) {
6904 Throw "User '$UserName' not found!"
6905 }
6906
6907 $TargetSIDs = @($UserSid)
6908 $ObjectSamAccountName = $User.samaccountname
6909 $TargetObject = $UserSid
6910 }
6911 elseif($GroupName) {
6912 # if a group name is specified, get that group object so we can extract the target SID
6913 $Group = Get-NetGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize | Select-Object -First 1
6914 $GroupSid = $Group.objectsid
6915
6916 if(-not $GroupSid) {
6917 Throw "Group '$GroupName' not found!"
6918 }
6919
6920 $TargetSIDs = @($GroupSid)
6921 $ObjectSamAccountName = $Group.samaccountname
6922 $TargetObject = $GroupSid
6923 }
6924 else {
6925 $TargetSIDs = @('*')
6926 }
6927
6928 # figure out what the SID is of the target local group we're checking for membership in
6929 if($LocalGroup -like "*Admin*") {
6930 $TargetLocalSID = 'S-1-5-32-544'
6931 }
6932 elseif ( ($LocalGroup -like "*RDP*") -or ($LocalGroup -like "*Remote*") ) {
6933 $TargetLocalSID = 'S-1-5-32-555'
6934 }
6935 elseif ($LocalGroup -like "S-1-5-*") {
6936 $TargetLocalSID = $LocalGroup
6937 }
6938 else {
6939 throw "LocalGroup must be 'Administrators', 'RDP', or a 'S-1-5-X' SID format."
6940 }
6941
6942 # if we're not listing all relationships, use the tokenGroups approach from Get-NetGroup to
6943 # get all effective security SIDs this object is a part of
6944 if($TargetSIDs[0] -and ($TargetSIDs[0] -ne '*')) {
6945 $TargetSIDs += Get-NetGroup -Domain $Domain -DomainController $DomainController -PageSize $PageSize -UserName $ObjectSamAccountName -RawSids
6946 }
6947
6948 if(-not $TargetSIDs) {
6949 throw "No effective target SIDs!"
6950 }
6951
6952 Write-Verbose "TargetLocalSID: $TargetLocalSID"
6953 Write-Verbose "Effective target SIDs: $TargetSIDs"
6954
6955 $GPOGroupArgs = @{
6956 'Domain' = $Domain
6957 'DomainController' = $DomainController
6958 'UsePSDrive' = $UsePSDrive
6959 'ResolveMemberSIDs' = $True
6960 'PageSize' = $PageSize
6961 }
6962
6963 # enumerate all GPO group mappings for the target domain that involve our target SID set
6964 $GPOgroups = Get-NetGPOGroup @GPOGroupArgs | ForEach-Object {
6965
6966 $GPOgroup = $_
6967
6968 # if the locally set group is what we're looking for, check the GroupMembers ('members')
6969 # for our target SID
6970 if($GPOgroup.GroupSID -match $TargetLocalSID) {
6971 $GPOgroup.GroupMembers | Where-Object {$_} | ForEach-Object {
6972 if ( ($TargetSIDs[0] -eq '*') -or ($TargetSIDs -Contains $_) ) {
6973 $GPOgroup
6974 }
6975 }
6976 }
6977 # if the group is a 'memberof' the group we're looking for, check GroupSID against the targt SIDs
6978 if( ($GPOgroup.GroupMemberOf -contains $TargetLocalSID) ) {
6979 if( ($TargetSIDs[0] -eq '*') -or ($TargetSIDs -Contains $GPOgroup.GroupSID) ) {
6980 $GPOgroup
6981 }
6982 }
6983 } | Sort-Object -Property GPOName -Unique
6984
6985 $GPOgroups | ForEach-Object {
6986
6987 $GPOname = $_.GPODisplayName
6988 $GPOguid = $_.GPOName
6989 $GPOPath = $_.GPOPath
6990 $GPOType = $_.GPOType
6991 if($_.GroupMembers) {
6992 $GPOMembers = $_.GroupMembers
6993 }
6994 else {
6995 $GPOMembers = $_.GroupSID
6996 }
6997
6998 $Filters = $_.Filters
6999
7000 if(-not $TargetObject) {
7001 # if the * wildcard was used, set the ObjectDistName as the GPO member SID set
7002 # so all relationship mappings are output
7003 $TargetObjectSIDs = $GPOMembers
7004 }
7005 else {
7006 $TargetObjectSIDs = $TargetObject
7007 }
7008
7009 # find any OUs that have this GUID applied and then retrieve any computers from the OU
7010 Get-NetOU -Domain $Domain -DomainController $DomainController -GUID $GPOguid -FullData -PageSize $PageSize | ForEach-Object {
7011 if($Filters) {
7012 # filter for computer name/org unit if a filter is specified
7013 # TODO: handle other filters (i.e. OU filters?) again, I hate you GPP...
7014 $OUComputers = Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_.ADSpath -FullData -PageSize $PageSize | Where-Object {
7015 $_.adspath -match ($Filters.Value)
7016 } | ForEach-Object { $_.dnshostname }
7017 }
7018 else {
7019 $OUComputers = Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $_.ADSpath -PageSize $PageSize
7020 }
7021
7022 if($OUComputers) {
7023 if($OUComputers -isnot [System.Array]) {$OUComputers = @($OUComputers)}
7024
7025 ForEach ($TargetSid in $TargetObjectSIDs) {
7026 $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize
7027
7028 $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype
7029
7030 $GPOLocation = New-Object PSObject
7031 $GPOLocation | Add-Member Noteproperty 'ObjectName' $Object.samaccountname
7032 $GPOLocation | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname
7033 $GPOLocation | Add-Member Noteproperty 'ObjectSID' $Object.objectsid
7034 $GPOLocation | Add-Member Noteproperty 'Domain' $Domain
7035 $GPOLocation | Add-Member Noteproperty 'IsGroup' $IsGroup
7036 $GPOLocation | Add-Member Noteproperty 'GPODisplayName' $GPOname
7037 $GPOLocation | Add-Member Noteproperty 'GPOGuid' $GPOGuid
7038 $GPOLocation | Add-Member Noteproperty 'GPOPath' $GPOPath
7039 $GPOLocation | Add-Member Noteproperty 'GPOType' $GPOType
7040 $GPOLocation | Add-Member Noteproperty 'ContainerName' $_.distinguishedname
7041 $GPOLocation | Add-Member Noteproperty 'ComputerName' $OUComputers
7042 $GPOLocation.PSObject.TypeNames.Add('PowerView.GPOLocalGroup')
7043 $GPOLocation
7044 }
7045 }
7046 }
7047
7048 # find any sites that have this GUID applied
7049 Get-NetSite -Domain $Domain -DomainController $DomainController -GUID $GPOguid -PageSize $PageSize -FullData | ForEach-Object {
7050
7051 ForEach ($TargetSid in $TargetObjectSIDs) {
7052 $Object = Get-ADObject -SID $TargetSid -Domain $Domain -DomainController $DomainController -Credential $Credential -PageSize $PageSize
7053
7054 $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype
7055
7056 $AppliedSite = New-Object PSObject
7057 $AppliedSite | Add-Member Noteproperty 'ObjectName' $Object.samaccountname
7058 $AppliedSite | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname
7059 $AppliedSite | Add-Member Noteproperty 'ObjectSID' $Object.objectsid
7060 $AppliedSite | Add-Member Noteproperty 'IsGroup' $IsGroup
7061 $AppliedSite | Add-Member Noteproperty 'Domain' $Domain
7062 $AppliedSite | Add-Member Noteproperty 'GPODisplayName' $GPOname
7063 $AppliedSite | Add-Member Noteproperty 'GPOGuid' $GPOGuid
7064 $AppliedSite | Add-Member Noteproperty 'GPOPath' $GPOPath
7065 $AppliedSite | Add-Member Noteproperty 'GPOType' $GPOType
7066 $AppliedSite | Add-Member Noteproperty 'ContainerName' $_.distinguishedname
7067 $AppliedSite | Add-Member Noteproperty 'ComputerName' $_.siteobjectbl
7068 $AppliedSite.PSObject.TypeNames.Add('PowerView.GPOLocalGroup')
7069 $AppliedSite
7070 }
7071 }
7072 }
7073}
7074
7075
7076function Find-GPOComputerAdmin {
7077<#
7078 .SYNOPSIS
7079
7080 Takes a computer (or GPO) object and determines what users/groups are in the specified
7081 local group for the machine.
7082
7083 Author: @harmj0y
7084 License: BSD 3-Clause
7085 Required Dependencies: Get-NetComputer, Get-SiteName, Get-NetSite, Get-NetGPOGroup, Get-ADObject, Get-NetGroupMember, Convert-SidToName
7086 Optional Dependencies: None
7087
7088 .DESCRIPTION
7089
7090 If a -ComputerName is specified, retrieve the complete computer object, attempt to
7091 determine the OU the computer is a part of. Then resolve the computer's site name with
7092 Get-SiteName and retrieve all sites object Get-NetSite. For those results, attempt to
7093 enumerate all linked GPOs and associated local group settings with Get-NetGPOGroup. For
7094 each resulting GPO group, resolve the resulting user/group name to a full AD object and
7095 return the results. This will return the domain objects that are members of the specified
7096 -LocalGroup for the given computer.
7097
7098 Inverse of Find-GPOLocation.
7099
7100 .PARAMETER ComputerName
7101
7102 The computer to determine local administrative access to.
7103
7104 .PARAMETER OUName
7105
7106 OU name to determine who has local adminisrtative acess to computers
7107 within it.
7108
7109 .PARAMETER Domain
7110
7111 Optional domain the computer/OU exists in, defaults to the current domain.
7112
7113 .PARAMETER DomainController
7114
7115 Domain controller to reflect LDAP queries through.
7116
7117 .PARAMETER Recurse
7118
7119 Switch. If a returned member is a group, recurse and get all members.
7120
7121 .PARAMETER LocalGroup
7122
7123 The local group to check access against.
7124 Can be "Administrators" (S-1-5-32-544), "RDP/Remote Desktop Users" (S-1-5-32-555),
7125 or a custom local SID.
7126 Defaults to local 'Administrators'.
7127
7128 .PARAMETER UsePSDrive
7129
7130 Switch. Mount any found policy files with temporary PSDrives.
7131
7132 .PARAMETER PageSize
7133
7134 The PageSize to set for the LDAP searcher object.
7135
7136 .EXAMPLE
7137
7138 PS C:\> Find-GPOComputerAdmin -ComputerName WINDOWS3.dev.testlab.local
7139
7140 Finds users who have local admin rights over WINDOWS3 through GPO correlation.
7141
7142 .EXAMPLE
7143
7144 PS C:\> Find-GPOComputerAdmin -ComputerName WINDOWS3.dev.testlab.local -LocalGroup RDP
7145
7146 Finds users who have RDP rights over WINDOWS3 through GPO correlation.
7147#>
7148
7149 [CmdletBinding()]
7150 Param (
7151 [Parameter(ValueFromPipeline=$True)]
7152 [String]
7153 $ComputerName,
7154
7155 [String]
7156 $OUName,
7157
7158 [String]
7159 $Domain,
7160
7161 [String]
7162 $DomainController,
7163
7164 [Switch]
7165 $Recurse,
7166
7167 [String]
7168 $LocalGroup = 'Administrators',
7169
7170 [Switch]
7171 $UsePSDrive,
7172
7173 [ValidateRange(1,10000)]
7174 [Int]
7175 $PageSize = 200
7176 )
7177
7178 process {
7179
7180 if(!$ComputerName -and !$OUName) {
7181 Throw "-ComputerName or -OUName must be provided"
7182 }
7183
7184 $GPOGroups = @()
7185
7186 if($ComputerName) {
7187 $Computers = Get-NetComputer -ComputerName $ComputerName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize
7188
7189 if(!$Computers) {
7190 throw "Computer $ComputerName in domain '$Domain' not found! Try a fully qualified host name"
7191 }
7192
7193 $TargetOUs = @()
7194 ForEach($Computer in $Computers) {
7195 # extract all OUs a computer is a part of
7196 $DN = $Computer.distinguishedname
7197
7198 $TargetOUs += $DN.split(",") | ForEach-Object {
7199 if($_.startswith("OU=")) {
7200 $DN.substring($DN.indexof($_))
7201 }
7202 }
7203 }
7204
7205 # enumerate any linked GPOs for the computer's site
7206 $ComputerSite = (Get-SiteName -ComputerName $ComputerName).SiteName
7207 if($ComputerSite -and ($ComputerSite -notlike 'Error*')) {
7208 $GPOGroups += Get-NetSite -SiteName $ComputerSite -FullData | ForEach-Object {
7209 if($_.gplink) {
7210 $_.gplink.split("][") | ForEach-Object {
7211 if ($_.startswith("LDAP")) {
7212 $_.split(";")[0]
7213 }
7214 }
7215 }
7216 } | ForEach-Object {
7217 $GPOGroupArgs = @{
7218 'Domain' = $Domain
7219 'DomainController' = $DomainController
7220 'ResolveMemberSIDs' = $True
7221 'UsePSDrive' = $UsePSDrive
7222 'PageSize' = $PageSize
7223 }
7224
7225 # for each GPO link, get any locally set user/group SIDs
7226 Get-NetGPOGroup @GPOGroupArgs
7227 }
7228 }
7229 }
7230 else {
7231 $TargetOUs = @($OUName)
7232 }
7233
7234 Write-Verbose "Target OUs: $TargetOUs"
7235
7236 $TargetOUs | Where-Object {$_} | ForEach-Object {
7237
7238 $GPOLinks = Get-NetOU -Domain $Domain -DomainController $DomainController -ADSpath $_ -FullData -PageSize $PageSize | ForEach-Object {
7239 # and then get any GPO links
7240 if($_.gplink) {
7241 $_.gplink.split("][") | ForEach-Object {
7242 if ($_.startswith("LDAP")) {
7243 $_.split(";")[0]
7244 }
7245 }
7246 }
7247 }
7248
7249 $GPOGroupArgs = @{
7250 'Domain' = $Domain
7251 'DomainController' = $DomainController
7252 'UsePSDrive' = $UsePSDrive
7253 'ResolveMemberSIDs' = $True
7254 'PageSize' = $PageSize
7255 }
7256
7257 # extract GPO groups that are set through any gPlink for this OU
7258 $GPOGroups += Get-NetGPOGroup @GPOGroupArgs | ForEach-Object {
7259 ForEach($GPOLink in $GPOLinks) {
7260 $Name = $_.GPOName
7261 if($GPOLink -like "*$Name*") {
7262 $_
7263 }
7264 }
7265 }
7266 }
7267
7268 # for each found GPO group, resolve the SIDs of the members
7269 $GPOgroups | Sort-Object -Property GPOName -Unique | ForEach-Object {
7270 $GPOGroup = $_
7271
7272 if($GPOGroup.GroupMembers) {
7273 $GPOMembers = $GPOGroup.GroupMembers
7274 }
7275 else {
7276 $GPOMembers = $GPOGroup.GroupSID
7277 }
7278
7279 $GPOMembers | ForEach-Object {
7280 # resolve this SID to a domain object
7281 $Object = Get-ADObject -Domain $Domain -DomainController $DomainController -PageSize $PageSize -SID $_
7282
7283 $IsGroup = @('268435456','268435457','536870912','536870913') -contains $Object.samaccounttype
7284
7285 $GPOComputerAdmin = New-Object PSObject
7286 $GPOComputerAdmin | Add-Member Noteproperty 'ComputerName' $ComputerName
7287 $GPOComputerAdmin | Add-Member Noteproperty 'ObjectName' $Object.samaccountname
7288 $GPOComputerAdmin | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname
7289 $GPOComputerAdmin | Add-Member Noteproperty 'ObjectSID' $_
7290 $GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $IsGroup
7291 $GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPOGroup.GPODisplayName
7292 $GPOComputerAdmin | Add-Member Noteproperty 'GPOGuid' $GPOGroup.GPOName
7293 $GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPOGroup.GPOPath
7294 $GPOComputerAdmin | Add-Member Noteproperty 'GPOType' $GPOGroup.GPOType
7295 $GPOComputerAdmin
7296
7297 # if we're recursing and the current result object is a group
7298 if($Recurse -and $GPOComputerAdmin.isGroup) {
7299
7300 Get-NetGroupMember -Domain $Domain -DomainController $DomainController -SID $_ -FullData -Recurse -PageSize $PageSize | ForEach-Object {
7301
7302 $MemberDN = $_.distinguishedName
7303
7304 # extract the FQDN from the Distinguished Name
7305 $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
7306
7307 $MemberIsGroup = @('268435456','268435457','536870912','536870913') -contains $_.samaccounttype
7308
7309 if ($_.samAccountName) {
7310 # forest users have the samAccountName set
7311 $MemberName = $_.samAccountName
7312 }
7313 else {
7314 # external trust users have a SID, so convert it
7315 try {
7316 $MemberName = Convert-SidToName $_.cn
7317 }
7318 catch {
7319 # if there's a problem contacting the domain to resolve the SID
7320 $MemberName = $_.cn
7321 }
7322 }
7323
7324 $GPOComputerAdmin = New-Object PSObject
7325 $GPOComputerAdmin | Add-Member Noteproperty 'ComputerName' $ComputerName
7326 $GPOComputerAdmin | Add-Member Noteproperty 'ObjectName' $MemberName
7327 $GPOComputerAdmin | Add-Member Noteproperty 'ObjectDN' $MemberDN
7328 $GPOComputerAdmin | Add-Member Noteproperty 'ObjectSID' $_.objectsid
7329 $GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $MemberIsGrou
7330 $GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPOGroup.GPODisplayName
7331 $GPOComputerAdmin | Add-Member Noteproperty 'GPOGuid' $GPOGroup.GPOName
7332 $GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPOGroup.GPOPath
7333 $GPOComputerAdmin | Add-Member Noteproperty 'GPOType' $GPOTypep
7334 $GPOComputerAdmin
7335 }
7336 }
7337 }
7338 }
7339 }
7340}
7341
7342
7343function Get-DomainPolicy {
7344<#
7345 .SYNOPSIS
7346
7347 Returns the default domain or DC policy for a given
7348 domain or domain controller.
7349
7350 Thanks Sean Metacalf (@pyrotek3) for the idea and guidance.
7351
7352 .PARAMETER Source
7353
7354 Extract Domain or DC (domain controller) policies.
7355
7356 .PARAMETER Domain
7357
7358 The domain to query for default policies, defaults to the current domain.
7359
7360 .PARAMETER DomainController
7361
7362 Domain controller to reflect LDAP queries through.
7363
7364 .PARAMETER ResolveSids
7365
7366 Switch. Resolve Sids from a DC policy to object names.
7367
7368 .PARAMETER UsePSDrive
7369
7370 Switch. Mount any found policy files with temporary PSDrives.
7371
7372 .EXAMPLE
7373
7374 PS C:\> Get-DomainPolicy
7375
7376 Returns the domain policy for the current domain.
7377
7378 .EXAMPLE
7379
7380 PS C:\> Get-DomainPolicy -Source DC -DomainController MASTER.testlab.local
7381
7382 Returns the policy for the MASTER.testlab.local domain controller.
7383#>
7384
7385 [CmdletBinding()]
7386 Param (
7387 [String]
7388 [ValidateSet("Domain","DC")]
7389 $Source ="Domain",
7390
7391 [String]
7392 $Domain,
7393
7394 [String]
7395 $DomainController,
7396
7397 [Switch]
7398 $ResolveSids,
7399
7400 [Switch]
7401 $UsePSDrive
7402 )
7403
7404 if($Source -eq "Domain") {
7405 # query the given domain for the default domain policy object
7406 $GPO = Get-NetGPO -Domain $Domain -DomainController $DomainController -GPOname "{31B2F340-016D-11D2-945F-00C04FB984F9}"
7407
7408 if($GPO) {
7409 # grab the GptTmpl.inf file and parse it
7410 $GptTmplPath = $GPO.gpcfilesyspath + "\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
7411
7412 $ParseArgs = @{
7413 'GptTmplPath' = $GptTmplPath
7414 'UsePSDrive' = $UsePSDrive
7415 }
7416
7417 # parse the GptTmpl.inf
7418 Get-GptTmpl @ParseArgs
7419 }
7420
7421 }
7422 elseif($Source -eq "DC") {
7423 # query the given domain/dc for the default domain controller policy object
7424 $GPO = Get-NetGPO -Domain $Domain -DomainController $DomainController -GPOname "{6AC1786C-016F-11D2-945F-00C04FB984F9}"
7425
7426 if($GPO) {
7427 # grab the GptTmpl.inf file and parse it
7428 $GptTmplPath = $GPO.gpcfilesyspath + "\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
7429
7430 $ParseArgs = @{
7431 'GptTmplPath' = $GptTmplPath
7432 'UsePSDrive' = $UsePSDrive
7433 }
7434
7435 # parse the GptTmpl.inf
7436 Get-GptTmpl @ParseArgs | ForEach-Object {
7437 if($ResolveSids) {
7438 # if we're resolving sids in PrivilegeRights to names
7439 $Policy = New-Object PSObject
7440 $_.psobject.properties | ForEach-Object {
7441 if( $_.Name -eq 'PrivilegeRights') {
7442
7443 $PrivilegeRights = New-Object PSObject
7444 # for every nested SID member of PrivilegeRights, try to unpack everything and resolve the SIDs as appropriate
7445 $_.Value.psobject.properties | ForEach-Object {
7446
7447 $Sids = $_.Value | ForEach-Object {
7448 try {
7449 if($_ -isnot [System.Array]) {
7450 Convert-SidToName $_
7451 }
7452 else {
7453 $_ | ForEach-Object { Convert-SidToName $_ }
7454 }
7455 }
7456 catch {
7457 Write-Verbose "Error resolving SID : $_"
7458 }
7459 }
7460
7461 $PrivilegeRights | Add-Member Noteproperty $_.Name $Sids
7462 }
7463
7464 $Policy | Add-Member Noteproperty 'PrivilegeRights' $PrivilegeRights
7465 }
7466 else {
7467 $Policy | Add-Member Noteproperty $_.Name $_.Value
7468 }
7469 }
7470 $Policy
7471 }
7472 else { $_ }
7473 }
7474 }
7475 }
7476}
7477
7478
7479
7480########################################################
7481#
7482# Functions that enumerate a single host, either through
7483# WinNT, WMI, remote registry, or API calls
7484# (with PSReflect).
7485#
7486########################################################
7487
7488function Get-NetLocalGroup {
7489<#
7490 .SYNOPSIS
7491
7492 Gets a list of all current users in a specified local group,
7493 or returns the names of all local groups with -ListGroups.
7494
7495 .PARAMETER ComputerName
7496
7497 The hostname or IP to query for local group users.
7498
7499 .PARAMETER ComputerFile
7500
7501 File of hostnames/IPs to query for local group users.
7502
7503 .PARAMETER GroupName
7504
7505 The local group name to query for users. If not given, it defaults to "Administrators"
7506
7507 .PARAMETER ListGroups
7508
7509 Switch. List all the local groups instead of their members.
7510 Old Get-NetLocalGroups functionality.
7511
7512 .PARAMETER Recurse
7513
7514 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.
7515
7516 .PARAMETER API
7517
7518 Switch. Use API calls instead of the WinNT service provider. Less information,
7519 but the results are faster.
7520
7521 .EXAMPLE
7522
7523 PS C:\> Get-NetLocalGroup
7524
7525 Returns the usernames that of members of localgroup "Administrators" on the local host.
7526
7527 .EXAMPLE
7528
7529 PS C:\> Get-NetLocalGroup -ComputerName WINDOWSXP
7530
7531 Returns all the local administrator accounts for WINDOWSXP
7532
7533 .EXAMPLE
7534
7535 PS C:\> Get-NetLocalGroup -ComputerName WINDOWS7 -Recurse
7536
7537 Returns all effective local/domain users/groups that can access WINDOWS7 with
7538 local administrative privileges.
7539
7540 .EXAMPLE
7541
7542 PS C:\> Get-NetLocalGroup -ComputerName WINDOWS7 -ListGroups
7543
7544 Returns all local groups on the WINDOWS7 host.
7545
7546 .EXAMPLE
7547
7548 PS C:\> "WINDOWS7", "WINDOWSSP" | Get-NetLocalGroup -API
7549
7550 Returns all local groups on the the passed hosts using API calls instead of the
7551 WinNT service provider.
7552
7553 .LINK
7554
7555 http://stackoverflow.com/questions/21288220/get-all-local-members-and-groups-displayed-together
7556 http://msdn.microsoft.com/en-us/library/aa772211(VS.85).aspx
7557#>
7558
7559 [CmdletBinding(DefaultParameterSetName = 'WinNT')]
7560 param(
7561 [Parameter(ParameterSetName = 'API', Position=0, ValueFromPipeline=$True)]
7562 [Parameter(ParameterSetName = 'WinNT', Position=0, ValueFromPipeline=$True)]
7563 [Alias('HostName')]
7564 [String[]]
7565 $ComputerName = $Env:ComputerName,
7566
7567 [Parameter(ParameterSetName = 'WinNT')]
7568 [Parameter(ParameterSetName = 'API')]
7569 [ValidateScript({Test-Path -Path $_ })]
7570 [Alias('HostList')]
7571 [String]
7572 $ComputerFile,
7573
7574 [Parameter(ParameterSetName = 'WinNT')]
7575 [Parameter(ParameterSetName = 'API')]
7576 [String]
7577 $GroupName = 'Administrators',
7578
7579 [Parameter(ParameterSetName = 'WinNT')]
7580 [Switch]
7581 $ListGroups,
7582
7583 [Parameter(ParameterSetName = 'WinNT')]
7584 [Switch]
7585 $Recurse,
7586
7587 [Parameter(ParameterSetName = 'API')]
7588 [Switch]
7589 $API
7590 )
7591
7592 process {
7593
7594 $Servers = @()
7595
7596 # if we have a host list passed, grab it
7597 if($ComputerFile) {
7598 $Servers = Get-Content -Path $ComputerFile
7599 }
7600 else {
7601 # otherwise assume a single host name
7602 $Servers += $ComputerName | Get-NameField
7603 }
7604
7605 # query the specified group using the WINNT provider, and
7606 # extract fields as appropriate from the results
7607 ForEach($Server in $Servers) {
7608
7609 if($API) {
7610 # if we're using the Netapi32 NetLocalGroupGetMembers API call to get the local group information
7611 # arguments for NetLocalGroupGetMembers
7612 $QueryLevel = 2
7613 $PtrInfo = [IntPtr]::Zero
7614 $EntriesRead = 0
7615 $TotalRead = 0
7616 $ResumeHandle = 0
7617
7618 # get the local user information
7619 $Result = $Netapi32::NetLocalGroupGetMembers($Server, $GroupName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
7620
7621 # Locate the offset of the initial intPtr
7622 $Offset = $PtrInfo.ToInt64()
7623
7624 $LocalUsers = @()
7625
7626 # 0 = success
7627 if (($Result -eq 0) -and ($Offset -gt 0)) {
7628
7629 # Work out how much to increment the pointer by finding out the size of the structure
7630 $Increment = $LOCALGROUP_MEMBERS_INFO_2::GetSize()
7631
7632 # parse all the result structures
7633 for ($i = 0; ($i -lt $EntriesRead); $i++) {
7634 # create a new int ptr at the given offset and cast the pointer as our result structure
7635 $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
7636 $Info = $NewIntPtr -as $LOCALGROUP_MEMBERS_INFO_2
7637
7638 $Offset = $NewIntPtr.ToInt64()
7639 $Offset += $Increment
7640
7641 $SidString = ""
7642 $Result2 = $Advapi32::ConvertSidToStringSid($Info.lgrmi2_sid, [ref]$SidString);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
7643
7644 if($Result2 -eq 0) {
7645 Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
7646 }
7647 else {
7648 $LocalUser = New-Object PSObject
7649 $LocalUser | Add-Member Noteproperty 'ComputerName' $Server
7650 $LocalUser | Add-Member Noteproperty 'AccountName' $Info.lgrmi2_domainandname
7651 $LocalUser | Add-Member Noteproperty 'SID' $SidString
7652
7653 $IsGroup = $($Info.lgrmi2_sidusage -eq 'SidTypeGroup')
7654 $LocalUser | Add-Member Noteproperty 'IsGroup' $IsGroup
7655 $LocalUser.PSObject.TypeNames.Add('PowerView.LocalUserAPI')
7656
7657 $LocalUsers += $LocalUser
7658 }
7659 }
7660
7661 # free up the result buffer
7662 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
7663
7664 # try to extract out the machine SID by using the -500 account as a reference
7665 $MachineSid = $LocalUsers | Where-Object {$_.SID -like '*-500'}
7666 $Parts = $MachineSid.SID.Split('-')
7667 $MachineSid = $Parts[0..($Parts.Length -2)] -join '-'
7668
7669 $LocalUsers | ForEach-Object {
7670 if($_.SID -match $MachineSid) {
7671 $_ | Add-Member Noteproperty 'IsDomain' $False
7672 }
7673 else {
7674 $_ | Add-Member Noteproperty 'IsDomain' $True
7675 }
7676 }
7677 $LocalUsers
7678 }
7679 else {
7680 Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)"
7681 }
7682 }
7683
7684 else {
7685 # otherwise we're using the WinNT service provider
7686 try {
7687 if($ListGroups) {
7688 # if we're listing the group names on a remote server
7689 $Computer = [ADSI]"WinNT://$Server,computer"
7690
7691 $Computer.psbase.children | Where-Object { $_.psbase.schemaClassName -eq 'group' } | ForEach-Object {
7692 $Group = New-Object PSObject
7693 $Group | Add-Member Noteproperty 'Server' $Server
7694 $Group | Add-Member Noteproperty 'Group' ($_.name[0])
7695 $Group | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier $_.objectsid[0],0).Value)
7696 $Group | Add-Member Noteproperty 'Description' ($_.Description[0])
7697 $Group.PSObject.TypeNames.Add('PowerView.LocalGroup')
7698 $Group
7699 }
7700 }
7701 else {
7702 # otherwise we're listing the group members
7703 $Members = @($([ADSI]"WinNT://$Server/$GroupName,group").psbase.Invoke('Members'))
7704
7705 $Members | ForEach-Object {
7706
7707 $Member = New-Object PSObject
7708 $Member | Add-Member Noteproperty 'ComputerName' $Server
7709
7710 $AdsPath = ($_.GetType().InvokeMember('Adspath', 'GetProperty', $Null, $_, $Null)).Replace('WinNT://', '')
7711 $Class = $_.GetType().InvokeMember('Class', 'GetProperty', $Null, $_, $Null)
7712
7713 # try to translate the NT4 domain to a FQDN if possible
7714 $Name = Convert-ADName -ObjectName $AdsPath -InputType 'NT4' -OutputType 'Canonical'
7715 $IsGroup = $Class -eq "Group"
7716
7717 if($Name) {
7718 $FQDN = $Name.split("/")[0]
7719 $ObjName = $AdsPath.split("/")[-1]
7720 $Name = "$FQDN/$ObjName"
7721 $IsDomain = $True
7722 }
7723 else {
7724 $ObjName = $AdsPath.split("/")[-1]
7725 $Name = $AdsPath
7726 $IsDomain = $False
7727 }
7728
7729 $Member | Add-Member Noteproperty 'AccountName' $Name
7730 $Member | Add-Member Noteproperty 'IsDomain' $IsDomain
7731 $Member | Add-Member Noteproperty 'IsGroup' $IsGroup
7732
7733 if($IsDomain) {
7734 # translate the binary sid to a string
7735 $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($_.GetType().InvokeMember('ObjectSID', 'GetProperty', $Null, $_, $Null),0)).Value)
7736 $Member | Add-Member Noteproperty 'Description' ""
7737 $Member | Add-Member Noteproperty 'Disabled' ""
7738
7739 if($IsGroup) {
7740 $Member | Add-Member Noteproperty 'LastLogin' ""
7741 }
7742 else {
7743 try {
7744 $Member | Add-Member Noteproperty 'LastLogin' ( $_.GetType().InvokeMember('LastLogin', 'GetProperty', $Null, $_, $Null))
7745 }
7746 catch {
7747 $Member | Add-Member Noteproperty 'LastLogin' ""
7748 }
7749 }
7750 $Member | Add-Member Noteproperty 'PwdLastSet' ""
7751 $Member | Add-Member Noteproperty 'PwdExpired' ""
7752 $Member | Add-Member Noteproperty 'UserFlags' ""
7753 }
7754 else {
7755 # repull this user object so we can ensure correct information
7756 $LocalUser = $([ADSI] "WinNT://$AdsPath")
7757
7758 # translate the binary sid to a string
7759 $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalUser.objectSid.value,0)).Value)
7760 $Member | Add-Member Noteproperty 'Description' ($LocalUser.Description[0])
7761
7762 if($IsGroup) {
7763 $Member | Add-Member Noteproperty 'PwdLastSet' ""
7764 $Member | Add-Member Noteproperty 'PwdExpired' ""
7765 $Member | Add-Member Noteproperty 'UserFlags' ""
7766 $Member | Add-Member Noteproperty 'Disabled' ""
7767 $Member | Add-Member Noteproperty 'LastLogin' ""
7768 }
7769 else {
7770 $Member | Add-Member Noteproperty 'PwdLastSet' ( (Get-Date).AddSeconds(-$LocalUser.PasswordAge[0]))
7771 $Member | Add-Member Noteproperty 'PwdExpired' ( $LocalUser.PasswordExpired[0] -eq '1')
7772 $Member | Add-Member Noteproperty 'UserFlags' ( $LocalUser.UserFlags[0] )
7773 # UAC flags of 0x2 mean the account is disabled
7774 $Member | Add-Member Noteproperty 'Disabled' $(($LocalUser.userFlags.value -band 2) -eq 2)
7775 try {
7776 $Member | Add-Member Noteproperty 'LastLogin' ( $LocalUser.LastLogin[0])
7777 }
7778 catch {
7779 $Member | Add-Member Noteproperty 'LastLogin' ""
7780 }
7781 }
7782 }
7783 $Member.PSObject.TypeNames.Add('PowerView.LocalUser')
7784 $Member
7785
7786 # if the result is a group domain object and we're recursing,
7787 # try to resolve all the group member results
7788 if($Recurse -and $IsGroup) {
7789 if($IsDomain) {
7790 $FQDN = $Name.split("/")[0]
7791 $GroupName = $Name.split("/")[1].trim()
7792
7793 Get-NetGroupMember -GroupName $GroupName -Domain $FQDN -FullData -Recurse | ForEach-Object {
7794
7795 $Member = New-Object PSObject
7796 $Member | Add-Member Noteproperty 'ComputerName' "$FQDN/$($_.GroupName)"
7797
7798 $MemberDN = $_.distinguishedName
7799 # extract the FQDN from the Distinguished Name
7800 $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
7801
7802 $MemberIsGroup = @('268435456','268435457','536870912','536870913') -contains $_.samaccounttype
7803
7804 if ($_.samAccountName) {
7805 # forest users have the samAccountName set
7806 $MemberName = $_.samAccountName
7807 }
7808 else {
7809 try {
7810 # external trust users have a SID, so convert it
7811 try {
7812 $MemberName = Convert-SidToName $_.cn
7813 }
7814 catch {
7815 # if there's a problem contacting the domain to resolve the SID
7816 $MemberName = $_.cn
7817 }
7818 }
7819 catch {
7820 Write-Debug "Error resolving SID : $_"
7821 }
7822 }
7823
7824 $Member | Add-Member Noteproperty 'AccountName' "$MemberDomain/$MemberName"
7825 $Member | Add-Member Noteproperty 'SID' $_.objectsid
7826 $Member | Add-Member Noteproperty 'Description' $_.description
7827 $Member | Add-Member Noteproperty 'Disabled' $False
7828 $Member | Add-Member Noteproperty 'IsGroup' $MemberIsGroup
7829 $Member | Add-Member Noteproperty 'IsDomain' $True
7830 $Member | Add-Member Noteproperty 'LastLogin' ''
7831 $Member | Add-Member Noteproperty 'PwdLastSet' $_.pwdLastSet
7832 $Member | Add-Member Noteproperty 'PwdExpired' ''
7833 $Member | Add-Member Noteproperty 'UserFlags' $_.userAccountControl
7834 $Member.PSObject.TypeNames.Add('PowerView.LocalUser')
7835 $Member
7836 }
7837 } else {
7838 Get-NetLocalGroup -ComputerName $Server -GroupName $ObjName -Recurse
7839 }
7840 }
7841 }
7842 }
7843 }
7844 catch {
7845 Write-Warning "[!] Error: $_"
7846 }
7847 }
7848 }
7849 }
7850}
7851
7852filter Get-NetShare {
7853<#
7854 .SYNOPSIS
7855
7856 This function will execute the NetShareEnum Win32API call to query
7857 a given host for open shares. This is a replacement for
7858 "net share \\hostname"
7859
7860 .PARAMETER ComputerName
7861
7862 The hostname to query for shares. Also accepts IP addresses.
7863
7864 .OUTPUTS
7865
7866 SHARE_INFO_1 structure. A representation of the SHARE_INFO_1
7867 result structure which includes the name and note for each share,
7868 with the ComputerName added.
7869
7870 .EXAMPLE
7871
7872 PS C:\> Get-NetShare
7873
7874 Returns active shares on the local host.
7875
7876 .EXAMPLE
7877
7878 PS C:\> Get-NetShare -ComputerName sqlserver
7879
7880 Returns active shares on the 'sqlserver' host
7881
7882 .EXAMPLE
7883
7884 PS C:\> Get-NetComputer | Get-NetShare
7885
7886 Returns all shares for all computers in the domain.
7887
7888 .LINK
7889
7890 http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
7891#>
7892
7893 [CmdletBinding()]
7894 param(
7895 [Parameter(ValueFromPipeline=$True)]
7896 [Alias('HostName')]
7897 [Object[]]
7898 [ValidateNotNullOrEmpty()]
7899 $ComputerName = 'localhost'
7900 )
7901
7902 # extract the computer name from whatever object was passed on the pipeline
7903 $Computer = $ComputerName | Get-NameField
7904
7905 # arguments for NetShareEnum
7906 $QueryLevel = 1
7907 $PtrInfo = [IntPtr]::Zero
7908 $EntriesRead = 0
7909 $TotalRead = 0
7910 $ResumeHandle = 0
7911
7912 # get the share information
7913 $Result = $Netapi32::NetShareEnum($Computer, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
7914
7915 # Locate the offset of the initial intPtr
7916 $Offset = $PtrInfo.ToInt64()
7917
7918 # 0 = success
7919 if (($Result -eq 0) -and ($Offset -gt 0)) {
7920
7921 # Work out how much to increment the pointer by finding out the size of the structure
7922 $Increment = $SHARE_INFO_1::GetSize()
7923
7924 # parse all the result structures
7925 for ($i = 0; ($i -lt $EntriesRead); $i++) {
7926 # create a new int ptr at the given offset and cast the pointer as our result structure
7927 $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
7928 $Info = $NewIntPtr -as $SHARE_INFO_1
7929
7930 # return all the sections of the structure
7931 $Shares = $Info | Select-Object *
7932 $Shares | Add-Member Noteproperty 'ComputerName' $Computer
7933 $Offset = $NewIntPtr.ToInt64()
7934 $Offset += $Increment
7935 $Shares
7936 }
7937
7938 # free up the result buffer
7939 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
7940 }
7941 else {
7942 Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)"
7943 }
7944}
7945
7946
7947filter Get-NetLoggedon {
7948<#
7949 .SYNOPSIS
7950
7951 This function will execute the NetWkstaUserEnum Win32API call to query
7952 a given host for actively logged on users.
7953
7954 .PARAMETER ComputerName
7955
7956 The hostname to query for logged on users.
7957
7958 .OUTPUTS
7959
7960 WKSTA_USER_INFO_1 structure. A representation of the WKSTA_USER_INFO_1
7961 result structure which includes the username and domain of logged on users,
7962 with the ComputerName added.
7963
7964 .EXAMPLE
7965
7966 PS C:\> Get-NetLoggedon
7967
7968 Returns users actively logged onto the local host.
7969
7970 .EXAMPLE
7971
7972 PS C:\> Get-NetLoggedon -ComputerName sqlserver
7973
7974 Returns users actively logged onto the 'sqlserver' host.
7975
7976 .EXAMPLE
7977
7978 PS C:\> Get-NetComputer | Get-NetLoggedon
7979
7980 Returns all logged on userse for all computers in the domain.
7981
7982 .LINK
7983
7984 http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
7985#>
7986
7987 [CmdletBinding()]
7988 param(
7989 [Parameter(ValueFromPipeline=$True)]
7990 [Alias('HostName')]
7991 [Object[]]
7992 [ValidateNotNullOrEmpty()]
7993 $ComputerName = 'localhost'
7994 )
7995
7996 # extract the computer name from whatever object was passed on the pipeline
7997 $Computer = $ComputerName | Get-NameField
7998
7999 # Declare the reference variables
8000 $QueryLevel = 1
8001 $PtrInfo = [IntPtr]::Zero
8002 $EntriesRead = 0
8003 $TotalRead = 0
8004 $ResumeHandle = 0
8005
8006 # get logged on user information
8007 $Result = $Netapi32::NetWkstaUserEnum($Computer, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
8008
8009 # Locate the offset of the initial intPtr
8010 $Offset = $PtrInfo.ToInt64()
8011
8012 # 0 = success
8013 if (($Result -eq 0) -and ($Offset -gt 0)) {
8014
8015 # Work out how much to increment the pointer by finding out the size of the structure
8016 $Increment = $WKSTA_USER_INFO_1::GetSize()
8017
8018 # parse all the result structures
8019 for ($i = 0; ($i -lt $EntriesRead); $i++) {
8020 # create a new int ptr at the given offset and cast the pointer as our result structure
8021 $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
8022 $Info = $NewIntPtr -as $WKSTA_USER_INFO_1
8023
8024 # return all the sections of the structure
8025 $LoggedOn = $Info | Select-Object *
8026 $LoggedOn | Add-Member Noteproperty 'ComputerName' $Computer
8027 $Offset = $NewIntPtr.ToInt64()
8028 $Offset += $Increment
8029 $LoggedOn
8030 }
8031
8032 # free up the result buffer
8033 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
8034 }
8035 else {
8036 Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)"
8037 }
8038}
8039
8040
8041filter Get-NetSession {
8042<#
8043 .SYNOPSIS
8044
8045 This function will execute the NetSessionEnum Win32API call to query
8046 a given host for active sessions on the host.
8047 Heavily adapted from dunedinite's post on stackoverflow (see LINK below)
8048
8049 .PARAMETER ComputerName
8050
8051 The ComputerName to query for active sessions.
8052
8053 .PARAMETER UserName
8054
8055 The user name to filter for active sessions.
8056
8057 .OUTPUTS
8058
8059 SESSION_INFO_10 structure. A representation of the SESSION_INFO_10
8060 result structure which includes the host and username associated
8061 with active sessions, with the ComputerName added.
8062
8063 .EXAMPLE
8064
8065 PS C:\> Get-NetSession
8066
8067 Returns active sessions on the local host.
8068
8069 .EXAMPLE
8070
8071 PS C:\> Get-NetSession -ComputerName sqlserver
8072
8073 Returns active sessions on the 'sqlserver' host.
8074
8075 .EXAMPLE
8076
8077 PS C:\> Get-NetDomainController | Get-NetSession
8078
8079 Returns active sessions on all domain controllers.
8080
8081 .LINK
8082
8083 http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
8084#>
8085
8086 [CmdletBinding()]
8087 param(
8088 [Parameter(ValueFromPipeline=$True)]
8089 [Alias('HostName')]
8090 [Object[]]
8091 [ValidateNotNullOrEmpty()]
8092 $ComputerName = 'localhost',
8093
8094 [String]
8095 $UserName = ''
8096 )
8097
8098 # extract the computer name from whatever object was passed on the pipeline
8099 $Computer = $ComputerName | Get-NameField
8100
8101 # arguments for NetSessionEnum
8102 $QueryLevel = 10
8103 $PtrInfo = [IntPtr]::Zero
8104 $EntriesRead = 0
8105 $TotalRead = 0
8106 $ResumeHandle = 0
8107
8108 # get session information
8109 $Result = $Netapi32::NetSessionEnum($Computer, '', $UserName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
8110
8111 # Locate the offset of the initial intPtr
8112 $Offset = $PtrInfo.ToInt64()
8113
8114 # 0 = success
8115 if (($Result -eq 0) -and ($Offset -gt 0)) {
8116
8117 # Work out how much to increment the pointer by finding out the size of the structure
8118 $Increment = $SESSION_INFO_10::GetSize()
8119
8120 # parse all the result structures
8121 for ($i = 0; ($i -lt $EntriesRead); $i++) {
8122 # create a new int ptr at the given offset and cast the pointer as our result structure
8123 $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
8124 $Info = $NewIntPtr -as $SESSION_INFO_10
8125
8126 # return all the sections of the structure
8127 $Sessions = $Info | Select-Object *
8128 $Sessions | Add-Member Noteproperty 'ComputerName' $Computer
8129 $Offset = $NewIntPtr.ToInt64()
8130 $Offset += $Increment
8131 $Sessions
8132 }
8133 # free up the result buffer
8134 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
8135 }
8136 else {
8137 Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)"
8138 }
8139}
8140
8141
8142filter Get-LoggedOnLocal {
8143<#
8144 .SYNOPSIS
8145
8146 This function will query the HKU registry values to retrieve the local
8147 logged on users SID and then attempt and reverse it.
8148 Adapted technique from Sysinternal's PSLoggedOn script. Benefit over
8149 using the NetWkstaUserEnum API (Get-NetLoggedon) of less user privileges
8150 required (NetWkstaUserEnum requires remote admin access).
8151
8152 Note: This function requires only domain user rights on the
8153 machine you're enumerating, but remote registry must be enabled.
8154
8155 Function: Get-LoggedOnLocal
8156 Author: Matt Kelly, @BreakersAll
8157
8158 .PARAMETER ComputerName
8159
8160 The ComputerName to query for active sessions.
8161
8162 .EXAMPLE
8163
8164 PS C:\> Get-LoggedOnLocal
8165
8166 Returns active sessions on the local host.
8167
8168 .EXAMPLE
8169
8170 PS C:\> Get-LoggedOnLocal -ComputerName sqlserver
8171
8172 Returns active sessions on the 'sqlserver' host.
8173
8174#>
8175
8176 [CmdletBinding()]
8177 param(
8178 [Parameter(ValueFromPipeline=$True)]
8179 [Alias('HostName')]
8180 [Object[]]
8181 [ValidateNotNullOrEmpty()]
8182 $ComputerName = 'localhost'
8183 )
8184
8185 # process multiple host object types from the pipeline
8186 $ComputerName = Get-NameField -Object $ComputerName
8187
8188 try {
8189 # retrieve HKU remote registry values
8190 $Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('Users', "$ComputerName")
8191
8192 # sort out bogus sid's like _class
8193 $Reg.GetSubKeyNames() | Where-Object { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' } | ForEach-Object {
8194 $UserName = Convert-SidToName $_
8195
8196 $Parts = $UserName.Split('\')
8197 $UserDomain = $Null
8198 $UserName = $Parts[-1]
8199 if ($Parts.Length -eq 2) {
8200 $UserDomain = $Parts[0]
8201 }
8202
8203 $LocalLoggedOnUser = New-Object PSObject
8204 $LocalLoggedOnUser | Add-Member Noteproperty 'ComputerName' "$ComputerName"
8205 $LocalLoggedOnUser | Add-Member Noteproperty 'UserDomain' $UserDomain
8206 $LocalLoggedOnUser | Add-Member Noteproperty 'UserName' $UserName
8207 $LocalLoggedOnUser | Add-Member Noteproperty 'UserSID' $_
8208 $LocalLoggedOnUser
8209 }
8210 }
8211 catch {
8212 Write-Verbose "Error opening remote registry on '$ComputerName'"
8213 }
8214}
8215
8216
8217filter Get-NetRDPSession {
8218<#
8219 .SYNOPSIS
8220
8221 This function will execute the WTSEnumerateSessionsEx and
8222 WTSQuerySessionInformation Win32API calls to query a given
8223 RDP remote service for active sessions and originating IPs.
8224 This is a replacement for qwinsta.
8225
8226 Note: only members of the Administrators or Account Operators local group
8227 can successfully execute this functionality on a remote target.
8228
8229 .PARAMETER ComputerName
8230
8231 The hostname to query for active RDP sessions.
8232
8233 .EXAMPLE
8234
8235 PS C:\> Get-NetRDPSession
8236
8237 Returns active RDP/terminal sessions on the local host.
8238
8239 .EXAMPLE
8240
8241 PS C:\> Get-NetRDPSession -ComputerName "sqlserver"
8242
8243 Returns active RDP/terminal sessions on the 'sqlserver' host.
8244
8245 .EXAMPLE
8246
8247 PS C:\> Get-NetDomainController | Get-NetRDPSession
8248
8249 Returns active RDP/terminal sessions on all domain controllers.
8250#>
8251
8252 [CmdletBinding()]
8253 param(
8254 [Parameter(ValueFromPipeline=$True)]
8255 [Alias('HostName')]
8256 [Object[]]
8257 [ValidateNotNullOrEmpty()]
8258 $ComputerName = 'localhost'
8259 )
8260
8261 # extract the computer name from whatever object was passed on the pipeline
8262 $Computer = $ComputerName | Get-NameField
8263
8264 # open up a handle to the Remote Desktop Session host
8265 $Handle = $Wtsapi32::WTSOpenServerEx($Computer)
8266
8267 # if we get a non-zero handle back, everything was successful
8268 if ($Handle -ne 0) {
8269
8270 # arguments for WTSEnumerateSessionsEx
8271 $ppSessionInfo = [IntPtr]::Zero
8272 $pCount = 0
8273
8274 # get information on all current sessions
8275 $Result = $Wtsapi32::WTSEnumerateSessionsEx($Handle, [ref]1, 0, [ref]$ppSessionInfo, [ref]$pCount);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
8276
8277 # Locate the offset of the initial intPtr
8278 $Offset = $ppSessionInfo.ToInt64()
8279
8280 if (($Result -ne 0) -and ($Offset -gt 0)) {
8281
8282 # Work out how much to increment the pointer by finding out the size of the structure
8283 $Increment = $WTS_SESSION_INFO_1::GetSize()
8284
8285 # parse all the result structures
8286 for ($i = 0; ($i -lt $pCount); $i++) {
8287
8288 # create a new int ptr at the given offset and cast the pointer as our result structure
8289 $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
8290 $Info = $NewIntPtr -as $WTS_SESSION_INFO_1
8291
8292 $RDPSession = New-Object PSObject
8293
8294 if ($Info.pHostName) {
8295 $RDPSession | Add-Member Noteproperty 'ComputerName' $Info.pHostName
8296 }
8297 else {
8298 # if no hostname returned, use the specified hostname
8299 $RDPSession | Add-Member Noteproperty 'ComputerName' $Computer
8300 }
8301
8302 $RDPSession | Add-Member Noteproperty 'SessionName' $Info.pSessionName
8303
8304 if ($(-not $Info.pDomainName) -or ($Info.pDomainName -eq '')) {
8305 # if a domain isn't returned just use the username
8306 $RDPSession | Add-Member Noteproperty 'UserName' "$($Info.pUserName)"
8307 }
8308 else {
8309 $RDPSession | Add-Member Noteproperty 'UserName' "$($Info.pDomainName)\$($Info.pUserName)"
8310 }
8311
8312 $RDPSession | Add-Member Noteproperty 'ID' $Info.SessionID
8313 $RDPSession | Add-Member Noteproperty 'State' $Info.State
8314
8315 $ppBuffer = [IntPtr]::Zero
8316 $pBytesReturned = 0
8317
8318 # query for the source client IP with WTSQuerySessionInformation
8319 # https://msdn.microsoft.com/en-us/library/aa383861(v=vs.85).aspx
8320 $Result2 = $Wtsapi32::WTSQuerySessionInformation($Handle, $Info.SessionID, 14, [ref]$ppBuffer, [ref]$pBytesReturned);$LastError2 = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
8321
8322 if($Result -eq 0) {
8323 Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError2).Message)"
8324 }
8325 else {
8326 $Offset2 = $ppBuffer.ToInt64()
8327 $NewIntPtr2 = New-Object System.Intptr -ArgumentList $Offset2
8328 $Info2 = $NewIntPtr2 -as $WTS_CLIENT_ADDRESS
8329
8330 $SourceIP = $Info2.Address
8331 if($SourceIP[2] -ne 0) {
8332 $SourceIP = [String]$SourceIP[2]+"."+[String]$SourceIP[3]+"."+[String]$SourceIP[4]+"."+[String]$SourceIP[5]
8333 }
8334 else {
8335 $SourceIP = $Null
8336 }
8337
8338 $RDPSession | Add-Member Noteproperty 'SourceIP' $SourceIP
8339 $RDPSession
8340
8341 # free up the memory buffer
8342 $Null = $Wtsapi32::WTSFreeMemory($ppBuffer)
8343
8344 $Offset += $Increment
8345 }
8346 }
8347 # free up the memory result buffer
8348 $Null = $Wtsapi32::WTSFreeMemoryEx(2, $ppSessionInfo, $pCount)
8349 }
8350 else {
8351 Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
8352 }
8353 # Close off the service handle
8354 $Null = $Wtsapi32::WTSCloseServer($Handle)
8355 }
8356 else {
8357 Write-Verbose "Error opening the Remote Desktop Session Host (RD Session Host) server for: $ComputerName"
8358 }
8359}
8360
8361
8362filter Invoke-CheckLocalAdminAccess {
8363<#
8364 .SYNOPSIS
8365
8366 This function will use the OpenSCManagerW Win32API call to establish
8367 a handle to the remote host. If this succeeds, the current user context
8368 has local administrator acess to the target.
8369
8370 Idea stolen from the local_admin_search_enum post module in Metasploit written by:
8371 'Brandon McCann "zeknox" <bmccann[at]accuvant.com>'
8372 'Thomas McCarthy "smilingraccoon" <smilingraccoon[at]gmail.com>'
8373 'Royce Davis "r3dy" <rdavis[at]accuvant.com>'
8374
8375 .PARAMETER ComputerName
8376
8377 The hostname to query for active sessions.
8378
8379 .OUTPUTS
8380
8381 $True if the current user has local admin access to the hostname, $False otherwise
8382
8383 .EXAMPLE
8384
8385 PS C:\> Invoke-CheckLocalAdminAccess -ComputerName sqlserver
8386
8387 Returns active sessions on the local host.
8388
8389 .EXAMPLE
8390
8391 PS C:\> Get-NetComputer | Invoke-CheckLocalAdminAccess
8392
8393 Sees what machines in the domain the current user has access to.
8394
8395 .LINK
8396
8397 https://github.com/rapid7/metasploit-framework/blob/master/modules/post/windows/gather/local_admin_search_enum.rb
8398 http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
8399#>
8400
8401 [CmdletBinding()]
8402 param(
8403 [Parameter(ValueFromPipeline=$True)]
8404 [Alias('HostName')]
8405 [Object[]]
8406 [ValidateNotNullOrEmpty()]
8407 $ComputerName = 'localhost'
8408 )
8409
8410 # extract the computer name from whatever object was passed on the pipeline
8411 $Computer = $ComputerName | Get-NameField
8412
8413 # 0xF003F - SC_MANAGER_ALL_ACCESS
8414 # http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx
8415 $Handle = $Advapi32::OpenSCManagerW("\\$Computer", 'ServicesActive', 0xF003F);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
8416
8417 Write-Verbose "Invoke-CheckLocalAdminAccess handle: $Handle"
8418
8419 $IsAdmin = New-Object PSObject
8420 $IsAdmin | Add-Member Noteproperty 'ComputerName' $Computer
8421
8422 # if we get a non-zero handle back, everything was successful
8423 if ($Handle -ne 0) {
8424 $Null = $Advapi32::CloseServiceHandle($Handle)
8425 $IsAdmin | Add-Member Noteproperty 'IsAdmin' $True
8426 }
8427 else {
8428 Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
8429 $IsAdmin | Add-Member Noteproperty 'IsAdmin' $False
8430 }
8431
8432 $IsAdmin
8433}
8434
8435
8436filter Get-SiteName {
8437<#
8438 .SYNOPSIS
8439
8440 This function will use the DsGetSiteName Win32API call to look up the
8441 name of the site where a specified computer resides.
8442
8443 .PARAMETER ComputerName
8444
8445 The hostname to look the site up for, default to localhost.
8446
8447 .EXAMPLE
8448
8449 PS C:\> Get-SiteName -ComputerName WINDOWS1
8450
8451 Returns the site for WINDOWS1.testlab.local.
8452
8453 .EXAMPLE
8454
8455 PS C:\> Get-NetComputer | Get-SiteName
8456
8457 Returns the sites for every machine in AD.
8458#>
8459 [CmdletBinding()]
8460 param(
8461 [Parameter(ValueFromPipeline=$True)]
8462 [Alias('HostName')]
8463 [Object[]]
8464 [ValidateNotNullOrEmpty()]
8465 $ComputerName = $Env:ComputerName
8466 )
8467
8468 # extract the computer name from whatever object was passed on the pipeline
8469 $Computer = $ComputerName | Get-NameField
8470
8471 # if we get an IP address, try to resolve the IP to a hostname
8472 if($Computer -match '^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$') {
8473 $IPAddress = $Computer
8474 $Computer = [System.Net.Dns]::GetHostByAddress($Computer)
8475 }
8476 else {
8477 $IPAddress = @(Get-IPAddress -ComputerName $Computer)[0].IPAddress
8478 }
8479
8480 $PtrInfo = [IntPtr]::Zero
8481
8482 $Result = $Netapi32::DsGetSiteName($Computer, [ref]$PtrInfo)
8483
8484 $ComputerSite = New-Object PSObject
8485 $ComputerSite | Add-Member Noteproperty 'ComputerName' $Computer
8486 $ComputerSite | Add-Member Noteproperty 'IPAddress' $IPAddress
8487
8488 if ($Result -eq 0) {
8489 $Sitename = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($PtrInfo)
8490 $ComputerSite | Add-Member Noteproperty 'SiteName' $Sitename
8491 }
8492 else {
8493 $ErrorMessage = "Error: $(([ComponentModel.Win32Exception] $Result).Message)"
8494 $ComputerSite | Add-Member Noteproperty 'SiteName' $ErrorMessage
8495 }
8496
8497 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
8498
8499 $ComputerSite
8500}
8501
8502
8503filter Get-LastLoggedOn {
8504<#
8505 .SYNOPSIS
8506
8507 This function uses remote registry functionality to return
8508 the last user logged onto a target machine.
8509
8510 Note: This function requires administrative rights on the
8511 machine you're enumerating.
8512
8513 .PARAMETER ComputerName
8514
8515 The hostname to query for the last logged on user.
8516 Defaults to the localhost.
8517
8518 .PARAMETER Credential
8519
8520 A [Management.Automation.PSCredential] object for the remote connection.
8521
8522 .EXAMPLE
8523
8524 PS C:\> Get-LastLoggedOn
8525
8526 Returns the last user logged onto the local machine.
8527
8528 .EXAMPLE
8529
8530 PS C:\> Get-LastLoggedOn -ComputerName WINDOWS1
8531
8532 Returns the last user logged onto WINDOWS1
8533
8534 .EXAMPLE
8535
8536 PS C:\> Get-NetComputer | Get-LastLoggedOn
8537
8538 Returns the last user logged onto all machines in the domain.
8539#>
8540
8541 [CmdletBinding()]
8542 param(
8543 [Parameter(ValueFromPipeline=$True)]
8544 [Alias('HostName')]
8545 [Object[]]
8546 [ValidateNotNullOrEmpty()]
8547 $ComputerName = 'localhost',
8548
8549 [Management.Automation.PSCredential]
8550 $Credential
8551 )
8552
8553 # extract the computer name from whatever object was passed on the pipeline
8554 $Computer = $ComputerName | Get-NameField
8555
8556 # HKEY_LOCAL_MACHINE
8557 $HKLM = 2147483650
8558
8559 # try to open up the remote registry key to grab the last logged on user
8560 try {
8561
8562 if($Credential) {
8563 $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -Credential $Credential -ErrorAction SilentlyContinue
8564 }
8565 else {
8566 $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -ErrorAction SilentlyContinue
8567 }
8568
8569 $Key = "SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI"
8570 $Value = "LastLoggedOnUser"
8571 $LastUser = $Reg.GetStringValue($HKLM, $Key, $Value).sValue
8572
8573 $LastLoggedOn = New-Object PSObject
8574 $LastLoggedOn | Add-Member Noteproperty 'ComputerName' $Computer
8575 $LastLoggedOn | Add-Member Noteproperty 'LastLoggedOn' $LastUser
8576 $LastLoggedOn
8577 }
8578 catch {
8579 Write-Warning "[!] Error opening remote registry on $Computer. Remote registry likely not enabled."
8580 }
8581}
8582
8583
8584filter Get-CachedRDPConnection {
8585<#
8586 .SYNOPSIS
8587
8588 Uses remote registry functionality to query all entries for the
8589 "Windows Remote Desktop Connection Client" on a machine, separated by
8590 user and target server.
8591
8592 Note: This function requires administrative rights on the
8593 machine you're enumerating.
8594
8595 .PARAMETER ComputerName
8596
8597 The hostname to query for RDP client information.
8598 Defaults to localhost.
8599
8600 .PARAMETER Credential
8601
8602 A [Management.Automation.PSCredential] object for the remote connection.
8603
8604 .EXAMPLE
8605
8606 PS C:\> Get-CachedRDPConnection
8607
8608 Returns the RDP connection client information for the local machine.
8609
8610 .EXAMPLE
8611
8612 PS C:\> Get-CachedRDPConnection -ComputerName WINDOWS2.testlab.local
8613
8614 Returns the RDP connection client information for the WINDOWS2.testlab.local machine
8615
8616 .EXAMPLE
8617
8618 PS C:\> Get-CachedRDPConnection -ComputerName WINDOWS2.testlab.local -Credential $Cred
8619
8620 Returns the RDP connection client information for the WINDOWS2.testlab.local machine using alternate credentials.
8621
8622 .EXAMPLE
8623
8624 PS C:\> Get-NetComputer | Get-CachedRDPConnection
8625
8626 Get cached RDP information for all machines in the domain.
8627#>
8628
8629 [CmdletBinding()]
8630 param(
8631 [Parameter(ValueFromPipeline=$True)]
8632 [Alias('HostName')]
8633 [Object[]]
8634 [ValidateNotNullOrEmpty()]
8635 $ComputerName = 'localhost',
8636
8637 [Management.Automation.PSCredential]
8638 $Credential
8639 )
8640
8641 # extract the computer name from whatever object was passed on the pipeline
8642 $Computer = $ComputerName | Get-NameField
8643
8644 # HKEY_USERS
8645 $HKU = 2147483651
8646
8647 try {
8648 if($Credential) {
8649 $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -Credential $Credential -ErrorAction SilentlyContinue
8650 }
8651 else {
8652 $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -ErrorAction SilentlyContinue
8653 }
8654
8655 # extract out the SIDs of domain users in this hive
8656 $UserSIDs = ($Reg.EnumKey($HKU, "")).sNames | ? { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' }
8657
8658 foreach ($UserSID in $UserSIDs) {
8659
8660 try {
8661 $UserName = Convert-SidToName $UserSID
8662
8663 # pull out all the cached RDP connections
8664 $ConnectionKeys = $Reg.EnumValues($HKU,"$UserSID\Software\Microsoft\Terminal Server Client\Default").sNames
8665
8666 foreach ($Connection in $ConnectionKeys) {
8667 # make sure this key is a cached connection
8668 if($Connection -match 'MRU.*') {
8669 $TargetServer = $Reg.GetStringValue($HKU, "$UserSID\Software\Microsoft\Terminal Server Client\Default", $Connection).sValue
8670
8671 $FoundConnection = New-Object PSObject
8672 $FoundConnection | Add-Member Noteproperty 'ComputerName' $Computer
8673 $FoundConnection | Add-Member Noteproperty 'UserName' $UserName
8674 $FoundConnection | Add-Member Noteproperty 'UserSID' $UserSID
8675 $FoundConnection | Add-Member Noteproperty 'TargetServer' $TargetServer
8676 $FoundConnection | Add-Member Noteproperty 'UsernameHint' $Null
8677 $FoundConnection
8678 }
8679 }
8680
8681 # pull out all the cached server info with username hints
8682 $ServerKeys = $Reg.EnumKey($HKU,"$UserSID\Software\Microsoft\Terminal Server Client\Servers").sNames
8683
8684 foreach ($Server in $ServerKeys) {
8685
8686 $UsernameHint = $Reg.GetStringValue($HKU, "$UserSID\Software\Microsoft\Terminal Server Client\Servers\$Server", 'UsernameHint').sValue
8687
8688 $FoundConnection = New-Object PSObject
8689 $FoundConnection | Add-Member Noteproperty 'ComputerName' $Computer
8690 $FoundConnection | Add-Member Noteproperty 'UserName' $UserName
8691 $FoundConnection | Add-Member Noteproperty 'UserSID' $UserSID
8692 $FoundConnection | Add-Member Noteproperty 'TargetServer' $Server
8693 $FoundConnection | Add-Member Noteproperty 'UsernameHint' $UsernameHint
8694 $FoundConnection
8695 }
8696
8697 }
8698 catch {
8699 Write-Verbose "Error: $_"
8700 }
8701 }
8702
8703 }
8704 catch {
8705 Write-Warning "Error accessing $Computer, likely insufficient permissions or firewall rules on host: $_"
8706 }
8707}
8708
8709
8710filter Get-RegistryMountedDrive {
8711<#
8712 .SYNOPSIS
8713
8714 Uses remote registry functionality to query all entries for the
8715 the saved network mounted drive on a machine, separated by
8716 user and target server.
8717
8718 Note: This function requires administrative rights on the
8719 machine you're enumerating.
8720
8721 .PARAMETER ComputerName
8722
8723 The hostname to query for RDP client information.
8724 Defaults to localhost.
8725
8726 .PARAMETER Credential
8727
8728 A [Management.Automation.PSCredential] object for the remote connection.
8729
8730 .EXAMPLE
8731
8732 PS C:\> Get-RegistryMountedDrive
8733
8734 Returns the saved network mounted drives for the local machine.
8735
8736 .EXAMPLE
8737
8738 PS C:\> Get-RegistryMountedDrive -ComputerName WINDOWS2.testlab.local
8739
8740 Returns the saved network mounted drives for the WINDOWS2.testlab.local machine
8741
8742 .EXAMPLE
8743
8744 PS C:\> Get-RegistryMountedDrive -ComputerName WINDOWS2.testlab.local -Credential $Cred
8745
8746 Returns the saved network mounted drives for the WINDOWS2.testlab.local machine using alternate credentials.
8747
8748 .EXAMPLE
8749
8750 PS C:\> Get-NetComputer | Get-RegistryMountedDrive
8751
8752 Get the saved network mounted drives for all machines in the domain.
8753#>
8754
8755 [CmdletBinding()]
8756 param(
8757 [Parameter(ValueFromPipeline=$True)]
8758 [Alias('HostName')]
8759 [Object[]]
8760 [ValidateNotNullOrEmpty()]
8761 $ComputerName = 'localhost',
8762
8763 [Management.Automation.PSCredential]
8764 $Credential
8765 )
8766
8767 # extract the computer name from whatever object was passed on the pipeline
8768 $Computer = $ComputerName | Get-NameField
8769
8770 # HKEY_USERS
8771 $HKU = 2147483651
8772
8773 try {
8774 if($Credential) {
8775 $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -Credential $Credential -ErrorAction SilentlyContinue
8776 }
8777 else {
8778 $Reg = Get-WmiObject -List 'StdRegProv' -Namespace root\default -Computername $Computer -ErrorAction SilentlyContinue
8779 }
8780
8781 # extract out the SIDs of domain users in this hive
8782 $UserSIDs = ($Reg.EnumKey($HKU, "")).sNames | ? { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' }
8783
8784 foreach ($UserSID in $UserSIDs) {
8785
8786 try {
8787 $UserName = Convert-SidToName $UserSID
8788
8789 $DriveLetters = ($Reg.EnumKey($HKU, "$UserSID\Network")).sNames
8790
8791 ForEach($DriveLetter in $DriveLetters) {
8792 $ProviderName = $Reg.GetStringValue($HKU, "$UserSID\Network\$DriveLetter", 'ProviderName').sValue
8793 $RemotePath = $Reg.GetStringValue($HKU, "$UserSID\Network\$DriveLetter", 'RemotePath').sValue
8794 $DriveUserName = $Reg.GetStringValue($HKU, "$UserSID\Network\$DriveLetter", 'UserName').sValue
8795 if(-not $UserName) { $UserName = '' }
8796
8797 if($RemotePath -and ($RemotePath -ne '')) {
8798 $MountedDrive = New-Object PSObject
8799 $MountedDrive | Add-Member Noteproperty 'ComputerName' $Computer
8800 $MountedDrive | Add-Member Noteproperty 'UserName' $UserName
8801 $MountedDrive | Add-Member Noteproperty 'UserSID' $UserSID
8802 $MountedDrive | Add-Member Noteproperty 'DriveLetter' $DriveLetter
8803 $MountedDrive | Add-Member Noteproperty 'ProviderName' $ProviderName
8804 $MountedDrive | Add-Member Noteproperty 'RemotePath' $RemotePath
8805 $MountedDrive | Add-Member Noteproperty 'DriveUserName' $DriveUserName
8806 $MountedDrive
8807 }
8808 }
8809 }
8810 catch {
8811 Write-Verbose "Error: $_"
8812 }
8813 }
8814 }
8815 catch {
8816 Write-Warning "Error accessing $Computer, likely insufficient permissions or firewall rules on host: $_"
8817 }
8818}
8819
8820
8821filter Get-NetProcess {
8822<#
8823 .SYNOPSIS
8824
8825 Gets a list of processes/owners on a remote machine.
8826
8827 .PARAMETER ComputerName
8828
8829 The hostname to query processes. Defaults to the local host name.
8830
8831 .PARAMETER Credential
8832
8833 A [Management.Automation.PSCredential] object for the remote connection.
8834
8835 .EXAMPLE
8836
8837 PS C:\> Get-NetProcess -ComputerName WINDOWS1
8838
8839 Returns the current processes for WINDOWS1
8840#>
8841
8842 [CmdletBinding()]
8843 param(
8844 [Parameter(ValueFromPipeline=$True)]
8845 [Alias('HostName')]
8846 [Object[]]
8847 [ValidateNotNullOrEmpty()]
8848 $ComputerName = [System.Net.Dns]::GetHostName(),
8849
8850 [Management.Automation.PSCredential]
8851 $Credential
8852 )
8853
8854 # extract the computer name from whatever object was passed on the pipeline
8855 $Computer = $ComputerName | Get-NameField
8856
8857 try {
8858 if($Credential) {
8859 $Processes = Get-WMIobject -Class Win32_process -ComputerName $ComputerName -Credential $Credential
8860 }
8861 else {
8862 $Processes = Get-WMIobject -Class Win32_process -ComputerName $ComputerName
8863 }
8864
8865 $Processes | ForEach-Object {
8866 $Owner = $_.getowner();
8867 $Process = New-Object PSObject
8868 $Process | Add-Member Noteproperty 'ComputerName' $Computer
8869 $Process | Add-Member Noteproperty 'ProcessName' $_.ProcessName
8870 $Process | Add-Member Noteproperty 'ProcessID' $_.ProcessID
8871 $Process | Add-Member Noteproperty 'Domain' $Owner.Domain
8872 $Process | Add-Member Noteproperty 'User' $Owner.User
8873 $Process
8874 }
8875 }
8876 catch {
8877 Write-Verbose "[!] Error enumerating remote processes on $Computer, access likely denied: $_"
8878 }
8879}
8880
8881
8882function Find-InterestingFile {
8883<#
8884 .SYNOPSIS
8885
8886 This function recursively searches a given UNC path for files with
8887 specific keywords in the name (default of pass, sensitive, secret, admin,
8888 login and unattend*.xml). The output can be piped out to a csv with the
8889 -OutFile flag. By default, hidden files/folders are included in search results.
8890
8891 .PARAMETER Path
8892
8893 UNC/local path to recursively search.
8894
8895 .PARAMETER Terms
8896
8897 Terms to search for.
8898
8899 .PARAMETER OfficeDocs
8900
8901 Switch. Search for office documents (*.doc*, *.xls*, *.ppt*)
8902
8903 .PARAMETER FreshEXEs
8904
8905 Switch. Find .EXEs accessed within the last week.
8906
8907 .PARAMETER LastAccessTime
8908
8909 Only return files with a LastAccessTime greater than this date value.
8910
8911 .PARAMETER LastWriteTime
8912
8913 Only return files with a LastWriteTime greater than this date value.
8914
8915 .PARAMETER CreationTime
8916
8917 Only return files with a CreationTime greater than this date value.
8918
8919 .PARAMETER ExcludeFolders
8920
8921 Switch. Exclude folders from the search results.
8922
8923 .PARAMETER ExcludeHidden
8924
8925 Switch. Exclude hidden files and folders from the search results.
8926
8927 .PARAMETER CheckWriteAccess
8928
8929 Switch. Only returns files the current user has write access to.
8930
8931 .PARAMETER OutFile
8932
8933 Output results to a specified csv output file.
8934
8935 .PARAMETER UsePSDrive
8936
8937 Switch. Mount target remote path with temporary PSDrives.
8938
8939 .OUTPUTS
8940
8941 The full path, owner, lastaccess time, lastwrite time, and size for each found file.
8942
8943 .EXAMPLE
8944
8945 PS C:\> Find-InterestingFile -Path C:\Backup\
8946
8947 Returns any files on the local path C:\Backup\ that have the default
8948 search term set in the title.
8949
8950 .EXAMPLE
8951
8952 PS C:\> Find-InterestingFile -Path \\WINDOWS7\Users\ -Terms salaries,email -OutFile out.csv
8953
8954 Returns any files on the remote path \\WINDOWS7\Users\ that have 'salaries'
8955 or 'email' in the title, and writes the results out to a csv file
8956 named 'out.csv'
8957
8958 .EXAMPLE
8959
8960 PS C:\> Find-InterestingFile -Path \\WINDOWS7\Users\ -LastAccessTime (Get-Date).AddDays(-7)
8961
8962 Returns any files on the remote path \\WINDOWS7\Users\ that have the default
8963 search term set in the title and were accessed within the last week.
8964
8965 .LINK
8966
8967 http://www.harmj0y.net/blog/redteaming/file-server-triage-on-red-team-engagements/
8968#>
8969
8970 param(
8971 [Parameter(ValueFromPipeline=$True)]
8972 [String]
8973 $Path = '.\',
8974
8975 [Alias('Terms')]
8976 [String[]]
8977 $SearchTerms = @('pass', 'sensitive', 'admin', 'login', 'secret', 'unattend*.xml', '.vmdk', 'creds', 'credential', '.config'),
8978
8979 [Switch]
8980 $OfficeDocs,
8981
8982 [Switch]
8983 $FreshEXEs,
8984
8985 [String]
8986 $LastAccessTime,
8987
8988 [String]
8989 $LastWriteTime,
8990
8991 [String]
8992 $CreationTime,
8993
8994 [Switch]
8995 $ExcludeFolders,
8996
8997 [Switch]
8998 $ExcludeHidden,
8999
9000 [Switch]
9001 $CheckWriteAccess,
9002
9003 [String]
9004 $OutFile,
9005
9006 [Switch]
9007 $UsePSDrive
9008 )
9009
9010 begin {
9011
9012 $Path += if(!$Path.EndsWith('\')) {"\"}
9013
9014 if ($Credential) {
9015 $UsePSDrive = $True
9016 }
9017
9018 # append wildcards to the front and back of all search terms
9019 $SearchTerms = $SearchTerms | ForEach-Object { if($_ -notmatch '^\*.*\*$') {"*$($_)*"} else{$_} }
9020
9021 # search just for office documents if specified
9022 if ($OfficeDocs) {
9023 $SearchTerms = @('*.doc', '*.docx', '*.xls', '*.xlsx', '*.ppt', '*.pptx')
9024 }
9025
9026 # find .exe's accessed within the last 7 days
9027 if($FreshEXEs) {
9028 # get an access time limit of 7 days ago
9029 $LastAccessTime = (Get-Date).AddDays(-7).ToString('MM/dd/yyyy')
9030 $SearchTerms = '*.exe'
9031 }
9032
9033 if($UsePSDrive) {
9034 # if we're PSDrives, create a temporary mount point
9035
9036 $Parts = $Path.split('\')
9037 $FolderPath = $Parts[0..($Parts.length-2)] -join '\'
9038 $FilePath = $Parts[-1]
9039
9040 $RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join ''
9041
9042 Write-Verbose "Mounting path '$Path' using a temp PSDrive at $RandDrive"
9043
9044 try {
9045 $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop
9046 }
9047 catch {
9048 Write-Verbose "Error mounting path '$Path' : $_"
9049 return $Null
9050 }
9051
9052 # so we can cd/dir the new drive
9053 $Path = "${RandDrive}:\${FilePath}"
9054 }
9055 }
9056
9057 process {
9058
9059 Write-Verbose "[*] Search path $Path"
9060
9061 function Invoke-CheckWrite {
9062 # short helper to check is the current user can write to a file
9063 [CmdletBinding()]param([String]$Path)
9064 try {
9065 $Filetest = [IO.FILE]::OpenWrite($Path)
9066 $Filetest.Close()
9067 $True
9068 }
9069 catch {
9070 Write-Verbose -Message $Error[0]
9071 $False
9072 }
9073 }
9074
9075 $SearchArgs = @{
9076 'Path' = $Path
9077 'Recurse' = $True
9078 'Force' = $(-not $ExcludeHidden)
9079 'Include' = $SearchTerms
9080 'ErrorAction' = 'SilentlyContinue'
9081 }
9082
9083 Get-ChildItem @SearchArgs | ForEach-Object {
9084 Write-Verbose $_
9085 # check if we're excluding folders
9086 if(!$ExcludeFolders -or !$_.PSIsContainer) {$_}
9087 } | ForEach-Object {
9088 if($LastAccessTime -or $LastWriteTime -or $CreationTime) {
9089 if($LastAccessTime -and ($_.LastAccessTime -gt $LastAccessTime)) {$_}
9090 elseif($LastWriteTime -and ($_.LastWriteTime -gt $LastWriteTime)) {$_}
9091 elseif($CreationTime -and ($_.CreationTime -gt $CreationTime)) {$_}
9092 }
9093 else {$_}
9094 } | ForEach-Object {
9095 # filter for write access (if applicable)
9096 if((-not $CheckWriteAccess) -or (Invoke-CheckWrite -Path $_.FullName)) {$_}
9097 } | Select-Object FullName,@{Name='Owner';Expression={(Get-Acl $_.FullName).Owner}},LastAccessTime,LastWriteTime,CreationTime,Length | ForEach-Object {
9098 # check if we're outputting to the pipeline or an output file
9099 if($OutFile) {Export-PowerViewCSV -InputObject $_ -OutFile $OutFile}
9100 else {$_}
9101 }
9102 }
9103
9104 end {
9105 if($UsePSDrive -and $RandDrive) {
9106 Write-Verbose "Removing temp PSDrive $RandDrive"
9107 Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive -Force
9108 }
9109 }
9110}
9111
9112
9113########################################################
9114#
9115# 'Meta'-functions start below
9116#
9117########################################################
9118
9119function Invoke-ThreadedFunction {
9120 # Helper used by any threaded host enumeration functions
9121 [CmdletBinding()]
9122 param(
9123 [Parameter(Position=0,Mandatory=$True)]
9124 [String[]]
9125 $ComputerName,
9126
9127 [Parameter(Position=1,Mandatory=$True)]
9128 [System.Management.Automation.ScriptBlock]
9129 $ScriptBlock,
9130
9131 [Parameter(Position=2)]
9132 [Hashtable]
9133 $ScriptParameters,
9134
9135 [Int]
9136 [ValidateRange(1,100)]
9137 $Threads = 20,
9138
9139 [Switch]
9140 $NoImports
9141 )
9142
9143 begin {
9144
9145 if ($PSBoundParameters['Debug']) {
9146 $DebugPreference = 'Continue'
9147 }
9148
9149 Write-Verbose "[*] Total number of hosts: $($ComputerName.count)"
9150
9151 # Adapted from:
9152 # http://powershell.org/wp/forums/topic/invpke-parallel-need-help-to-clone-the-current-runspace/
9153 $SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
9154 $SessionState.ApartmentState = [System.Threading.Thread]::CurrentThread.GetApartmentState()
9155
9156 # import the current session state's variables and functions so the chained PowerView
9157 # functionality can be used by the threaded blocks
9158 if(!$NoImports) {
9159
9160 # grab all the current variables for this runspace
9161 $MyVars = Get-Variable -Scope 2
9162
9163 # these Variables are added by Runspace.Open() Method and produce Stop errors if you add them twice
9164 $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")
9165
9166 # Add Variables from Parent Scope (current runspace) into the InitialSessionState
9167 ForEach($Var in $MyVars) {
9168 if($VorbiddenVars -NotContains $Var.Name) {
9169 $SessionState.Variables.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Var.name,$Var.Value,$Var.description,$Var.options,$Var.attributes))
9170 }
9171 }
9172
9173 # Add Functions from current runspace to the InitialSessionState
9174 ForEach($Function in (Get-ChildItem Function:)) {
9175 $SessionState.Commands.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $Function.Name, $Function.Definition))
9176 }
9177 }
9178
9179 # threading adapted from
9180 # https://github.com/darkoperator/Posh-SecMod/blob/master/Discovery/Discovery.psm1#L407
9181 # Thanks Carlos!
9182
9183 # create a pool of maxThread runspaces
9184 $Pool = [runspacefactory]::CreateRunspacePool(1, $Threads, $SessionState, $Host)
9185 $Pool.Open()
9186
9187 $method = $null
9188 ForEach ($m in [PowerShell].GetMethods() | Where-Object { $_.Name -eq "BeginInvoke" }) {
9189 $methodParameters = $m.GetParameters()
9190 if (($methodParameters.Count -eq 2) -and $methodParameters[0].Name -eq "input" -and $methodParameters[1].Name -eq "output") {
9191 $method = $m.MakeGenericMethod([Object], [Object])
9192 break
9193 }
9194 }
9195
9196 $Jobs = @()
9197 }
9198
9199 process {
9200
9201 ForEach ($Computer in $ComputerName) {
9202
9203 # make sure we get a server name
9204 if ($Computer -ne '') {
9205 # Write-Verbose "[*] Enumerating server $Computer ($($Counter+1) of $($ComputerName.count))"
9206
9207 While ($($Pool.GetAvailableRunspaces()) -le 0) {
9208 Start-Sleep -MilliSeconds 500
9209 }
9210
9211 # create a "powershell pipeline runner"
9212 $p = [powershell]::create()
9213
9214 $p.runspacepool = $Pool
9215
9216 # add the script block + arguments
9217 $Null = $p.AddScript($ScriptBlock).AddParameter('ComputerName', $Computer)
9218 if($ScriptParameters) {
9219 ForEach ($Param in $ScriptParameters.GetEnumerator()) {
9220 $Null = $p.AddParameter($Param.Name, $Param.Value)
9221 }
9222 }
9223
9224 $o = New-Object Management.Automation.PSDataCollection[Object]
9225
9226 $Jobs += @{
9227 PS = $p
9228 Output = $o
9229 Result = $method.Invoke($p, @($null, [Management.Automation.PSDataCollection[Object]]$o))
9230 }
9231 }
9232 }
9233 }
9234
9235 end {
9236 Write-Verbose "Waiting for threads to finish..."
9237
9238 Do {
9239 ForEach ($Job in $Jobs) {
9240 $Job.Output.ReadAll()
9241 }
9242 } While (($Jobs | Where-Object { ! $_.Result.IsCompleted }).Count -gt 0)
9243
9244 ForEach ($Job in $Jobs) {
9245 $Job.PS.Dispose()
9246 }
9247
9248 $Pool.Dispose()
9249 Write-Verbose "All threads completed!"
9250 }
9251}
9252
9253
9254function Invoke-UserHunter {
9255<#
9256 .SYNOPSIS
9257
9258 Finds which machines users of a specified group are logged into.
9259
9260 Author: @harmj0y
9261 License: BSD 3-Clause
9262
9263 .DESCRIPTION
9264
9265 This function finds the local domain name for a host using Get-NetDomain,
9266 queries the domain for users of a specified group (default "domain admins")
9267 with Get-NetGroupMember or reads in a target user list, queries the domain for all
9268 active machines with Get-NetComputer or reads in a pre-populated host list,
9269 randomly shuffles the target list, then for each server it gets a list of
9270 active users with Get-NetSession/Get-NetLoggedon. The found user list is compared
9271 against the target list, and a status message is displayed for any hits.
9272 The flag -CheckAccess will check each positive host to see if the current
9273 user has local admin access to the machine.
9274
9275 .PARAMETER ComputerName
9276
9277 Host array to enumerate, passable on the pipeline.
9278
9279 .PARAMETER ComputerFile
9280
9281 File of hostnames/IPs to search.
9282
9283 .PARAMETER ComputerFilter
9284
9285 Host filter name to query AD for, wildcards accepted.
9286
9287 .PARAMETER ComputerADSpath
9288
9289 The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
9290 Useful for OU queries.
9291
9292 .PARAMETER Unconstrained
9293
9294 Switch. Only enumerate computers that have unconstrained delegation.
9295
9296 .PARAMETER GroupName
9297
9298 Group name to query for target users.
9299
9300 .PARAMETER TargetServer
9301
9302 Hunt for users who are effective local admins on a target server.
9303
9304 .PARAMETER UserName
9305
9306 Specific username to search for.
9307
9308 .PARAMETER UserFilter
9309
9310 A customized ldap filter string to use for user enumeration, e.g. "(description=*admin*)"
9311
9312 .PARAMETER UserADSpath
9313
9314 The LDAP source to search through for users, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
9315 Useful for OU queries.
9316
9317 .PARAMETER UserFile
9318
9319 File of usernames to search for.
9320
9321 .PARAMETER AdminCount
9322
9323 Switch. Hunt for users with adminCount=1.
9324
9325 .PARAMETER AllowDelegation
9326
9327 Switch. Return user accounts that are not marked as 'sensitive and not allowed for delegation'
9328
9329 .PARAMETER StopOnSuccess
9330
9331 Switch. Stop hunting after finding after finding a target user.
9332
9333 .PARAMETER NoPing
9334
9335 Don't ping each host to ensure it's up before enumerating.
9336
9337 .PARAMETER CheckAccess
9338
9339 Switch. Check if the current user has local admin access to found machines.
9340
9341 .PARAMETER Delay
9342
9343 Delay between enumerating hosts, defaults to 0
9344
9345 .PARAMETER Jitter
9346
9347 Jitter for the host delay, defaults to +/- 0.3
9348
9349 .PARAMETER Domain
9350
9351 Domain for query for machines, defaults to the current domain.
9352
9353 .PARAMETER DomainController
9354
9355 Domain controller to reflect LDAP queries through.
9356
9357 .PARAMETER ShowAll
9358
9359 Switch. Return all user location results, i.e. Invoke-UserView functionality.
9360
9361 .PARAMETER SearchForest
9362
9363 Switch. Search all domains in the forest for target users instead of just
9364 a single domain.
9365
9366 .PARAMETER Stealth
9367
9368 Switch. Only enumerate sessions from connonly used target servers.
9369
9370 .PARAMETER StealthSource
9371
9372 The source of target servers to use, 'DFS' (distributed file servers),
9373 'DC' (domain controllers), 'File' (file servers), or 'All'
9374
9375 .PARAMETER ForeignUsers
9376
9377 Switch. Only return results that are not part of searched domain.
9378
9379 .PARAMETER Threads
9380
9381 The maximum concurrent threads to execute.
9382
9383 .PARAMETER Poll
9384
9385 Continuously poll for sessions for the given duration. Automatically
9386 sets Threads to the number of computers being polled.
9387
9388 .EXAMPLE
9389
9390 PS C:\> Invoke-UserHunter -CheckAccess
9391
9392 Finds machines on the local domain where domain admins are logged into
9393 and checks if the current user has local administrator access.
9394
9395 .EXAMPLE
9396
9397 PS C:\> Invoke-UserHunter -Domain 'testing'
9398
9399 Finds machines on the 'testing' domain where domain admins are logged into.
9400
9401 .EXAMPLE
9402
9403 PS C:\> Invoke-UserHunter -Threads 20
9404
9405 Multi-threaded user hunting, replaces Invoke-UserHunterThreaded.
9406
9407 .EXAMPLE
9408
9409 PS C:\> Invoke-UserHunter -UserFile users.txt -ComputerFile hosts.txt
9410
9411 Finds machines in hosts.txt where any members of users.txt are logged in
9412 or have sessions.
9413
9414 .EXAMPLE
9415
9416 PS C:\> Invoke-UserHunter -GroupName "Power Users" -Delay 60
9417
9418 Find machines on the domain where members of the "Power Users" groups are
9419 logged into with a 60 second (+/- *.3) randomized delay between
9420 touching each host.
9421
9422 .EXAMPLE
9423
9424 PS C:\> Invoke-UserHunter -TargetServer FILESERVER
9425
9426 Query FILESERVER for useres who are effective local administrators using
9427 Get-NetLocalGroup -Recurse, and hunt for that user set on the network.
9428
9429 .EXAMPLE
9430
9431 PS C:\> Invoke-UserHunter -SearchForest
9432
9433 Find all machines in the current forest where domain admins are logged in.
9434
9435 .EXAMPLE
9436
9437 PS C:\> Invoke-UserHunter -Stealth
9438
9439 Executes old Invoke-StealthUserHunter functionality, enumerating commonly
9440 used servers and checking just sessions for each.
9441
9442 .EXAMPLE
9443
9444 PS C:\> Invoke-UserHunter -Stealth -StealthSource DC -Poll 3600 -Delay 5 -ShowAll | ? { ! $_.UserName.EndsWith('$') }
9445
9446 Poll Domain Controllers in parallel for sessions for an hour, waiting five
9447 seconds before querying each DC again and filtering out computer accounts.
9448
9449 .LINK
9450 http://blog.harmj0y.net
9451#>
9452
9453 [CmdletBinding()]
9454 param(
9455 [Parameter(Position=0,ValueFromPipeline=$True)]
9456 [Alias('Hosts')]
9457 [String[]]
9458 $ComputerName,
9459
9460 [ValidateScript({Test-Path -Path $_ })]
9461 [Alias('HostList')]
9462 [String]
9463 $ComputerFile,
9464
9465 [String]
9466 $ComputerFilter,
9467
9468 [String]
9469 $ComputerADSpath,
9470
9471 [Switch]
9472 $Unconstrained,
9473
9474 [String]
9475 $GroupName = 'Domain Admins',
9476
9477 [String]
9478 $TargetServer,
9479
9480 [String]
9481 $UserName,
9482
9483 [String]
9484 $UserFilter,
9485
9486 [String]
9487 $UserADSpath,
9488
9489 [ValidateScript({Test-Path -Path $_ })]
9490 [String]
9491 $UserFile,
9492
9493 [Switch]
9494 $AdminCount,
9495
9496 [Switch]
9497 $AllowDelegation,
9498
9499 [Switch]
9500 $CheckAccess,
9501
9502 [Switch]
9503 $StopOnSuccess,
9504
9505 [Switch]
9506 $NoPing,
9507
9508 [UInt32]
9509 $Delay = 0,
9510
9511 [Double]
9512 $Jitter = .3,
9513
9514 [String]
9515 $Domain,
9516
9517 [String]
9518 $DomainController,
9519
9520 [Switch]
9521 $ShowAll,
9522
9523 [Switch]
9524 $SearchForest,
9525
9526 [Switch]
9527 $Stealth,
9528
9529 [String]
9530 [ValidateSet("DFS","DC","File","All")]
9531 $StealthSource ="All",
9532
9533 [Switch]
9534 $ForeignUsers,
9535
9536 [Int]
9537 [ValidateRange(1,100)]
9538 $Threads,
9539
9540 [UInt32]
9541 $Poll = 0
9542 )
9543
9544 begin {
9545
9546 if ($PSBoundParameters['Debug']) {
9547 $DebugPreference = 'Continue'
9548 }
9549
9550 Write-Verbose "[*] Running Invoke-UserHunter with delay of $Delay"
9551
9552 #####################################################
9553 #
9554 # First we build the host target set
9555 #
9556 #####################################################
9557
9558 if($ComputerFile) {
9559 # if we're using a host list, read the targets in and add them to the target list
9560 $ComputerName = Get-Content -Path $ComputerFile
9561 }
9562
9563 if(!$ComputerName) {
9564 [Array]$ComputerName = @()
9565
9566 if($Domain) {
9567 $TargetDomains = @($Domain)
9568 }
9569 elseif($SearchForest) {
9570 # get ALL the domains in the forest to search
9571 $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
9572 }
9573 else {
9574 # use the local domain
9575 $TargetDomains = @( (Get-NetDomain).name )
9576 }
9577
9578 if($Stealth) {
9579 Write-Verbose "Stealth mode! Enumerating commonly used servers"
9580 Write-Verbose "Stealth source: $StealthSource"
9581
9582 ForEach ($Domain in $TargetDomains) {
9583 if (($StealthSource -eq "File") -or ($StealthSource -eq "All")) {
9584 Write-Verbose "[*] Querying domain $Domain for File Servers..."
9585 $ComputerName += Get-NetFileServer -Domain $Domain -DomainController $DomainController
9586 }
9587 if (($StealthSource -eq "DFS") -or ($StealthSource -eq "All")) {
9588 Write-Verbose "[*] Querying domain $Domain for DFS Servers..."
9589 $ComputerName += Get-DFSshare -Domain $Domain -DomainController $DomainController | ForEach-Object {$_.RemoteServerName}
9590 }
9591 if (($StealthSource -eq "DC") -or ($StealthSource -eq "All")) {
9592 Write-Verbose "[*] Querying domain $Domain for Domain Controllers..."
9593 $ComputerName += Get-NetDomainController -LDAP -Domain $Domain -DomainController $DomainController | ForEach-Object { $_.dnshostname}
9594 }
9595 }
9596 }
9597 else {
9598 ForEach ($Domain in $TargetDomains) {
9599 Write-Verbose "[*] Querying domain $Domain for hosts"
9600
9601 $Arguments = @{
9602 'Domain' = $Domain
9603 'DomainController' = $DomainController
9604 'ADSpath' = $ADSpath
9605 'Filter' = $ComputerFilter
9606 'Unconstrained' = $Unconstrained
9607 }
9608
9609 $ComputerName += Get-NetComputer @Arguments
9610 }
9611 }
9612
9613 # remove any null target hosts, uniquify the list and shuffle it
9614 $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
9615 if($($ComputerName.Count) -eq 0) {
9616 throw "No hosts found!"
9617 }
9618 }
9619
9620 if ($Poll -gt 0) {
9621 Write-Verbose "[*] Polling for $Poll seconds. Automatically enabling threaded mode."
9622 if ($ComputerName.Count -gt 100) {
9623 throw "Too many hosts to poll! Try fewer than 100."
9624 }
9625 $Threads = $ComputerName.Count
9626 }
9627
9628 #####################################################
9629 #
9630 # Now we build the user target set
9631 #
9632 #####################################################
9633
9634 # users we're going to be searching for
9635 $TargetUsers = @()
9636
9637 # get the current user so we can ignore it in the results
9638 $CurrentUser = ([Environment]::UserName).toLower()
9639
9640 # if we're showing all results, skip username enumeration
9641 if($ShowAll -or $ForeignUsers) {
9642 $User = New-Object PSObject
9643 $User | Add-Member Noteproperty 'MemberDomain' $Null
9644 $User | Add-Member Noteproperty 'MemberName' '*'
9645 $TargetUsers = @($User)
9646
9647 if($ForeignUsers) {
9648 # if we're searching for user results not in the primary domain
9649 $krbtgtName = Convert-ADName -ObjectName "krbtgt@$($Domain)" -InputType Simple -OutputType NT4
9650 $DomainShortName = $krbtgtName.split("\")[0]
9651 }
9652 }
9653 # if we want to hunt for the effective domain users who can access a target server
9654 elseif($TargetServer) {
9655 Write-Verbose "Querying target server '$TargetServer' for local users"
9656 $TargetUsers = Get-NetLocalGroup $TargetServer -Recurse | Where-Object {(-not $_.IsGroup) -and $_.IsDomain } | ForEach-Object {
9657 $User = New-Object PSObject
9658 $User | Add-Member Noteproperty 'MemberDomain' ($_.AccountName).split("/")[0].toLower()
9659 $User | Add-Member Noteproperty 'MemberName' ($_.AccountName).split("/")[1].toLower()
9660 $User
9661 } | Where-Object {$_}
9662 }
9663 # if we get a specific username, only use that
9664 elseif($UserName) {
9665 Write-Verbose "[*] Using target user '$UserName'..."
9666 $User = New-Object PSObject
9667 if($TargetDomains) {
9668 $User | Add-Member Noteproperty 'MemberDomain' $TargetDomains[0]
9669 }
9670 else {
9671 $User | Add-Member Noteproperty 'MemberDomain' $Null
9672 }
9673 $User | Add-Member Noteproperty 'MemberName' $UserName.ToLower()
9674 $TargetUsers = @($User)
9675 }
9676 # read in a target user list if we have one
9677 elseif($UserFile) {
9678 $TargetUsers = Get-Content -Path $UserFile | ForEach-Object {
9679 $User = New-Object PSObject
9680 if($TargetDomains) {
9681 $User | Add-Member Noteproperty 'MemberDomain' $TargetDomains[0]
9682 }
9683 else {
9684 $User | Add-Member Noteproperty 'MemberDomain' $Null
9685 }
9686 $User | Add-Member Noteproperty 'MemberName' $_
9687 $User
9688 } | Where-Object {$_}
9689 }
9690 elseif($UserADSpath -or $UserFilter -or $AdminCount) {
9691 ForEach ($Domain in $TargetDomains) {
9692
9693 $Arguments = @{
9694 'Domain' = $Domain
9695 'DomainController' = $DomainController
9696 'ADSpath' = $UserADSpath
9697 'Filter' = $UserFilter
9698 'AdminCount' = $AdminCount
9699 'AllowDelegation' = $AllowDelegation
9700 }
9701
9702 Write-Verbose "[*] Querying domain $Domain for users"
9703 $TargetUsers += Get-NetUser @Arguments | ForEach-Object {
9704 $User = New-Object PSObject
9705 $User | Add-Member Noteproperty 'MemberDomain' $Domain
9706 $User | Add-Member Noteproperty 'MemberName' $_.samaccountname
9707 $User
9708 } | Where-Object {$_}
9709
9710 }
9711 }
9712 else {
9713 ForEach ($Domain in $TargetDomains) {
9714 Write-Verbose "[*] Querying domain $Domain for users of group '$GroupName'"
9715 $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController
9716 }
9717 }
9718
9719 if (( (-not $ShowAll) -and (-not $ForeignUsers) ) -and ((!$TargetUsers) -or ($TargetUsers.Count -eq 0))) {
9720 throw "[!] No users found to search for!"
9721 }
9722
9723 # script block that enumerates a server
9724 $HostEnumBlock = {
9725 param($ComputerName, $Ping, $TargetUsers, $CurrentUser, $Stealth, $DomainShortName, $Poll, $Delay, $Jitter)
9726
9727 # optionally check if the server is up first
9728 $Up = $True
9729 if($Ping) {
9730 $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
9731 }
9732 if($Up) {
9733 $Timer = [System.Diagnostics.Stopwatch]::StartNew()
9734 $RandNo = New-Object System.Random
9735
9736 Do {
9737 if(!$DomainShortName) {
9738 # if we're not searching for foreign users, check session information
9739 $Sessions = Get-NetSession -ComputerName $ComputerName
9740 ForEach ($Session in $Sessions) {
9741 $UserName = $Session.sesi10_username
9742 $CName = $Session.sesi10_cname
9743
9744 if($CName -and $CName.StartsWith("\\")) {
9745 $CName = $CName.TrimStart("\")
9746 }
9747
9748 # make sure we have a result
9749 if (($UserName) -and ($UserName.trim() -ne '') -and (!($UserName -match $CurrentUser))) {
9750
9751 $TargetUsers | Where-Object {$UserName -like $_.MemberName} | ForEach-Object {
9752
9753 $IPAddress = @(Get-IPAddress -ComputerName $ComputerName)[0].IPAddress
9754 $FoundUser = New-Object PSObject
9755 $FoundUser | Add-Member Noteproperty 'UserDomain' $_.MemberDomain
9756 $FoundUser | Add-Member Noteproperty 'UserName' $UserName
9757 $FoundUser | Add-Member Noteproperty 'ComputerName' $ComputerName
9758 $FoundUser | Add-Member Noteproperty 'IPAddress' $IPAddress
9759 $FoundUser | Add-Member Noteproperty 'SessionFrom' $CName
9760
9761 # Try to resolve the DNS hostname of $Cname
9762 try {
9763 $CNameDNSName = [System.Net.Dns]::GetHostEntry($CName) | Select-Object -ExpandProperty HostName
9764 $FoundUser | Add-Member NoteProperty 'SessionFromName' $CnameDNSName
9765 }
9766 catch {
9767 $FoundUser | Add-Member NoteProperty 'SessionFromName' $Null
9768 }
9769
9770 # see if we're checking to see if we have local admin access on this machine
9771 if ($CheckAccess) {
9772 $Admin = Invoke-CheckLocalAdminAccess -ComputerName $CName
9773 $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin
9774 }
9775 else {
9776 $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Null
9777 }
9778 $FoundUser.PSObject.TypeNames.Add('PowerView.UserSession')
9779 $FoundUser
9780 }
9781 }
9782 }
9783 }
9784 if(!$Stealth) {
9785 # if we're not 'stealthy', enumerate loggedon users as well
9786 $LoggedOn = Get-NetLoggedon -ComputerName $ComputerName
9787 ForEach ($User in $LoggedOn) {
9788 $UserName = $User.wkui1_username
9789 # TODO: translate domain to authoratative name
9790 # then match domain name ?
9791 $UserDomain = $User.wkui1_logon_domain
9792
9793 # make sure wet have a result
9794 if (($UserName) -and ($UserName.trim() -ne '')) {
9795
9796 $TargetUsers | Where-Object {$UserName -like $_.MemberName} | ForEach-Object {
9797
9798 $Proceed = $True
9799 if($DomainShortName) {
9800 if ($DomainShortName.ToLower() -ne $UserDomain.ToLower()) {
9801 $Proceed = $True
9802 }
9803 else {
9804 $Proceed = $False
9805 }
9806 }
9807 if($Proceed) {
9808 $IPAddress = @(Get-IPAddress -ComputerName $ComputerName)[0].IPAddress
9809 $FoundUser = New-Object PSObject
9810 $FoundUser | Add-Member Noteproperty 'UserDomain' $UserDomain
9811 $FoundUser | Add-Member Noteproperty 'UserName' $UserName
9812 $FoundUser | Add-Member Noteproperty 'ComputerName' $ComputerName
9813 $FoundUser | Add-Member Noteproperty 'IPAddress' $IPAddress
9814 $FoundUser | Add-Member Noteproperty 'SessionFrom' $Null
9815 $FoundUser | Add-Member Noteproperty 'SessionFromName' $Null
9816
9817 # see if we're checking to see if we have local admin access on this machine
9818 if ($CheckAccess) {
9819 $Admin = Invoke-CheckLocalAdminAccess -ComputerName $ComputerName
9820 $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin
9821 }
9822 else {
9823 $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Null
9824 }
9825 $FoundUser.PSObject.TypeNames.Add('PowerView.UserSession')
9826 $FoundUser
9827 }
9828 }
9829 }
9830 }
9831 }
9832
9833 if ($Poll -gt 0) {
9834 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
9835 }
9836 } While ($Poll -gt 0 -and $Timer.Elapsed.TotalSeconds -lt $Poll)
9837 }
9838 }
9839 }
9840
9841 process {
9842
9843 if($Threads) {
9844 Write-Verbose "Using threading with threads = $Threads"
9845
9846 # if we're using threading, kick off the script block with Invoke-ThreadedFunction
9847 $ScriptParams = @{
9848 'Ping' = $(-not $NoPing)
9849 'TargetUsers' = $TargetUsers
9850 'CurrentUser' = $CurrentUser
9851 'Stealth' = $Stealth
9852 'DomainShortName' = $DomainShortName
9853 'Poll' = $Poll
9854 'Delay' = $Delay
9855 'Jitter' = $Jitter
9856 }
9857
9858 # kick off the threaded script block + arguments
9859 Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
9860 }
9861
9862 else {
9863 if(-not $NoPing -and ($ComputerName.count -ne 1)) {
9864 # ping all hosts in parallel
9865 $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
9866 $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
9867 }
9868
9869 Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
9870 $Counter = 0
9871 $RandNo = New-Object System.Random
9872
9873 ForEach ($Computer in $ComputerName) {
9874
9875 $Counter = $Counter + 1
9876
9877 # sleep for our semi-randomized interval
9878 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
9879
9880 Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
9881 $Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $TargetUsers, $CurrentUser, $Stealth, $DomainShortName, 0, 0, 0
9882 $Result
9883
9884 if($Result -and $StopOnSuccess) {
9885 Write-Verbose "[*] Target user found, returning early"
9886 return
9887 }
9888 }
9889 }
9890
9891 }
9892}
9893
9894
9895function Invoke-StealthUserHunter {
9896 [CmdletBinding()]
9897 param(
9898 [Parameter(Position=0,ValueFromPipeline=$True)]
9899 [Alias('Hosts')]
9900 [String[]]
9901 $ComputerName,
9902
9903 [ValidateScript({Test-Path -Path $_ })]
9904 [Alias('HostList')]
9905 [String]
9906 $ComputerFile,
9907
9908 [String]
9909 $ComputerFilter,
9910
9911 [String]
9912 $ComputerADSpath,
9913
9914 [String]
9915 $GroupName = 'Domain Admins',
9916
9917 [String]
9918 $TargetServer,
9919
9920 [String]
9921 $UserName,
9922
9923 [String]
9924 $UserFilter,
9925
9926 [String]
9927 $UserADSpath,
9928
9929 [ValidateScript({Test-Path -Path $_ })]
9930 [String]
9931 $UserFile,
9932
9933 [Switch]
9934 $CheckAccess,
9935
9936 [Switch]
9937 $StopOnSuccess,
9938
9939 [Switch]
9940 $NoPing,
9941
9942 [UInt32]
9943 $Delay = 0,
9944
9945 [Double]
9946 $Jitter = .3,
9947
9948 [String]
9949 $Domain,
9950
9951 [Switch]
9952 $ShowAll,
9953
9954 [Switch]
9955 $SearchForest,
9956
9957 [String]
9958 [ValidateSet("DFS","DC","File","All")]
9959 $StealthSource ="All"
9960 )
9961 # kick off Invoke-UserHunter with stealth options
9962 Invoke-UserHunter -Stealth @PSBoundParameters
9963}
9964
9965
9966function Invoke-ProcessHunter {
9967<#
9968 .SYNOPSIS
9969
9970 Query the process lists of remote machines, searching for
9971 processes with a specific name or owned by a specific user.
9972 Thanks to @paulbrandau for the approach idea.
9973
9974 Author: @harmj0y
9975 License: BSD 3-Clause
9976
9977 .PARAMETER ComputerName
9978
9979 Host array to enumerate, passable on the pipeline.
9980
9981 .PARAMETER ComputerFile
9982
9983 File of hostnames/IPs to search.
9984
9985 .PARAMETER ComputerFilter
9986
9987 Host filter name to query AD for, wildcards accepted.
9988
9989 .PARAMETER ComputerADSpath
9990
9991 The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
9992 Useful for OU queries.
9993
9994 .PARAMETER ProcessName
9995
9996 The name of the process to hunt, or a comma separated list of names.
9997
9998 .PARAMETER GroupName
9999
10000 Group name to query for target users.
10001
10002 .PARAMETER TargetServer
10003
10004 Hunt for users who are effective local admins on a target server.
10005
10006 .PARAMETER UserName
10007
10008 Specific username to search for.
10009
10010 .PARAMETER UserFilter
10011
10012 A customized ldap filter string to use for user enumeration, e.g. "(description=*admin*)"
10013
10014 .PARAMETER UserADSpath
10015
10016 The LDAP source to search through for users, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
10017 Useful for OU queries.
10018
10019 .PARAMETER UserFile
10020
10021 File of usernames to search for.
10022
10023 .PARAMETER StopOnSuccess
10024
10025 Switch. Stop hunting after finding after finding a target user/process.
10026
10027 .PARAMETER NoPing
10028
10029 Switch. Don't ping each host to ensure it's up before enumerating.
10030
10031 .PARAMETER Delay
10032
10033 Delay between enumerating hosts, defaults to 0
10034
10035 .PARAMETER Jitter
10036
10037 Jitter for the host delay, defaults to +/- 0.3
10038
10039 .PARAMETER Domain
10040
10041 Domain for query for machines, defaults to the current domain.
10042
10043 .PARAMETER DomainController
10044
10045 Domain controller to reflect LDAP queries through.
10046
10047 .PARAMETER ShowAll
10048
10049 Switch. Return all user location results.
10050
10051 .PARAMETER SearchForest
10052
10053 Switch. Search all domains in the forest for target users instead of just
10054 a single domain.
10055
10056 .PARAMETER Threads
10057
10058 The maximum concurrent threads to execute.
10059
10060 .PARAMETER Credential
10061
10062 A [Management.Automation.PSCredential] object of alternate credentials
10063 for connection to the target machine/domain.
10064
10065 .EXAMPLE
10066
10067 PS C:\> Invoke-ProcessHunter -Domain 'testing'
10068
10069 Finds machines on the 'testing' domain where domain admins have a
10070 running process.
10071
10072 .EXAMPLE
10073
10074 PS C:\> Invoke-ProcessHunter -Threads 20
10075
10076 Multi-threaded process hunting, replaces Invoke-ProcessHunterThreaded.
10077
10078 .EXAMPLE
10079
10080 PS C:\> Invoke-ProcessHunter -UserFile users.txt -ComputerFile hosts.txt
10081
10082 Finds machines in hosts.txt where any members of users.txt have running
10083 processes.
10084
10085 .EXAMPLE
10086
10087 PS C:\> Invoke-ProcessHunter -GroupName "Power Users" -Delay 60
10088
10089 Find machines on the domain where members of the "Power Users" groups have
10090 running processes with a 60 second (+/- *.3) randomized delay between
10091 touching each host.
10092
10093 .LINK
10094
10095 http://blog.harmj0y.net
10096#>
10097
10098 [CmdletBinding()]
10099 param(
10100 [Parameter(Position=0,ValueFromPipeline=$True)]
10101 [Alias('Hosts')]
10102 [String[]]
10103 $ComputerName,
10104
10105 [ValidateScript({Test-Path -Path $_ })]
10106 [Alias('HostList')]
10107 [String]
10108 $ComputerFile,
10109
10110 [String]
10111 $ComputerFilter,
10112
10113 [String]
10114 $ComputerADSpath,
10115
10116 [String]
10117 $ProcessName,
10118
10119 [String]
10120 $GroupName = 'Domain Admins',
10121
10122 [String]
10123 $TargetServer,
10124
10125 [String]
10126 $UserName,
10127
10128 [String]
10129 $UserFilter,
10130
10131 [String]
10132 $UserADSpath,
10133
10134 [ValidateScript({Test-Path -Path $_ })]
10135 [String]
10136 $UserFile,
10137
10138 [Switch]
10139 $StopOnSuccess,
10140
10141 [Switch]
10142 $NoPing,
10143
10144 [UInt32]
10145 $Delay = 0,
10146
10147 [Double]
10148 $Jitter = .3,
10149
10150 [String]
10151 $Domain,
10152
10153 [String]
10154 $DomainController,
10155
10156 [Switch]
10157 $ShowAll,
10158
10159 [Switch]
10160 $SearchForest,
10161
10162 [ValidateRange(1,100)]
10163 [Int]
10164 $Threads,
10165
10166 [Management.Automation.PSCredential]
10167 $Credential
10168 )
10169
10170 begin {
10171
10172 if ($PSBoundParameters['Debug']) {
10173 $DebugPreference = 'Continue'
10174 }
10175
10176 # random object for delay
10177 $RandNo = New-Object System.Random
10178
10179 Write-Verbose "[*] Running Invoke-ProcessHunter with delay of $Delay"
10180
10181 #####################################################
10182 #
10183 # First we build the host target set
10184 #
10185 #####################################################
10186
10187 # if we're using a host list, read the targets in and add them to the target list
10188 if($ComputerFile) {
10189 $ComputerName = Get-Content -Path $ComputerFile
10190 }
10191
10192 if(!$ComputerName) {
10193 [array]$ComputerName = @()
10194
10195 if($Domain) {
10196 $TargetDomains = @($Domain)
10197 }
10198 elseif($SearchForest) {
10199 # get ALL the domains in the forest to search
10200 $TargetDomains = Get-NetForestDomain -DomainController $DomainController -Credential $Credential | ForEach-Object { $_.Name }
10201 }
10202 else {
10203 # use the local domain
10204 $TargetDomains = @( (Get-NetDomain -Domain $Domain -Credential $Credential).name )
10205 }
10206
10207 ForEach ($Domain in $TargetDomains) {
10208 Write-Verbose "[*] Querying domain $Domain for hosts"
10209 $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -Filter $ComputerFilter -ADSpath $ComputerADSpath
10210 }
10211
10212 # remove any null target hosts, uniquify the list and shuffle it
10213 $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
10214 if($($ComputerName.Count) -eq 0) {
10215 throw "No hosts found!"
10216 }
10217 }
10218
10219 #####################################################
10220 #
10221 # Now we build the user target set
10222 #
10223 #####################################################
10224
10225 if(!$ProcessName) {
10226 Write-Verbose "No process name specified, building a target user set"
10227
10228 # users we're going to be searching for
10229 $TargetUsers = @()
10230
10231 # if we want to hunt for the effective domain users who can access a target server
10232 if($TargetServer) {
10233 Write-Verbose "Querying target server '$TargetServer' for local users"
10234 $TargetUsers = Get-NetLocalGroup $TargetServer -Recurse | Where-Object {(-not $_.IsGroup) -and $_.IsDomain } | ForEach-Object {
10235 ($_.AccountName).split("/")[1].toLower()
10236 } | Where-Object {$_}
10237 }
10238 # if we get a specific username, only use that
10239 elseif($UserName) {
10240 Write-Verbose "[*] Using target user '$UserName'..."
10241 $TargetUsers = @( $UserName.ToLower() )
10242 }
10243 # read in a target user list if we have one
10244 elseif($UserFile) {
10245 $TargetUsers = Get-Content -Path $UserFile | Where-Object {$_}
10246 }
10247 elseif($UserADSpath -or $UserFilter) {
10248 ForEach ($Domain in $TargetDomains) {
10249 Write-Verbose "[*] Querying domain $Domain for users"
10250 $TargetUsers += Get-NetUser -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $UserADSpath -Filter $UserFilter | ForEach-Object {
10251 $_.samaccountname
10252 } | Where-Object {$_}
10253 }
10254 }
10255 else {
10256 ForEach ($Domain in $TargetDomains) {
10257 Write-Verbose "[*] Querying domain $Domain for users of group '$GroupName'"
10258 $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController -Credential $Credential| ForEach-Object {
10259 $_.MemberName
10260 }
10261 }
10262 }
10263
10264 if ((-not $ShowAll) -and ((!$TargetUsers) -or ($TargetUsers.Count -eq 0))) {
10265 throw "[!] No users found to search for!"
10266 }
10267 }
10268
10269 # script block that enumerates a server
10270 $HostEnumBlock = {
10271 param($ComputerName, $Ping, $ProcessName, $TargetUsers, $Credential)
10272
10273 # optionally check if the server is up first
10274 $Up = $True
10275 if($Ping) {
10276 $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
10277 }
10278 if($Up) {
10279 # try to enumerate all active processes on the remote host
10280 # and search for a specific process name
10281 $Processes = Get-NetProcess -Credential $Credential -ComputerName $ComputerName -ErrorAction SilentlyContinue
10282
10283 ForEach ($Process in $Processes) {
10284 # if we're hunting for a process name or comma-separated names
10285 if($ProcessName) {
10286 $ProcessName.split(",") | ForEach-Object {
10287 if ($Process.ProcessName -match $_) {
10288 $Process
10289 }
10290 }
10291 }
10292 # if the session user is in the target list, display some output
10293 elseif ($TargetUsers -contains $Process.User) {
10294 $Process
10295 }
10296 }
10297 }
10298 }
10299
10300 }
10301
10302 process {
10303
10304 if($Threads) {
10305 Write-Verbose "Using threading with threads = $Threads"
10306
10307 # if we're using threading, kick off the script block with Invoke-ThreadedFunction
10308 $ScriptParams = @{
10309 'Ping' = $(-not $NoPing)
10310 'ProcessName' = $ProcessName
10311 'TargetUsers' = $TargetUsers
10312 'Credential' = $Credential
10313 }
10314
10315 # kick off the threaded script block + arguments
10316 Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
10317 }
10318
10319 else {
10320 if(-not $NoPing -and ($ComputerName.count -ne 1)) {
10321 # ping all hosts in parallel
10322 $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
10323 $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
10324 }
10325
10326 Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
10327 $Counter = 0
10328
10329 ForEach ($Computer in $ComputerName) {
10330
10331 $Counter = $Counter + 1
10332
10333 # sleep for our semi-randomized interval
10334 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
10335
10336 Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
10337 $Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $ProcessName, $TargetUsers, $Credential
10338 $Result
10339
10340 if($Result -and $StopOnSuccess) {
10341 Write-Verbose "[*] Target user/process found, returning early"
10342 return
10343 }
10344 }
10345 }
10346 }
10347}
10348
10349
10350function Invoke-EventHunter {
10351<#
10352 .SYNOPSIS
10353
10354 Queries all domain controllers on the network for account
10355 logon events (ID 4624) and TGT request events (ID 4768),
10356 searching for target users.
10357
10358 Note: Domain Admin (or equiv) rights are needed to query
10359 this information from the DCs.
10360
10361 Author: @sixdub, @harmj0y
10362 License: BSD 3-Clause
10363
10364 .PARAMETER ComputerName
10365
10366 Host array to enumerate, passable on the pipeline.
10367
10368 .PARAMETER ComputerFile
10369
10370 File of hostnames/IPs to search.
10371
10372 .PARAMETER ComputerFilter
10373
10374 Host filter name to query AD for, wildcards accepted.
10375
10376 .PARAMETER ComputerADSpath
10377
10378 The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
10379 Useful for OU queries.
10380
10381 .PARAMETER GroupName
10382
10383 Group name to query for target users.
10384
10385 .PARAMETER TargetServer
10386
10387 Hunt for users who are effective local admins on a target server.
10388
10389 .PARAMETER UserName
10390
10391 Specific username to search for.
10392
10393 .PARAMETER UserFilter
10394
10395 A customized ldap filter string to use for user enumeration, e.g. "(description=*admin*)"
10396
10397 .PARAMETER UserADSpath
10398
10399 The LDAP source to search through for users, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
10400 Useful for OU queries.
10401
10402 .PARAMETER UserFile
10403
10404 File of usernames to search for.
10405
10406 .PARAMETER NoPing
10407
10408 Don't ping each host to ensure it's up before enumerating.
10409
10410 .PARAMETER Domain
10411
10412 Domain for query for machines, defaults to the current domain.
10413
10414 .PARAMETER DomainController
10415
10416 Domain controller to reflect LDAP queries through.
10417
10418 .PARAMETER SearchDays
10419
10420 Number of days back to search logs for. Default 3.
10421
10422 .PARAMETER SearchForest
10423
10424 Switch. Search all domains in the forest for target users instead of just
10425 a single domain.
10426
10427 .PARAMETER Threads
10428
10429 The maximum concurrent threads to execute.
10430
10431 .PARAMETER Credential
10432
10433 A [Management.Automation.PSCredential] object of alternate credentials
10434 for connection to the target domain.
10435
10436 .EXAMPLE
10437
10438 PS C:\> Invoke-EventHunter
10439
10440 .LINK
10441
10442 http://blog.harmj0y.net
10443#>
10444
10445 [CmdletBinding()]
10446 param(
10447 [Parameter(Position=0,ValueFromPipeline=$True)]
10448 [Alias('Hosts')]
10449 [String[]]
10450 $ComputerName,
10451
10452 [ValidateScript({Test-Path -Path $_ })]
10453 [Alias('HostList')]
10454 [String]
10455 $ComputerFile,
10456
10457 [String]
10458 $ComputerFilter,
10459
10460 [String]
10461 $ComputerADSpath,
10462
10463 [String]
10464 $GroupName = 'Domain Admins',
10465
10466 [String]
10467 $TargetServer,
10468
10469 [String[]]
10470 $UserName,
10471
10472 [String]
10473 $UserFilter,
10474
10475 [String]
10476 $UserADSpath,
10477
10478 [ValidateScript({Test-Path -Path $_ })]
10479 [String]
10480 $UserFile,
10481
10482 [String]
10483 $Domain,
10484
10485 [String]
10486 $DomainController,
10487
10488 [Int32]
10489 $SearchDays = 3,
10490
10491 [Switch]
10492 $SearchForest,
10493
10494 [ValidateRange(1,100)]
10495 [Int]
10496 $Threads,
10497
10498 [Management.Automation.PSCredential]
10499 $Credential
10500 )
10501
10502 begin {
10503
10504 if ($PSBoundParameters['Debug']) {
10505 $DebugPreference = 'Continue'
10506 }
10507
10508 # random object for delay
10509 $RandNo = New-Object System.Random
10510
10511 Write-Verbose "[*] Running Invoke-EventHunter"
10512
10513 if($Domain) {
10514 $TargetDomains = @($Domain)
10515 }
10516 elseif($SearchForest) {
10517 # get ALL the domains in the forest to search
10518 $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
10519 }
10520 else {
10521 # use the local domain
10522 $TargetDomains = @( (Get-NetDomain -Credential $Credential).name )
10523 }
10524
10525 #####################################################
10526 #
10527 # First we build the host target set
10528 #
10529 #####################################################
10530
10531 if(!$ComputerName) {
10532 # if we're using a host list, read the targets in and add them to the target list
10533 if($ComputerFile) {
10534 $ComputerName = Get-Content -Path $ComputerFile
10535 }
10536 elseif($ComputerFilter -or $ComputerADSpath) {
10537 [array]$ComputerName = @()
10538 ForEach ($Domain in $TargetDomains) {
10539 Write-Verbose "[*] Querying domain $Domain for hosts"
10540 $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Credential $Credential -Filter $ComputerFilter -ADSpath $ComputerADSpath
10541 }
10542 }
10543 else {
10544 # if a computer specifier isn't given, try to enumerate all domain controllers
10545 [array]$ComputerName = @()
10546 ForEach ($Domain in $TargetDomains) {
10547 Write-Verbose "[*] Querying domain $Domain for domain controllers"
10548 $ComputerName += Get-NetDomainController -LDAP -Domain $Domain -DomainController $DomainController -Credential $Credential | ForEach-Object { $_.dnshostname}
10549 }
10550 }
10551
10552 # remove any null target hosts, uniquify the list and shuffle it
10553 $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
10554 if($($ComputerName.Count) -eq 0) {
10555 throw "No hosts found!"
10556 }
10557 }
10558
10559 #####################################################
10560 #
10561 # Now we build the user target set
10562 #
10563 #####################################################
10564
10565 # users we're going to be searching for
10566 $TargetUsers = @()
10567
10568 # if we want to hunt for the effective domain users who can access a target server
10569 if($TargetServer) {
10570 Write-Verbose "Querying target server '$TargetServer' for local users"
10571 $TargetUsers = Get-NetLocalGroup $TargetServer -Recurse | Where-Object {(-not $_.IsGroup) -and $_.IsDomain } | ForEach-Object {
10572 ($_.AccountName).split("/")[1].toLower()
10573 } | Where-Object {$_}
10574 }
10575 # if we get a specific username, only use that
10576 elseif($UserName) {
10577 # Write-Verbose "[*] Using target user '$UserName'..."
10578 $TargetUsers = $UserName | ForEach-Object {$_.ToLower()}
10579 if($TargetUsers -isnot [System.Array]) {
10580 $TargetUsers = @($TargetUsers)
10581 }
10582 }
10583 # read in a target user list if we have one
10584 elseif($UserFile) {
10585 $TargetUsers = Get-Content -Path $UserFile | Where-Object {$_}
10586 }
10587 elseif($UserADSpath -or $UserFilter) {
10588 ForEach ($Domain in $TargetDomains) {
10589 Write-Verbose "[*] Querying domain $Domain for users"
10590 $TargetUsers += Get-NetUser -Domain $Domain -DomainController $DomainController -Credential $Credential -ADSpath $UserADSpath -Filter $UserFilter | ForEach-Object {
10591 $_.samaccountname
10592 } | Where-Object {$_}
10593 }
10594 }
10595 else {
10596 ForEach ($Domain in $TargetDomains) {
10597 Write-Verbose "[*] Querying domain $Domain for users of group '$GroupName'"
10598 $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController -Credential $Credential | ForEach-Object {
10599 $_.MemberName
10600 }
10601 }
10602 }
10603
10604 if (((!$TargetUsers) -or ($TargetUsers.Count -eq 0))) {
10605 throw "[!] No users found to search for!"
10606 }
10607
10608 # script block that enumerates a server
10609 $HostEnumBlock = {
10610 param($ComputerName, $Ping, $TargetUsers, $SearchDays, $Credential)
10611
10612 # optionally check if the server is up first
10613 $Up = $True
10614 if($Ping) {
10615 $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
10616 }
10617 if($Up) {
10618 # try to enumerate
10619 if($Credential) {
10620 Get-UserEvent -ComputerName $ComputerName -Credential $Credential -EventType 'all' -DateStart ([DateTime]::Today.AddDays(-$SearchDays)) | Where-Object {
10621 # filter for the target user set
10622 $TargetUsers -contains $_.UserName
10623 }
10624 }
10625 else {
10626 Get-UserEvent -ComputerName $ComputerName -EventType 'all' -DateStart ([DateTime]::Today.AddDays(-$SearchDays)) | Where-Object {
10627 # filter for the target user set
10628 $TargetUsers -contains $_.UserName
10629 }
10630 }
10631 }
10632 }
10633
10634 }
10635
10636 process {
10637
10638 if($Threads) {
10639 Write-Verbose "Using threading with threads = $Threads"
10640
10641 # if we're using threading, kick off the script block with Invoke-ThreadedFunction
10642 $ScriptParams = @{
10643 'Ping' = $(-not $NoPing)
10644 'TargetUsers' = $TargetUsers
10645 'SearchDays' = $SearchDays
10646 'Credential' = $Credential
10647 }
10648
10649 # kick off the threaded script block + arguments
10650 Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
10651 }
10652
10653 else {
10654 if(-not $NoPing -and ($ComputerName.count -ne 1)) {
10655 # ping all hosts in parallel
10656 $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
10657 $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
10658 }
10659
10660 Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
10661 $Counter = 0
10662
10663 ForEach ($Computer in $ComputerName) {
10664
10665 $Counter = $Counter + 1
10666
10667 # sleep for our semi-randomized interval
10668 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
10669
10670 Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
10671 Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $(-not $NoPing), $TargetUsers, $SearchDays, $Credential
10672 }
10673 }
10674
10675 }
10676}
10677
10678
10679function Invoke-ShareFinder {
10680<#
10681 .SYNOPSIS
10682
10683 This function finds the local domain name for a host using Get-NetDomain,
10684 queries the domain for all active machines with Get-NetComputer, then for
10685 each server it lists of active shares with Get-NetShare. Non-standard shares
10686 can be filtered out with -Exclude* flags.
10687
10688 Author: @harmj0y
10689 License: BSD 3-Clause
10690
10691 .PARAMETER ComputerName
10692
10693 Host array to enumerate, passable on the pipeline.
10694
10695 .PARAMETER ComputerFile
10696
10697 File of hostnames/IPs to search.
10698
10699 .PARAMETER ComputerFilter
10700
10701 Host filter name to query AD for, wildcards accepted.
10702
10703 .PARAMETER ComputerADSpath
10704
10705 The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
10706 Useful for OU queries.
10707
10708 .PARAMETER ExcludeStandard
10709
10710 Switch. Exclude standard shares from display (C$, IPC$, print$ etc.)
10711
10712 .PARAMETER ExcludePrint
10713
10714 Switch. Exclude the print$ share.
10715
10716 .PARAMETER ExcludeIPC
10717
10718 Switch. Exclude the IPC$ share.
10719
10720 .PARAMETER CheckShareAccess
10721
10722 Switch. Only display found shares that the local user has access to.
10723
10724 .PARAMETER CheckAdmin
10725
10726 Switch. Only display ADMIN$ shares the local user has access to.
10727
10728 .PARAMETER NoPing
10729
10730 Switch. Don't ping each host to ensure it's up before enumerating.
10731
10732 .PARAMETER Delay
10733
10734 Delay between enumerating hosts, defaults to 0.
10735
10736 .PARAMETER Jitter
10737
10738 Jitter for the host delay, defaults to +/- 0.3.
10739
10740 .PARAMETER Domain
10741
10742 Domain to query for machines, defaults to the current domain.
10743
10744 .PARAMETER DomainController
10745
10746 Domain controller to reflect LDAP queries through.
10747
10748 .PARAMETER SearchForest
10749
10750 Switch. Search all domains in the forest for target users instead of just
10751 a single domain.
10752
10753 .PARAMETER Threads
10754
10755 The maximum concurrent threads to execute.
10756
10757 .EXAMPLE
10758
10759 PS C:\> Invoke-ShareFinder -ExcludeStandard
10760
10761 Find non-standard shares on the domain.
10762
10763 .EXAMPLE
10764
10765 PS C:\> Invoke-ShareFinder -Threads 20
10766
10767 Multi-threaded share finding, replaces Invoke-ShareFinderThreaded.
10768
10769 .EXAMPLE
10770
10771 PS C:\> Invoke-ShareFinder -Delay 60
10772
10773 Find shares on the domain with a 60 second (+/- *.3)
10774 randomized delay between touching each host.
10775
10776 .EXAMPLE
10777
10778 PS C:\> Invoke-ShareFinder -ComputerFile hosts.txt
10779
10780 Find shares for machines in the specified hosts file.
10781
10782 .LINK
10783 http://blog.harmj0y.net
10784#>
10785
10786 [CmdletBinding()]
10787 param(
10788 [Parameter(Position=0,ValueFromPipeline=$True)]
10789 [Alias('Hosts')]
10790 [String[]]
10791 $ComputerName,
10792
10793 [ValidateScript({Test-Path -Path $_ })]
10794 [Alias('HostList')]
10795 [String]
10796 $ComputerFile,
10797
10798 [String]
10799 $ComputerFilter,
10800
10801 [String]
10802 $ComputerADSpath,
10803
10804 [Switch]
10805 $ExcludeStandard,
10806
10807 [Switch]
10808 $ExcludePrint,
10809
10810 [Switch]
10811 $ExcludeIPC,
10812
10813 [Switch]
10814 $NoPing,
10815
10816 [Switch]
10817 $CheckShareAccess,
10818
10819 [Switch]
10820 $CheckAdmin,
10821
10822 [UInt32]
10823 $Delay = 0,
10824
10825 [Double]
10826 $Jitter = .3,
10827
10828 [String]
10829 $Domain,
10830
10831 [String]
10832 $DomainController,
10833
10834 [Switch]
10835 $SearchForest,
10836
10837 [ValidateRange(1,100)]
10838 [Int]
10839 $Threads
10840 )
10841
10842 begin {
10843 if ($PSBoundParameters['Debug']) {
10844 $DebugPreference = 'Continue'
10845 }
10846
10847 # random object for delay
10848 $RandNo = New-Object System.Random
10849
10850 Write-Verbose "[*] Running Invoke-ShareFinder with delay of $Delay"
10851
10852 # figure out the shares we want to ignore
10853 [String[]] $ExcludedShares = @('')
10854
10855 if ($ExcludePrint) {
10856 $ExcludedShares = $ExcludedShares + "PRINT$"
10857 }
10858 if ($ExcludeIPC) {
10859 $ExcludedShares = $ExcludedShares + "IPC$"
10860 }
10861 if ($ExcludeStandard) {
10862 $ExcludedShares = @('', "ADMIN$", "IPC$", "C$", "PRINT$")
10863 }
10864
10865 # if we're using a host file list, read the targets in and add them to the target list
10866 if($ComputerFile) {
10867 $ComputerName = Get-Content -Path $ComputerFile
10868 }
10869
10870 if(!$ComputerName) {
10871 [array]$ComputerName = @()
10872
10873 if($Domain) {
10874 $TargetDomains = @($Domain)
10875 }
10876 elseif($SearchForest) {
10877 # get ALL the domains in the forest to search
10878 $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
10879 }
10880 else {
10881 # use the local domain
10882 $TargetDomains = @( (Get-NetDomain).name )
10883 }
10884
10885 ForEach ($Domain in $TargetDomains) {
10886 Write-Verbose "[*] Querying domain $Domain for hosts"
10887 $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Filter $ComputerFilter -ADSpath $ComputerADSpath
10888 }
10889
10890 # remove any null target hosts, uniquify the list and shuffle it
10891 $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
10892 if($($ComputerName.count) -eq 0) {
10893 throw "No hosts found!"
10894 }
10895 }
10896
10897 # script block that enumerates a server
10898 $HostEnumBlock = {
10899 param($ComputerName, $Ping, $CheckShareAccess, $ExcludedShares, $CheckAdmin)
10900
10901 # optionally check if the server is up first
10902 $Up = $True
10903 if($Ping) {
10904 $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
10905 }
10906 if($Up) {
10907 # get the shares for this host and check what we find
10908 $Shares = Get-NetShare -ComputerName $ComputerName
10909 ForEach ($Share in $Shares) {
10910 Write-Verbose "[*] Server share: $Share"
10911 $NetName = $Share.shi1_netname
10912 $Remark = $Share.shi1_remark
10913 $Path = '\\'+$ComputerName+'\'+$NetName
10914
10915 # make sure we get a real share name back
10916 if (($NetName) -and ($NetName.trim() -ne '')) {
10917 # if we're just checking for access to ADMIN$
10918 if($CheckAdmin) {
10919 if($NetName.ToUpper() -eq "ADMIN$") {
10920 try {
10921 $Null = [IO.Directory]::GetFiles($Path)
10922 "\\$ComputerName\$NetName `t- $Remark"
10923 }
10924 catch {
10925 Write-Verbose "Error accessing path $Path : $_"
10926 }
10927 }
10928 }
10929 # skip this share if it's in the exclude list
10930 elseif ($ExcludedShares -NotContains $NetName.ToUpper()) {
10931 # see if we want to check access to this share
10932 if($CheckShareAccess) {
10933 # check if the user has access to this path
10934 try {
10935 $Null = [IO.Directory]::GetFiles($Path)
10936 "\\$ComputerName\$NetName `t- $Remark"
10937 }
10938 catch {
10939 Write-Verbose "Error accessing path $Path : $_"
10940 }
10941 }
10942 else {
10943 "\\$ComputerName\$NetName `t- $Remark"
10944 }
10945 }
10946 }
10947 }
10948 }
10949 }
10950
10951 }
10952
10953 process {
10954
10955 if($Threads) {
10956 Write-Verbose "Using threading with threads = $Threads"
10957
10958 # if we're using threading, kick off the script block with Invoke-ThreadedFunction
10959 $ScriptParams = @{
10960 'Ping' = $(-not $NoPing)
10961 'CheckShareAccess' = $CheckShareAccess
10962 'ExcludedShares' = $ExcludedShares
10963 'CheckAdmin' = $CheckAdmin
10964 }
10965
10966 # kick off the threaded script block + arguments
10967 Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
10968 }
10969
10970 else {
10971 if(-not $NoPing -and ($ComputerName.count -ne 1)) {
10972 # ping all hosts in parallel
10973 $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
10974 $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
10975 }
10976
10977 Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
10978 $Counter = 0
10979
10980 ForEach ($Computer in $ComputerName) {
10981
10982 $Counter = $Counter + 1
10983
10984 # sleep for our semi-randomized interval
10985 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
10986
10987 Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
10988 Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $CheckShareAccess, $ExcludedShares, $CheckAdmin
10989 }
10990 }
10991
10992 }
10993}
10994
10995
10996function Invoke-FileFinder {
10997<#
10998 .SYNOPSIS
10999
11000 Finds sensitive files on the domain.
11001
11002 Author: @harmj0y
11003 License: BSD 3-Clause
11004
11005 .DESCRIPTION
11006
11007 This function finds the local domain name for a host using Get-NetDomain,
11008 queries the domain for all active machines with Get-NetComputer, grabs
11009 the readable shares for each server, and recursively searches every
11010 share for files with specific keywords in the name.
11011 If a share list is passed, EVERY share is enumerated regardless of
11012 other options.
11013
11014 .PARAMETER ComputerName
11015
11016 Host array to enumerate, passable on the pipeline.
11017
11018 .PARAMETER ComputerFile
11019
11020 File of hostnames/IPs to search.
11021
11022 .PARAMETER ComputerFilter
11023
11024 Host filter name to query AD for, wildcards accepted.
11025
11026 .PARAMETER ComputerADSpath
11027
11028 The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
11029 Useful for OU queries.
11030
11031 .PARAMETER ShareList
11032
11033 List if \\HOST\shares to search through.
11034
11035 .PARAMETER Terms
11036
11037 Terms to search for.
11038
11039 .PARAMETER OfficeDocs
11040
11041 Switch. Search for office documents (*.doc*, *.xls*, *.ppt*)
11042
11043 .PARAMETER FreshEXEs
11044
11045 Switch. Find .EXEs accessed within the last week.
11046
11047 .PARAMETER LastAccessTime
11048
11049 Only return files with a LastAccessTime greater than this date value.
11050
11051 .PARAMETER LastWriteTime
11052
11053 Only return files with a LastWriteTime greater than this date value.
11054
11055 .PARAMETER CreationTime
11056
11057 Only return files with a CreationDate greater than this date value.
11058
11059 .PARAMETER IncludeC
11060
11061 Switch. Include any C$ shares in recursive searching (default ignore).
11062
11063 .PARAMETER IncludeAdmin
11064
11065 Switch. Include any ADMIN$ shares in recursive searching (default ignore).
11066
11067 .PARAMETER ExcludeFolders
11068
11069 Switch. Exclude folders from the search results.
11070
11071 .PARAMETER ExcludeHidden
11072
11073 Switch. Exclude hidden files and folders from the search results.
11074
11075 .PARAMETER CheckWriteAccess
11076
11077 Switch. Only returns files the current user has write access to.
11078
11079 .PARAMETER OutFile
11080
11081 Output results to a specified csv output file.
11082
11083 .PARAMETER NoClobber
11084
11085 Switch. Don't overwrite any existing output file.
11086
11087 .PARAMETER NoPing
11088
11089 Switch. Don't ping each host to ensure it's up before enumerating.
11090
11091 .PARAMETER Delay
11092
11093 Delay between enumerating hosts, defaults to 0
11094
11095 .PARAMETER Jitter
11096
11097 Jitter for the host delay, defaults to +/- 0.3
11098
11099 .PARAMETER Domain
11100
11101 Domain to query for machines, defaults to the current domain.
11102
11103 .PARAMETER DomainController
11104
11105 Domain controller to reflect LDAP queries through.
11106
11107 .PARAMETER SearchForest
11108
11109 Search all domains in the forest for target users instead of just
11110 a single domain.
11111
11112 .PARAMETER SearchSYSVOL
11113
11114 Switch. Search for login scripts on the SYSVOL of the primary DCs for each specified domain.
11115
11116 .PARAMETER Threads
11117
11118 The maximum concurrent threads to execute.
11119
11120 .PARAMETER UsePSDrive
11121
11122 Switch. Mount target remote path with temporary PSDrives.
11123
11124 .EXAMPLE
11125
11126 PS C:\> Invoke-FileFinder
11127
11128 Find readable files on the domain with 'pass', 'sensitive',
11129 'secret', 'admin', 'login', or 'unattend*.xml' in the name,
11130
11131 .EXAMPLE
11132
11133 PS C:\> Invoke-FileFinder -Domain testing
11134
11135 Find readable files on the 'testing' domain with 'pass', 'sensitive',
11136 'secret', 'admin', 'login', or 'unattend*.xml' in the name,
11137
11138 .EXAMPLE
11139
11140 PS C:\> Invoke-FileFinder -IncludeC
11141
11142 Find readable files on the domain with 'pass', 'sensitive',
11143 'secret', 'admin', 'login' or 'unattend*.xml' in the name,
11144 including C$ shares.
11145
11146 .EXAMPLE
11147
11148 PS C:\> Invoke-FileFinder -ShareList shares.txt -Terms accounts,ssn -OutFile out.csv
11149
11150 Enumerate a specified share list for files with 'accounts' or
11151 'ssn' in the name, and write everything to "out.csv"
11152
11153 .LINK
11154 http://www.harmj0y.net/blog/redteaming/file-server-triage-on-red-team-engagements/
11155
11156#>
11157
11158 [CmdletBinding()]
11159 param(
11160 [Parameter(Position=0,ValueFromPipeline=$True)]
11161 [Alias('Hosts')]
11162 [String[]]
11163 $ComputerName,
11164
11165 [ValidateScript({Test-Path -Path $_ })]
11166 [Alias('HostList')]
11167 [String]
11168 $ComputerFile,
11169
11170 [String]
11171 $ComputerFilter,
11172
11173 [String]
11174 $ComputerADSpath,
11175
11176 [ValidateScript({Test-Path -Path $_ })]
11177 [String]
11178 $ShareList,
11179
11180 [Switch]
11181 $OfficeDocs,
11182
11183 [Switch]
11184 $FreshEXEs,
11185
11186 [Alias('Terms')]
11187 [String[]]
11188 $SearchTerms,
11189
11190 [ValidateScript({Test-Path -Path $_ })]
11191 [String]
11192 $TermList,
11193
11194 [String]
11195 $LastAccessTime,
11196
11197 [String]
11198 $LastWriteTime,
11199
11200 [String]
11201 $CreationTime,
11202
11203 [Switch]
11204 $IncludeC,
11205
11206 [Switch]
11207 $IncludeAdmin,
11208
11209 [Switch]
11210 $ExcludeFolders,
11211
11212 [Switch]
11213 $ExcludeHidden,
11214
11215 [Switch]
11216 $CheckWriteAccess,
11217
11218 [String]
11219 $OutFile,
11220
11221 [Switch]
11222 $NoClobber,
11223
11224 [Switch]
11225 $NoPing,
11226
11227 [UInt32]
11228 $Delay = 0,
11229
11230 [Double]
11231 $Jitter = .3,
11232
11233 [String]
11234 $Domain,
11235
11236 [String]
11237 $DomainController,
11238
11239 [Switch]
11240 $SearchForest,
11241
11242 [Switch]
11243 $SearchSYSVOL,
11244
11245 [ValidateRange(1,100)]
11246 [Int]
11247 $Threads,
11248
11249 [Switch]
11250 $UsePSDrive
11251 )
11252
11253 begin {
11254 if ($PSBoundParameters['Debug']) {
11255 $DebugPreference = 'Continue'
11256 }
11257
11258 # random object for delay
11259 $RandNo = New-Object System.Random
11260
11261 Write-Verbose "[*] Running Invoke-FileFinder with delay of $Delay"
11262
11263 $Shares = @()
11264
11265 # figure out the shares we want to ignore
11266 [String[]] $ExcludedShares = @("C$", "ADMIN$")
11267
11268 # see if we're specifically including any of the normally excluded sets
11269 if ($IncludeC) {
11270 if ($IncludeAdmin) {
11271 $ExcludedShares = @()
11272 }
11273 else {
11274 $ExcludedShares = @("ADMIN$")
11275 }
11276 }
11277
11278 if ($IncludeAdmin) {
11279 if ($IncludeC) {
11280 $ExcludedShares = @()
11281 }
11282 else {
11283 $ExcludedShares = @("C$")
11284 }
11285 }
11286
11287 # delete any existing output file if it already exists
11288 if(!$NoClobber) {
11289 if ($OutFile -and (Test-Path -Path $OutFile)) { Remove-Item -Path $OutFile }
11290 }
11291
11292 # if there's a set of terms specified to search for
11293 if ($TermList) {
11294 ForEach ($Term in Get-Content -Path $TermList) {
11295 if (($Term -ne $Null) -and ($Term.trim() -ne '')) {
11296 $SearchTerms += $Term
11297 }
11298 }
11299 }
11300
11301 # if we're hard-passed a set of shares
11302 if($ShareList) {
11303 ForEach ($Item in Get-Content -Path $ShareList) {
11304 if (($Item -ne $Null) -and ($Item.trim() -ne '')) {
11305 # exclude any "[tab]- commants", i.e. the output from Invoke-ShareFinder
11306 $Share = $Item.Split("`t")[0]
11307 $Shares += $Share
11308 }
11309 }
11310 }
11311 else {
11312 # if we're using a host file list, read the targets in and add them to the target list
11313 if($ComputerFile) {
11314 $ComputerName = Get-Content -Path $ComputerFile
11315 }
11316
11317 if(!$ComputerName) {
11318
11319 if($Domain) {
11320 $TargetDomains = @($Domain)
11321 }
11322 elseif($SearchForest) {
11323 # get ALL the domains in the forest to search
11324 $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
11325 }
11326 else {
11327 # use the local domain
11328 $TargetDomains = @( (Get-NetDomain).name )
11329 }
11330
11331 if($SearchSYSVOL) {
11332 ForEach ($Domain in $TargetDomains) {
11333 $DCSearchPath = "\\$Domain\SYSVOL\"
11334 Write-Verbose "[*] Adding share search path $DCSearchPath"
11335 $Shares += $DCSearchPath
11336 }
11337 if(!$SearchTerms) {
11338 # search for interesting scripts on SYSVOL
11339 $SearchTerms = @('.vbs', '.bat', '.ps1')
11340 }
11341 }
11342 else {
11343 [array]$ComputerName = @()
11344
11345 ForEach ($Domain in $TargetDomains) {
11346 Write-Verbose "[*] Querying domain $Domain for hosts"
11347 $ComputerName += Get-NetComputer -Filter $ComputerFilter -ADSpath $ComputerADSpath -Domain $Domain -DomainController $DomainController
11348 }
11349
11350 # remove any null target hosts, uniquify the list and shuffle it
11351 $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
11352 if($($ComputerName.Count) -eq 0) {
11353 throw "No hosts found!"
11354 }
11355 }
11356 }
11357 }
11358
11359 # script block that enumerates shares and files on a server
11360 $HostEnumBlock = {
11361 param($ComputerName, $Ping, $ExcludedShares, $SearchTerms, $ExcludeFolders, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $OutFile, $UsePSDrive)
11362
11363 Write-Verbose "ComputerName: $ComputerName"
11364 Write-Verbose "ExcludedShares: $ExcludedShares"
11365 $SearchShares = @()
11366
11367 if($ComputerName.StartsWith("\\")) {
11368 # if a share is passed as the server
11369 $SearchShares += $ComputerName
11370 }
11371 else {
11372 # if we're enumerating the shares on the target server first
11373 $Up = $True
11374 if($Ping) {
11375 $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
11376 }
11377 if($Up) {
11378 # get the shares for this host and display what we find
11379 $Shares = Get-NetShare -ComputerName $ComputerName
11380 ForEach ($Share in $Shares) {
11381
11382 $NetName = $Share.shi1_netname
11383 $Path = '\\'+$ComputerName+'\'+$NetName
11384
11385 # make sure we get a real share name back
11386 if (($NetName) -and ($NetName.trim() -ne '')) {
11387
11388 # skip this share if it's in the exclude list
11389 if ($ExcludedShares -NotContains $NetName.ToUpper()) {
11390 # check if the user has access to this path
11391 try {
11392 $Null = [IO.Directory]::GetFiles($Path)
11393 $SearchShares += $Path
11394 }
11395 catch {
11396 Write-Verbose "[!] No access to $Path"
11397 }
11398 }
11399 }
11400 }
11401 }
11402 }
11403
11404 ForEach($Share in $SearchShares) {
11405 $SearchArgs = @{
11406 'Path' = $Share
11407 'SearchTerms' = $SearchTerms
11408 'OfficeDocs' = $OfficeDocs
11409 'FreshEXEs' = $FreshEXEs
11410 'LastAccessTime' = $LastAccessTime
11411 'LastWriteTime' = $LastWriteTime
11412 'CreationTime' = $CreationTime
11413 'ExcludeFolders' = $ExcludeFolders
11414 'ExcludeHidden' = $ExcludeHidden
11415 'CheckWriteAccess' = $CheckWriteAccess
11416 'OutFile' = $OutFile
11417 'UsePSDrive' = $UsePSDrive
11418 }
11419
11420 Find-InterestingFile @SearchArgs
11421 }
11422 }
11423 }
11424
11425 process {
11426
11427 if($Threads) {
11428 Write-Verbose "Using threading with threads = $Threads"
11429
11430 # if we're using threading, kick off the script block with Invoke-ThreadedFunction
11431 $ScriptParams = @{
11432 'Ping' = $(-not $NoPing)
11433 'ExcludedShares' = $ExcludedShares
11434 'SearchTerms' = $SearchTerms
11435 'ExcludeFolders' = $ExcludeFolders
11436 'OfficeDocs' = $OfficeDocs
11437 'ExcludeHidden' = $ExcludeHidden
11438 'FreshEXEs' = $FreshEXEs
11439 'CheckWriteAccess' = $CheckWriteAccess
11440 'OutFile' = $OutFile
11441 'UsePSDrive' = $UsePSDrive
11442 }
11443
11444 # kick off the threaded script block + arguments
11445 if($Shares) {
11446 # pass the shares as the hosts so the threaded function code doesn't have to be hacked up
11447 Invoke-ThreadedFunction -ComputerName $Shares -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
11448 }
11449 else {
11450 Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
11451 }
11452 }
11453
11454 else {
11455 if($Shares){
11456 $ComputerName = $Shares
11457 }
11458 elseif(-not $NoPing -and ($ComputerName.count -gt 1)) {
11459 # ping all hosts in parallel
11460 $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
11461 $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
11462 }
11463
11464 Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
11465 $Counter = 0
11466
11467 $ComputerName | Where-Object {$_} | ForEach-Object {
11468 Write-Verbose "Computer: $_"
11469 $Counter = $Counter + 1
11470
11471 # sleep for our semi-randomized interval
11472 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
11473
11474 Write-Verbose "[*] Enumerating server $_ ($Counter of $($ComputerName.count))"
11475
11476 Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $_, $False, $ExcludedShares, $SearchTerms, $ExcludeFolders, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $OutFile, $UsePSDrive
11477 }
11478 }
11479 }
11480}
11481
11482
11483function Find-LocalAdminAccess {
11484<#
11485 .SYNOPSIS
11486
11487 Finds machines on the local domain where the current user has
11488 local administrator access. Uses multithreading to
11489 speed up enumeration.
11490
11491 Author: @harmj0y
11492 License: BSD 3-Clause
11493
11494 .DESCRIPTION
11495
11496 This function finds the local domain name for a host using Get-NetDomain,
11497 queries the domain for all active machines with Get-NetComputer, then for
11498 each server it checks if the current user has local administrator
11499 access using Invoke-CheckLocalAdminAccess.
11500
11501 Idea stolen from the local_admin_search_enum post module in
11502 Metasploit written by:
11503 'Brandon McCann "zeknox" <bmccann[at]accuvant.com>'
11504 'Thomas McCarthy "smilingraccoon" <smilingraccoon[at]gmail.com>'
11505 'Royce Davis "r3dy" <rdavis[at]accuvant.com>'
11506
11507 .PARAMETER ComputerName
11508
11509 Host array to enumerate, passable on the pipeline.
11510
11511 .PARAMETER ComputerFile
11512
11513 File of hostnames/IPs to search.
11514
11515 .PARAMETER ComputerFilter
11516
11517 Host filter name to query AD for, wildcards accepted.
11518
11519 .PARAMETER ComputerADSpath
11520
11521 The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
11522 Useful for OU queries.
11523
11524 .PARAMETER NoPing
11525
11526 Switch. Don't ping each host to ensure it's up before enumerating.
11527
11528 .PARAMETER Delay
11529
11530 Delay between enumerating hosts, defaults to 0
11531
11532 .PARAMETER Jitter
11533
11534 Jitter for the host delay, defaults to +/- 0.3
11535
11536 .PARAMETER Domain
11537
11538 Domain to query for machines, defaults to the current domain.
11539
11540 .PARAMETER DomainController
11541
11542 Domain controller to reflect LDAP queries through.
11543
11544 .PARAMETER SearchForest
11545
11546 Switch. Search all domains in the forest for target users instead of just
11547 a single domain.
11548
11549 .PARAMETER Threads
11550
11551 The maximum concurrent threads to execute.
11552
11553 .EXAMPLE
11554
11555 PS C:\> Find-LocalAdminAccess
11556
11557 Find machines on the local domain where the current user has local
11558 administrator access.
11559
11560 .EXAMPLE
11561
11562 PS C:\> Find-LocalAdminAccess -Threads 10
11563
11564 Multi-threaded access hunting, replaces Find-LocalAdminAccessThreaded.
11565
11566 .EXAMPLE
11567
11568 PS C:\> Find-LocalAdminAccess -Domain testing
11569
11570 Find machines on the 'testing' domain where the current user has
11571 local administrator access.
11572
11573 .EXAMPLE
11574
11575 PS C:\> Find-LocalAdminAccess -ComputerFile hosts.txt
11576
11577 Find which machines in the host list the current user has local
11578 administrator access.
11579
11580 .LINK
11581
11582 https://github.com/rapid7/metasploit-framework/blob/master/modules/post/windows/gather/local_admin_search_enum.rb
11583 http://www.harmj0y.net/blog/penetesting/finding-local-admin-with-the-veil-framework/
11584#>
11585
11586 [CmdletBinding()]
11587 param(
11588 [Parameter(Position=0,ValueFromPipeline=$True)]
11589 [Alias('Hosts')]
11590 [String[]]
11591 $ComputerName,
11592
11593 [ValidateScript({Test-Path -Path $_ })]
11594 [Alias('HostList')]
11595 [String]
11596 $ComputerFile,
11597
11598 [String]
11599 $ComputerFilter,
11600
11601 [String]
11602 $ComputerADSpath,
11603
11604 [Switch]
11605 $NoPing,
11606
11607 [UInt32]
11608 $Delay = 0,
11609
11610 [Double]
11611 $Jitter = .3,
11612
11613 [String]
11614 $Domain,
11615
11616 [String]
11617 $DomainController,
11618
11619 [Switch]
11620 $SearchForest,
11621
11622 [ValidateRange(1,100)]
11623 [Int]
11624 $Threads
11625 )
11626
11627 begin {
11628 if ($PSBoundParameters['Debug']) {
11629 $DebugPreference = 'Continue'
11630 }
11631
11632 # random object for delay
11633 $RandNo = New-Object System.Random
11634
11635 Write-Verbose "[*] Running Find-LocalAdminAccess with delay of $Delay"
11636
11637 # if we're using a host list, read the targets in and add them to the target list
11638 if($ComputerFile) {
11639 $ComputerName = Get-Content -Path $ComputerFile
11640 }
11641
11642 if(!$ComputerName) {
11643 [array]$ComputerName = @()
11644
11645 if($Domain) {
11646 $TargetDomains = @($Domain)
11647 }
11648 elseif($SearchForest) {
11649 # get ALL the domains in the forest to search
11650 $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
11651 }
11652 else {
11653 # use the local domain
11654 $TargetDomains = @( (Get-NetDomain).name )
11655 }
11656
11657 ForEach ($Domain in $TargetDomains) {
11658 Write-Verbose "[*] Querying domain $Domain for hosts"
11659 $ComputerName += Get-NetComputer -Filter $ComputerFilter -ADSpath $ComputerADSpath -Domain $Domain -DomainController $DomainController
11660 }
11661
11662 # remove any null target hosts, uniquify the list and shuffle it
11663 $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
11664 if($($ComputerName.Count) -eq 0) {
11665 throw "No hosts found!"
11666 }
11667 }
11668
11669 # script block that enumerates a server
11670 $HostEnumBlock = {
11671 param($ComputerName, $Ping)
11672
11673 $Up = $True
11674 if($Ping) {
11675 $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
11676 }
11677 if($Up) {
11678 # check if the current user has local admin access to this server
11679 $Access = Invoke-CheckLocalAdminAccess -ComputerName $ComputerName
11680 if ($Access.IsAdmin) {
11681 $ComputerName
11682 }
11683 }
11684 }
11685
11686 }
11687
11688 process {
11689
11690 if($Threads) {
11691 Write-Verbose "Using threading with threads = $Threads"
11692
11693 # if we're using threading, kick off the script block with Invoke-ThreadedFunction
11694 $ScriptParams = @{
11695 'Ping' = $(-not $NoPing)
11696 }
11697
11698 # kick off the threaded script block + arguments
11699 Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
11700 }
11701
11702 else {
11703 if(-not $NoPing -and ($ComputerName.count -ne 1)) {
11704 # ping all hosts in parallel
11705 $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
11706 $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
11707 }
11708
11709 Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
11710 $Counter = 0
11711
11712 ForEach ($Computer in $ComputerName) {
11713
11714 $Counter = $Counter + 1
11715
11716 # sleep for our semi-randomized interval
11717 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
11718
11719 Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
11720 Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False
11721 }
11722 }
11723 }
11724}
11725
11726
11727function Get-ExploitableSystem {
11728<#
11729 .Synopsis
11730
11731 This module will query Active Directory for the hostname, OS version, and service pack level
11732 for each computer account. That information is then cross-referenced against a list of common
11733 Metasploit exploits that can be used during penetration testing.
11734
11735 .DESCRIPTION
11736
11737 This module will query Active Directory for the hostname, OS version, and service pack level
11738 for each computer account. That information is then cross-referenced against a list of common
11739 Metasploit exploits that can be used during penetration testing. The script filters out disabled
11740 domain computers and provides the computer's last logon time to help determine if it's been
11741 decommissioned. Also, since the script uses data tables to output affected systems the results
11742 can be easily piped to other commands such as test-connection or a Export-Csv.
11743
11744 .PARAMETER ComputerName
11745
11746 Return computers with a specific name, wildcards accepted.
11747
11748 .PARAMETER SPN
11749
11750 Return computers with a specific service principal name, wildcards accepted.
11751
11752 .PARAMETER OperatingSystem
11753
11754 Return computers with a specific operating system, wildcards accepted.
11755
11756 .PARAMETER ServicePack
11757
11758 Return computers with a specific service pack, wildcards accepted.
11759
11760 .PARAMETER Filter
11761
11762 A customized ldap filter string to use, e.g. "(description=*admin*)"
11763
11764 .PARAMETER Ping
11765
11766 Switch. Ping each host to ensure it's up before enumerating.
11767
11768 .PARAMETER Domain
11769
11770 The domain to query for computers, defaults to the current domain.
11771
11772 .PARAMETER DomainController
11773
11774 Domain controller to reflect LDAP queries through.
11775
11776 .PARAMETER ADSpath
11777
11778 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
11779 Useful for OU queries.
11780
11781 .PARAMETER Unconstrained
11782
11783 Switch. Return computer objects that have unconstrained delegation.
11784
11785 .PARAMETER PageSize
11786
11787 The PageSize to set for the LDAP searcher object.
11788
11789 .PARAMETER Credential
11790
11791 A [Management.Automation.PSCredential] object of alternate credentials
11792 for connection to the target domain.
11793
11794 .EXAMPLE
11795
11796 The example below shows the standard command usage. Disabled system are excluded by default, but
11797 the "LastLgon" column can be used to determine which systems are live. Usually, if a system hasn't
11798 logged on for two or more weeks it's been decommissioned.
11799 PS C:\> Get-ExploitableSystem -DomainController 192.168.1.1 -Credential demo.com\user | Format-Table -AutoSize
11800 [*] Grabbing computer accounts from Active Directory...
11801 [*] Loading exploit list for critical missing patches...
11802 [*] Checking computers for vulnerable OS and SP levels...
11803 [+] Found 5 potentially vulnerable systems!
11804 ComputerName OperatingSystem ServicePack LastLogon MsfModule CVE
11805 ------------ --------------- ----------- --------- --------- ---
11806 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....
11807 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....
11808 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....
11809 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....
11810 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....
11811 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....
11812 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....
11813 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....
11814 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....
11815 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....
11816 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....
11817 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....
11818 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....
11819 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....
11820
11821 .EXAMPLE
11822
11823 PS C:\> Get-ExploitableSystem | Export-Csv c:\temp\output.csv -NoTypeInformation
11824
11825 How to write the output to a csv file.
11826
11827 .EXAMPLE
11828
11829 PS C:\> Get-ExploitableSystem -Domain testlab.local -Ping
11830
11831 Return a set of live hosts from the testlab.local domain
11832
11833 .LINK
11834
11835 http://www.netspi.com
11836 https://github.com/nullbind/Powershellery/blob/master/Stable-ish/ADS/Get-ExploitableSystems.psm1
11837
11838 .NOTES
11839
11840 Author: Scott Sutherland - 2015, NetSPI
11841 Modifications to integrate into PowerView by @harmj0y
11842 Version: Get-ExploitableSystem.psm1 v1.1
11843 Comments: The technique used to query LDAP was based on the "Get-AuditDSComputerAccount"
11844 function found in Carols Perez's PoshSec-Mod project. The general idea is based off of
11845 Will Schroeder's "Invoke-FindVulnSystems" function from the PowerView toolkit.
11846#>
11847 [CmdletBinding()]
11848 Param(
11849 [Parameter(ValueFromPipeline=$True)]
11850 [Alias('HostName')]
11851 [String]
11852 $ComputerName = '*',
11853
11854 [String]
11855 $SPN,
11856
11857 [String]
11858 $OperatingSystem = '*',
11859
11860 [String]
11861 $ServicePack = '*',
11862
11863 [String]
11864 $Filter,
11865
11866 [Switch]
11867 $Ping,
11868
11869 [String]
11870 $Domain,
11871
11872 [String]
11873 $DomainController,
11874
11875 [String]
11876 $ADSpath,
11877
11878 [Switch]
11879 $Unconstrained,
11880
11881 [ValidateRange(1,10000)]
11882 [Int]
11883 $PageSize = 200,
11884
11885 [Management.Automation.PSCredential]
11886 $Credential
11887 )
11888
11889 Write-Verbose "[*] Grabbing computer accounts from Active Directory..."
11890
11891 # Create data table for hostnames, os, and service packs from LDAP
11892 $TableAdsComputers = New-Object System.Data.DataTable
11893 $Null = $TableAdsComputers.Columns.Add('Hostname')
11894 $Null = $TableAdsComputers.Columns.Add('OperatingSystem')
11895 $Null = $TableAdsComputers.Columns.Add('ServicePack')
11896 $Null = $TableAdsComputers.Columns.Add('LastLogon')
11897
11898 Get-NetComputer -FullData @PSBoundParameters | ForEach-Object {
11899
11900 $CurrentHost = $_.dnshostname
11901 $CurrentOs = $_.operatingsystem
11902 $CurrentSp = $_.operatingsystemservicepack
11903 $CurrentLast = $_.lastlogon
11904 $CurrentUac = $_.useraccountcontrol
11905
11906 $CurrentUacBin = [convert]::ToString($_.useraccountcontrol,2)
11907
11908 # Check the 2nd to last value to determine if its disabled
11909 $DisableOffset = $CurrentUacBin.Length - 2
11910 $CurrentDisabled = $CurrentUacBin.Substring($DisableOffset,1)
11911
11912 # Add computer to list if it's enabled
11913 if ($CurrentDisabled -eq 0) {
11914 # Add domain computer to data table
11915 $Null = $TableAdsComputers.Rows.Add($CurrentHost,$CurrentOS,$CurrentSP,$CurrentLast)
11916 }
11917 }
11918
11919 # Status user
11920 Write-Verbose "[*] Loading exploit list for critical missing patches..."
11921
11922 # ----------------------------------------------------------------
11923 # Setup data table for list of msf exploits
11924 # ----------------------------------------------------------------
11925
11926 # Create data table for list of patches levels with a MSF exploit
11927 $TableExploits = New-Object System.Data.DataTable
11928 $Null = $TableExploits.Columns.Add('OperatingSystem')
11929 $Null = $TableExploits.Columns.Add('ServicePack')
11930 $Null = $TableExploits.Columns.Add('MsfModule')
11931 $Null = $TableExploits.Columns.Add('CVE')
11932
11933 # Add exploits to data table
11934 $Null = $TableExploits.Rows.Add("Windows 7","","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
11935 $Null = $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
11936 $Null = $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
11937 $Null = $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109")
11938 $Null = $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
11939 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
11940 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
11941 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109")
11942 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/")
11943 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
11944 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
11945 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
11946 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109")
11947 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
11948 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
11949 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
11950 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/dcerpc/ms07_029_msdns_zonename","http://www.cvedetails.com/cve/2007-1748")
11951 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/")
11952 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
11953 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms06_066_nwapi","http://www.cvedetails.com/cve/2006-4688")
11954 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms06_070_wkssvc","http://www.cvedetails.com/cve/2006-4691")
11955 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
11956 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
11957 $Null = $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
11958 $Null = $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
11959 $Null = $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109")
11960 $Null = $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/smb/ms05_039_pnp","http://www.cvedetails.com/cve/2005-1983")
11961 $Null = $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
11962 $Null = $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/dcerpc/ms07_029_msdns_zonename","http://www.cvedetails.com/cve/2007-1748")
11963 $Null = $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
11964 $Null = $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/smb/ms06_066_nwapi","http://www.cvedetails.com/cve/2006-4688")
11965 $Null = $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
11966 $Null = $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
11967 $Null = $TableExploits.Rows.Add("Windows Server 2003","Service Pack 2","exploit/windows/dcerpc/ms07_029_msdns_zonename","http://www.cvedetails.com/cve/2007-1748")
11968 $Null = $TableExploits.Rows.Add("Windows Server 2003","Service Pack 2","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
11969 $Null = $TableExploits.Rows.Add("Windows Server 2003","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
11970 $Null = $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
11971 $Null = $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
11972 $Null = $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
11973 $Null = $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
11974 $Null = $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
11975 $Null = $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/")
11976 $Null = $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
11977 $Null = $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
11978 $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")
11979 $Null = $TableExploits.Rows.Add("Windows Server 2008","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
11980 $Null = $TableExploits.Rows.Add("Windows Server 2008","","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103")
11981 $Null = $TableExploits.Rows.Add("Windows Server 2008","","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
11982 $Null = $TableExploits.Rows.Add("Windows Server 2008 R2","","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
11983 $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")
11984 $Null = $TableExploits.Rows.Add("Windows Vista","Server Pack 1","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
11985 $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")
11986 $Null = $TableExploits.Rows.Add("Windows Vista","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
11987 $Null = $TableExploits.Rows.Add("Windows Vista","","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103")
11988 $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
11989 $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
11990 $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/")
11991 $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/smb/ms05_039_pnp","http://www.cvedetails.com/cve/2005-1983")
11992 $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
11993 $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
11994 $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
11995 $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms06_066_nwapi","http://www.cvedetails.com/cve/2006-4688")
11996 $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms06_070_wkssvc","http://www.cvedetails.com/cve/2006-4691")
11997 $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
11998 $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
11999 $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 3","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
12000 $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 3","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
12001 $Null = $TableExploits.Rows.Add("Windows XP","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
12002 $Null = $TableExploits.Rows.Add("Windows XP","","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
12003 $Null = $TableExploits.Rows.Add("Windows XP","","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
12004 $Null = $TableExploits.Rows.Add("Windows XP","","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
12005
12006 # Status user
12007 Write-Verbose "[*] Checking computers for vulnerable OS and SP levels..."
12008
12009 # ----------------------------------------------------------------
12010 # Setup data table to store vulnerable systems
12011 # ----------------------------------------------------------------
12012
12013 # Create data table to house vulnerable server list
12014 $TableVulnComputers = New-Object System.Data.DataTable
12015 $Null = $TableVulnComputers.Columns.Add('ComputerName')
12016 $Null = $TableVulnComputers.Columns.Add('OperatingSystem')
12017 $Null = $TableVulnComputers.Columns.Add('ServicePack')
12018 $Null = $TableVulnComputers.Columns.Add('LastLogon')
12019 $Null = $TableVulnComputers.Columns.Add('MsfModule')
12020 $Null = $TableVulnComputers.Columns.Add('CVE')
12021
12022 # Iterate through each exploit
12023 $TableExploits | ForEach-Object {
12024
12025 $ExploitOS = $_.OperatingSystem
12026 $ExploitSP = $_.ServicePack
12027 $ExploitMsf = $_.MsfModule
12028 $ExploitCVE = $_.CVE
12029
12030 # Iterate through each ADS computer
12031 $TableAdsComputers | ForEach-Object {
12032
12033 $AdsHostname = $_.Hostname
12034 $AdsOS = $_.OperatingSystem
12035 $AdsSP = $_.ServicePack
12036 $AdsLast = $_.LastLogon
12037
12038 # Add exploitable systems to vul computers data table
12039 if ($AdsOS -like "$ExploitOS*" -and $AdsSP -like "$ExploitSP" ) {
12040 # Add domain computer to data table
12041 $Null = $TableVulnComputers.Rows.Add($AdsHostname,$AdsOS,$AdsSP,$AdsLast,$ExploitMsf,$ExploitCVE)
12042 }
12043 }
12044 }
12045
12046 # Display results
12047 $VulnComputer = $TableVulnComputers | Select-Object ComputerName -Unique | Measure-Object
12048 $VulnComputerCount = $VulnComputer.Count
12049
12050 if ($VulnComputer.Count -gt 0) {
12051 # Return vulnerable server list order with some hack date casting
12052 Write-Verbose "[+] Found $VulnComputerCount potentially vulnerable systems!"
12053 $TableVulnComputers | Sort-Object { $_.lastlogon -as [datetime]} -Descending
12054 }
12055 else {
12056 Write-Verbose "[-] No vulnerable systems were found."
12057 }
12058}
12059
12060
12061function Invoke-EnumerateLocalAdmin {
12062<#
12063 .SYNOPSIS
12064
12065 This function queries the domain for all active machines with
12066 Get-NetComputer, then for each server it queries the local
12067 Administrators with Get-NetLocalGroup.
12068
12069 Author: @harmj0y
12070 License: BSD 3-Clause
12071
12072 .PARAMETER ComputerName
12073
12074 Host array to enumerate, passable on the pipeline.
12075
12076 .PARAMETER ComputerFile
12077
12078 File of hostnames/IPs to search.
12079
12080 .PARAMETER ComputerFilter
12081
12082 Host filter name to query AD for, wildcards accepted.
12083
12084 .PARAMETER ComputerADSpath
12085
12086 The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
12087 Useful for OU queries.
12088
12089 .PARAMETER NoPing
12090
12091 Switch. Don't ping each host to ensure it's up before enumerating.
12092
12093 .PARAMETER Delay
12094
12095 Delay between enumerating hosts, defaults to 0
12096
12097 .PARAMETER Jitter
12098
12099 Jitter for the host delay, defaults to +/- 0.3
12100
12101 .PARAMETER OutFile
12102
12103 Output results to a specified csv output file.
12104
12105 .PARAMETER NoClobber
12106
12107 Switch. Don't overwrite any existing output file.
12108
12109 .PARAMETER TrustGroups
12110
12111 Switch. Only return results that are not part of the local machine
12112 or the machine's domain. Old Invoke-EnumerateLocalTrustGroup
12113 functionality.
12114
12115 .PARAMETER DomainOnly
12116
12117 Switch. Only return domain (non-local) results
12118
12119 .PARAMETER Domain
12120
12121 Domain to query for machines, defaults to the current domain.
12122
12123 .PARAMETER DomainController
12124
12125 Domain controller to reflect LDAP queries through.
12126
12127 .PARAMETER SearchForest
12128
12129 Switch. Search all domains in the forest for target users instead of just
12130 a single domain.
12131
12132 .PARAMETER API
12133
12134 Switch. Use API calls instead of the WinNT service provider. Less information,
12135 but the results are faster.
12136
12137 .PARAMETER Threads
12138
12139 The maximum concurrent threads to execute.
12140
12141 .EXAMPLE
12142
12143 PS C:\> Invoke-EnumerateLocalAdmin
12144
12145 Enumerates the members of local administrators for all machines
12146 in the current domain.
12147
12148 .EXAMPLE
12149
12150 PS C:\> Invoke-EnumerateLocalAdmin -Threads 10
12151
12152 Threaded local admin enumeration, replaces Invoke-EnumerateLocalAdminThreaded
12153
12154 .LINK
12155
12156 http://blog.harmj0y.net/
12157#>
12158
12159 [CmdletBinding()]
12160 param(
12161 [Parameter(Position=0,ValueFromPipeline=$True)]
12162 [Alias('Hosts')]
12163 [String[]]
12164 $ComputerName,
12165
12166 [ValidateScript({Test-Path -Path $_ })]
12167 [Alias('HostList')]
12168 [String]
12169 $ComputerFile,
12170
12171 [String]
12172 $ComputerFilter,
12173
12174 [String]
12175 $ComputerADSpath,
12176
12177 [Switch]
12178 $NoPing,
12179
12180 [UInt32]
12181 $Delay = 0,
12182
12183 [Double]
12184 $Jitter = .3,
12185
12186 [String]
12187 $OutFile,
12188
12189 [Switch]
12190 $NoClobber,
12191
12192 [Switch]
12193 $TrustGroups,
12194
12195 [Switch]
12196 $DomainOnly,
12197
12198 [String]
12199 $Domain,
12200
12201 [String]
12202 $DomainController,
12203
12204 [Switch]
12205 $SearchForest,
12206
12207 [ValidateRange(1,100)]
12208 [Int]
12209 $Threads,
12210
12211 [Switch]
12212 $API
12213 )
12214
12215 begin {
12216 if ($PSBoundParameters['Debug']) {
12217 $DebugPreference = 'Continue'
12218 }
12219
12220 # random object for delay
12221 $RandNo = New-Object System.Random
12222
12223 Write-Verbose "[*] Running Invoke-EnumerateLocalAdmin with delay of $Delay"
12224
12225 # if we're using a host list, read the targets in and add them to the target list
12226 if($ComputerFile) {
12227 $ComputerName = Get-Content -Path $ComputerFile
12228 }
12229
12230 if(!$ComputerName) {
12231 [array]$ComputerName = @()
12232
12233 if($Domain) {
12234 $TargetDomains = @($Domain)
12235 }
12236 elseif($SearchForest) {
12237 # get ALL the domains in the forest to search
12238 $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
12239 }
12240 else {
12241 # use the local domain
12242 $TargetDomains = @( (Get-NetDomain).name )
12243 }
12244
12245 ForEach ($Domain in $TargetDomains) {
12246 Write-Verbose "[*] Querying domain $Domain for hosts"
12247 $ComputerName += Get-NetComputer -Filter $ComputerFilter -ADSpath $ComputerADSpath -Domain $Domain -DomainController $DomainController
12248 }
12249
12250 # remove any null target hosts, uniquify the list and shuffle it
12251 $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
12252 if($($ComputerName.Count) -eq 0) {
12253 throw "No hosts found!"
12254 }
12255 }
12256
12257 # delete any existing output file if it already exists
12258 if(!$NoClobber) {
12259 if ($OutFile -and (Test-Path -Path $OutFile)) { Remove-Item -Path $OutFile }
12260 }
12261
12262 if($TrustGroups) {
12263
12264 Write-Verbose "Determining domain trust groups"
12265
12266 # find all group names that have one or more users in another domain
12267 $TrustGroupNames = Find-ForeignGroup -Domain $Domain -DomainController $DomainController | ForEach-Object { $_.GroupName } | Sort-Object -Unique
12268
12269 $TrustGroupsSIDs = $TrustGroupNames | ForEach-Object {
12270 # ignore the builtin administrators group for a DC (S-1-5-32-544)
12271 # TODO: ignore all default built in sids?
12272 Get-NetGroup -Domain $Domain -DomainController $DomainController -GroupName $_ -FullData | Where-Object { $_.objectsid -notmatch "S-1-5-32-544" } | ForEach-Object { $_.objectsid }
12273 }
12274
12275 # query for the primary domain controller so we can extract the domain SID for filtering
12276 $DomainSID = Get-DomainSID -Domain $Domain -DomainController $DomainController
12277 }
12278
12279 # script block that enumerates a server
12280 $HostEnumBlock = {
12281 param($ComputerName, $Ping, $OutFile, $DomainSID, $TrustGroupsSIDs, $API, $DomainOnly)
12282
12283 # optionally check if the server is up first
12284 $Up = $True
12285 if($Ping) {
12286 $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
12287 }
12288 if($Up) {
12289 # grab the users for the local admins on this server
12290 if($API) {
12291 $LocalAdmins = Get-NetLocalGroup -ComputerName $ComputerName -API
12292 }
12293 else {
12294 $LocalAdmins = Get-NetLocalGroup -ComputerName $ComputerName
12295 }
12296
12297 # if we just want to return cross-trust users
12298 if($DomainSID) {
12299 # get the local machine SID
12300 $LocalSID = ($LocalAdmins | Where-Object { $_.SID -match '.*-500$' }).SID -replace "-500$"
12301 Write-Verbose "LocalSid for $ComputerName : $LocalSID"
12302 # filter out accounts that begin with the machine SID and domain SID
12303 # but preserve any groups that have users across a trust ($TrustGroupSIDS)
12304 $LocalAdmins = $LocalAdmins | Where-Object { ($TrustGroupsSIDs -contains $_.SID) -or ((-not $_.SID.startsWith($LocalSID)) -and (-not $_.SID.startsWith($DomainSID))) }
12305 }
12306
12307 if($DomainOnly) {
12308 $LocalAdmins = $LocalAdmins | Where-Object {$_.IsDomain}
12309 }
12310
12311 if($LocalAdmins -and ($LocalAdmins.Length -ne 0)) {
12312 # output the results to a csv if specified
12313 if($OutFile) {
12314 $LocalAdmins | Export-PowerViewCSV -OutFile $OutFile
12315 }
12316 else {
12317 # otherwise return the user objects
12318 $LocalAdmins
12319 }
12320 }
12321 else {
12322 Write-Verbose "[!] No users returned from $ComputerName"
12323 }
12324 }
12325 }
12326 }
12327
12328 process {
12329
12330 if($Threads) {
12331 Write-Verbose "Using threading with threads = $Threads"
12332
12333 # if we're using threading, kick off the script block with Invoke-ThreadedFunction
12334 $ScriptParams = @{
12335 'Ping' = $(-not $NoPing)
12336 'OutFile' = $OutFile
12337 'DomainSID' = $DomainSID
12338 'TrustGroupsSIDs' = $TrustGroupsSIDs
12339 }
12340
12341 # kick off the threaded script block + arguments
12342 if($API) {
12343 $ScriptParams['API'] = $True
12344 }
12345
12346 if($DomainOnly) {
12347 $ScriptParams['DomainOnly'] = $True
12348 }
12349
12350 Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams -Threads $Threads
12351 }
12352
12353 else {
12354 if(-not $NoPing -and ($ComputerName.count -ne 1)) {
12355 # ping all hosts in parallel
12356 $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
12357 $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
12358 }
12359
12360 Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
12361 $Counter = 0
12362
12363 ForEach ($Computer in $ComputerName) {
12364
12365 $Counter = $Counter + 1
12366
12367 # sleep for our semi-randomized interval
12368 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
12369 Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
12370
12371 $ScriptArgs = @($Computer, $False, $OutFile, $DomainSID, $TrustGroupsSIDs, $API, $DomainOnly)
12372
12373 Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $ScriptArgs
12374 }
12375 }
12376 }
12377}
12378
12379
12380########################################################
12381#
12382# Domain trust functions below.
12383#
12384########################################################
12385
12386function Get-NetDomainTrust {
12387<#
12388 .SYNOPSIS
12389
12390 Return all domain trusts for the current domain or
12391 a specified domain.
12392
12393 .PARAMETER Domain
12394
12395 The domain whose trusts to enumerate, defaults to the current domain.
12396
12397 .PARAMETER DomainController
12398
12399 Domain controller to reflect LDAP queries through.
12400
12401 .PARAMETER ADSpath
12402
12403 The LDAP source to search through, e.g. "LDAP://DC=testlab,DC=local".
12404 Useful for global catalog queries ;)
12405
12406 .PARAMETER API
12407
12408 Use an API call (DsEnumerateDomainTrusts) to enumerate the trusts.
12409
12410 .PARAMETER LDAP
12411
12412 Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections.
12413 More likely to get around network segmentation, but not as accurate.
12414
12415 .PARAMETER PageSize
12416
12417 The PageSize to set for the LDAP searcher object.
12418
12419 .EXAMPLE
12420
12421 PS C:\> Get-NetDomainTrust
12422
12423 Return domain trusts for the current domain using built in .NET methods.
12424
12425 .EXAMPLE
12426
12427 PS C:\> Get-NetDomainTrust -Domain "prod.testlab.local"
12428
12429 Return domain trusts for the "prod.testlab.local" domain using .NET methods
12430
12431 .EXAMPLE
12432
12433 PS C:\> Get-NetDomainTrust -LDAP -Domain "prod.testlab.local" -DomainController "PRIMARY.testlab.local"
12434
12435 Return domain trusts for the "prod.testlab.local" domain enumerated through LDAP
12436 queries, reflecting queries through the "Primary.testlab.local" domain controller,
12437 using .NET methods.
12438
12439 .EXAMPLE
12440
12441 PS C:\> Get-NetDomainTrust -API -Domain "prod.testlab.local"
12442
12443 Return domain trusts for the "prod.testlab.local" domain enumerated through API calls.
12444
12445 .EXAMPLE
12446
12447 PS C:\> Get-NetDomainTrust -API -DomainController WINDOWS2.testlab.local
12448
12449 Return domain trusts reachable from the WINDOWS2 machine through API calls.
12450#>
12451
12452 [CmdletBinding()]
12453 param(
12454 [Parameter(Position=0, ValueFromPipeline=$True)]
12455 [String]
12456 $Domain,
12457
12458 [String]
12459 $DomainController,
12460
12461 [String]
12462 $ADSpath,
12463
12464 [Switch]
12465 $API,
12466
12467 [Switch]
12468 $LDAP,
12469
12470 [ValidateRange(1,10000)]
12471 [Int]
12472 $PageSize = 200,
12473
12474 [Management.Automation.PSCredential]
12475 $Credential
12476 )
12477
12478 begin {
12479 $TrustAttributes = @{
12480 [uint32]'0x00000001' = 'non_transitive'
12481 [uint32]'0x00000002' = 'uplevel_only'
12482 [uint32]'0x00000004' = 'quarantined_domain'
12483 [uint32]'0x00000008' = 'forest_transitive'
12484 [uint32]'0x00000010' = 'cross_organization'
12485 [uint32]'0x00000020' = 'within_forest'
12486 [uint32]'0x00000040' = 'treat_as_external'
12487 [uint32]'0x00000080' = 'trust_uses_rc4_encryption'
12488 [uint32]'0x00000100' = 'trust_uses_aes_keys'
12489 [uint32]'0x00000200' = 'cross_organization_no_tgt_delegation'
12490 [uint32]'0x00000400' = 'pim_trust'
12491 }
12492 }
12493
12494 process {
12495
12496 if(-not $Domain) {
12497 # if not domain is specified grab the current domain
12498 $SourceDomain = (Get-NetDomain -Credential $Credential).Name
12499 }
12500 else {
12501 $SourceDomain = $Domain
12502 }
12503
12504 if($LDAP -or $ADSPath) {
12505
12506 $TrustSearcher = Get-DomainSearcher -Domain $SourceDomain -DomainController $DomainController -Credential $Credential -PageSize $PageSize -ADSpath $ADSpath
12507
12508 $SourceSID = Get-DomainSID -Domain $SourceDomain -DomainController $DomainController
12509
12510 if($TrustSearcher) {
12511
12512 $TrustSearcher.Filter = '(objectClass=trustedDomain)'
12513
12514 $Results = $TrustSearcher.FindAll()
12515 $Results | Where-Object {$_} | ForEach-Object {
12516 $Props = $_.Properties
12517 $DomainTrust = New-Object PSObject
12518
12519 $TrustAttrib = @()
12520 $TrustAttrib += $TrustAttributes.Keys | Where-Object { $Props.trustattributes[0] -band $_ } | ForEach-Object { $TrustAttributes[$_] }
12521
12522 $Direction = Switch ($Props.trustdirection) {
12523 0 { 'Disabled' }
12524 1 { 'Inbound' }
12525 2 { 'Outbound' }
12526 3 { 'Bidirectional' }
12527 }
12528 $ObjectGuid = New-Object Guid @(,$Props.objectguid[0])
12529 $TargetSID = (New-Object System.Security.Principal.SecurityIdentifier($Props.securityidentifier[0],0)).Value
12530 $DomainTrust | Add-Member Noteproperty 'SourceName' $SourceDomain
12531 $DomainTrust | Add-Member Noteproperty 'SourceSID' $SourceSID
12532 $DomainTrust | Add-Member Noteproperty 'TargetName' $Props.name[0]
12533 $DomainTrust | Add-Member Noteproperty 'TargetSID' $TargetSID
12534 $DomainTrust | Add-Member Noteproperty 'ObjectGuid' "{$ObjectGuid}"
12535 $DomainTrust | Add-Member Noteproperty 'TrustType' $($TrustAttrib -join ',')
12536 $DomainTrust | Add-Member Noteproperty 'TrustDirection' "$Direction"
12537 $DomainTrust.PSObject.TypeNames.Add('PowerView.DomainTrustLDAP')
12538 $DomainTrust
12539 }
12540 $Results.dispose()
12541 $TrustSearcher.dispose()
12542 }
12543 }
12544 elseif($API) {
12545 if(-not $DomainController) {
12546 $DomainController = Get-NetDomainController -Credential $Credential -Domain $SourceDomain | Select-Object -First 1 | Select-Object -ExpandProperty Name
12547 }
12548
12549 if($DomainController) {
12550 # arguments for DsEnumerateDomainTrusts
12551 $PtrInfo = [IntPtr]::Zero
12552
12553 # 63 = DS_DOMAIN_IN_FOREST + DS_DOMAIN_DIRECT_OUTBOUND + DS_DOMAIN_TREE_ROOT + DS_DOMAIN_PRIMARY + DS_DOMAIN_NATIVE_MODE + DS_DOMAIN_DIRECT_INBOUND
12554 $Flags = 63
12555 $DomainCount = 0
12556
12557 # get the trust information from the target server
12558 $Result = $Netapi32::DsEnumerateDomainTrusts($DomainController, $Flags, [ref]$PtrInfo, [ref]$DomainCount)
12559
12560 # Locate the offset of the initial intPtr
12561 $Offset = $PtrInfo.ToInt64()
12562
12563 # 0 = success
12564 if (($Result -eq 0) -and ($Offset -gt 0)) {
12565
12566 # Work out how much to increment the pointer by finding out the size of the structure
12567 $Increment = $DS_DOMAIN_TRUSTS::GetSize()
12568
12569 # parse all the result structures
12570 for ($i = 0; ($i -lt $DomainCount); $i++) {
12571 # create a new int ptr at the given offset and cast the pointer as our result structure
12572 $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
12573 $Info = $NewIntPtr -as $DS_DOMAIN_TRUSTS
12574
12575 $Offset = $NewIntPtr.ToInt64()
12576 $Offset += $Increment
12577
12578 $SidString = ""
12579 $Result = $Advapi32::ConvertSidToStringSid($Info.DomainSid, [ref]$SidString);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
12580
12581 if($Result -eq 0) {
12582 Write-Verbose "Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
12583 }
12584 else {
12585 $DomainTrust = New-Object PSObject
12586 $DomainTrust | Add-Member Noteproperty 'SourceDomain' $SourceDomain
12587 $DomainTrust | Add-Member Noteproperty 'SourceDomainController' $DomainController
12588 $DomainTrust | Add-Member Noteproperty 'NetbiosDomainName' $Info.NetbiosDomainName
12589 $DomainTrust | Add-Member Noteproperty 'DnsDomainName' $Info.DnsDomainName
12590 $DomainTrust | Add-Member Noteproperty 'Flags' $Info.Flags
12591 $DomainTrust | Add-Member Noteproperty 'ParentIndex' $Info.ParentIndex
12592 $DomainTrust | Add-Member Noteproperty 'TrustType' $Info.TrustType
12593 $DomainTrust | Add-Member Noteproperty 'TrustAttributes' $Info.TrustAttributes
12594 $DomainTrust | Add-Member Noteproperty 'DomainSid' $SidString
12595 $DomainTrust | Add-Member Noteproperty 'DomainGuid' $Info.DomainGuid
12596 $DomainTrust.PSObject.TypeNames.Add('PowerView.APIDomainTrust')
12597 $DomainTrust
12598 }
12599 }
12600 # free up the result buffer
12601 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
12602 }
12603 else {
12604 Write-Verbose "Error: $(([ComponentModel.Win32Exception] $Result).Message)"
12605 }
12606 }
12607 else {
12608 Write-Verbose "Could not retrieve domain controller for $Domain"
12609 }
12610 }
12611 else {
12612 # if we're using direct domain connections through .NET
12613 $FoundDomain = Get-NetDomain -Domain $Domain -Credential $Credential
12614 if($FoundDomain) {
12615 $FoundDomain.GetAllTrustRelationships() | ForEach-Object {
12616 $_.PSObject.TypeNames.Add('PowerView.DomainTrust')
12617 $_
12618 }
12619 }
12620 }
12621 }
12622}
12623
12624
12625function Get-NetForestTrust {
12626<#
12627 .SYNOPSIS
12628
12629 Return all trusts for the current forest.
12630
12631 .PARAMETER Forest
12632
12633 Return trusts for the specified forest.
12634
12635 .PARAMETER Credential
12636
12637 A [Management.Automation.PSCredential] object of alternate credentials
12638 for connection to the target domain.
12639
12640 .EXAMPLE
12641
12642 PS C:\> Get-NetForestTrust
12643
12644 Return current forest trusts.
12645
12646 .EXAMPLE
12647
12648 PS C:\> Get-NetForestTrust -Forest "test"
12649
12650 Return trusts for the "test" forest.
12651#>
12652
12653 [CmdletBinding()]
12654 param(
12655 [Parameter(Position=0,ValueFromPipeline=$True)]
12656 [String]
12657 $Forest,
12658
12659 [Management.Automation.PSCredential]
12660 $Credential
12661 )
12662
12663 process {
12664 $FoundForest = Get-NetForest -Forest $Forest -Credential $Credential
12665
12666 if($FoundForest) {
12667 $FoundForest.GetAllTrustRelationships() | ForEach-Object {
12668 $_.PSObject.TypeNames.Add('PowerView.ForestTrust')
12669 $_
12670 }
12671 }
12672 }
12673}
12674
12675
12676function Find-ForeignUser {
12677<#
12678 .SYNOPSIS
12679
12680 Enumerates users who are in groups outside of their
12681 principal domain. The -Recurse option will try to map all
12682 transitive domain trust relationships and enumerate all
12683 users who are in groups outside of their principal domain.
12684
12685 .PARAMETER UserName
12686
12687 Username to filter results for, wildcards accepted.
12688
12689 .PARAMETER Domain
12690
12691 Domain to query for users, defaults to the current domain.
12692
12693 .PARAMETER DomainController
12694
12695 Domain controller to reflect LDAP queries through.
12696
12697 .PARAMETER LDAP
12698
12699 Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections.
12700 More likely to get around network segmentation, but not as accurate.
12701
12702 .PARAMETER Recurse
12703
12704 Switch. Enumerate all user trust groups from all reachable domains recursively.
12705
12706 .PARAMETER PageSize
12707
12708 The PageSize to set for the LDAP searcher object.
12709
12710 .LINK
12711
12712 http://blog.harmj0y.net/
12713#>
12714
12715 [CmdletBinding()]
12716 param(
12717 [String]
12718 $UserName,
12719
12720 [String]
12721 $Domain,
12722
12723 [String]
12724 $DomainController,
12725
12726 [Switch]
12727 $LDAP,
12728
12729 [Switch]
12730 $Recurse,
12731
12732 [ValidateRange(1,10000)]
12733 [Int]
12734 $PageSize = 200
12735 )
12736
12737 function Get-ForeignUser {
12738 # helper used to enumerate users who are in groups outside of their principal domain
12739 param(
12740 [String]
12741 $UserName,
12742
12743 [String]
12744 $Domain,
12745
12746 [String]
12747 $DomainController,
12748
12749 [ValidateRange(1,10000)]
12750 [Int]
12751 $PageSize = 200
12752 )
12753
12754 if ($Domain) {
12755 # get the domain name into distinguished form
12756 $DistinguishedDomainName = "DC=" + $Domain -replace '\.',',DC='
12757 }
12758 else {
12759 $DistinguishedDomainName = [String] ([adsi]'').distinguishedname
12760 $Domain = $DistinguishedDomainName -replace 'DC=','' -replace ',','.'
12761 }
12762
12763 Get-NetUser -Domain $Domain -DomainController $DomainController -UserName $UserName -PageSize $PageSize -Filter '(memberof=*)' | ForEach-Object {
12764 ForEach ($Membership in $_.memberof) {
12765 $Index = $Membership.IndexOf("DC=")
12766 if($Index) {
12767
12768 $GroupDomain = $($Membership.substring($Index)) -replace 'DC=','' -replace ',','.'
12769
12770 if ($GroupDomain.CompareTo($Domain)) {
12771 # if the group domain doesn't match the user domain, output
12772 $GroupName = $Membership.split(",")[0].split("=")[1]
12773 $ForeignUser = New-Object PSObject
12774 $ForeignUser | Add-Member Noteproperty 'UserDomain' $Domain
12775 $ForeignUser | Add-Member Noteproperty 'UserName' $_.samaccountname
12776 $ForeignUser | Add-Member Noteproperty 'GroupDomain' $GroupDomain
12777 $ForeignUser | Add-Member Noteproperty 'GroupName' $GroupName
12778 $ForeignUser | Add-Member Noteproperty 'GroupDN' $Membership
12779 $ForeignUser
12780 }
12781 }
12782 }
12783 }
12784 }
12785
12786 if ($Recurse) {
12787 # get all rechable domains in the trust mesh and uniquify them
12788 if($LDAP -or $DomainController) {
12789 $DomainTrusts = Invoke-MapDomainTrust -LDAP -DomainController $DomainController -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique
12790 }
12791 else {
12792 $DomainTrusts = Invoke-MapDomainTrust -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique
12793 }
12794
12795 ForEach($DomainTrust in $DomainTrusts) {
12796 # get the trust groups for each domain in the trust mesh
12797 Write-Verbose "Enumerating trust groups in domain $DomainTrust"
12798 Get-ForeignUser -Domain $DomainTrust -UserName $UserName -PageSize $PageSize
12799 }
12800 }
12801 else {
12802 Get-ForeignUser -Domain $Domain -DomainController $DomainController -UserName $UserName -PageSize $PageSize
12803 }
12804}
12805
12806
12807function Find-ForeignGroup {
12808<#
12809 .SYNOPSIS
12810
12811 Enumerates all the members of a given domain's groups
12812 and finds users that are not in the queried domain.
12813 The -Recurse flag will perform this enumeration for all
12814 eachable domain trusts.
12815
12816 .PARAMETER GroupName
12817
12818 Groupname to filter results for, wildcards accepted.
12819
12820 .PARAMETER Domain
12821
12822 Domain to query for groups, defaults to the current domain.
12823
12824 .PARAMETER DomainController
12825
12826 Domain controller to reflect LDAP queries through.
12827
12828 .PARAMETER LDAP
12829
12830 Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections.
12831 More likely to get around network segmentation, but not as accurate.
12832
12833 .PARAMETER Recurse
12834
12835 Switch. Enumerate all group trust users from all reachable domains recursively.
12836
12837 .PARAMETER PageSize
12838
12839 The PageSize to set for the LDAP searcher object.
12840
12841 .LINK
12842
12843 http://blog.harmj0y.net/
12844#>
12845
12846 [CmdletBinding()]
12847 param(
12848 [String]
12849 $GroupName = '*',
12850
12851 [String]
12852 $Domain,
12853
12854 [String]
12855 $DomainController,
12856
12857 [Switch]
12858 $LDAP,
12859
12860 [Switch]
12861 $Recurse,
12862
12863 [ValidateRange(1,10000)]
12864 [Int]
12865 $PageSize = 200
12866 )
12867
12868 function Get-ForeignGroup {
12869 param(
12870 [String]
12871 $GroupName = '*',
12872
12873 [String]
12874 $Domain,
12875
12876 [String]
12877 $DomainController,
12878
12879 [ValidateRange(1,10000)]
12880 [Int]
12881 $PageSize = 200
12882 )
12883
12884 if(-not $Domain) {
12885 $Domain = (Get-NetDomain).Name
12886 }
12887
12888 $DomainDN = "DC=$($Domain.Replace('.', ',DC='))"
12889 Write-Verbose "DomainDN: $DomainDN"
12890
12891 # standard group names to ignore
12892 $ExcludeGroups = @("Users", "Domain Users", "Guests")
12893
12894 # get all the groupnames for the given domain
12895 Get-NetGroup -GroupName $GroupName -Filter '(member=*)' -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize | Where-Object {
12896 # exclude common large groups
12897 -not ($ExcludeGroups -contains $_.samaccountname) } | ForEach-Object {
12898
12899 $GroupName = $_.samAccountName
12900
12901 $_.member | ForEach-Object {
12902 # filter for foreign SIDs in the cn field for users in another domain,
12903 # or if the DN doesn't end with the proper DN for the queried domain
12904 if (($_ -match 'CN=S-1-5-21.*-.*') -or ($DomainDN -ne ($_.substring($_.IndexOf("DC="))))) {
12905
12906 $UserDomain = $_.subString($_.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
12907 $UserName = $_.split(",")[0].split("=")[1]
12908
12909 $ForeignGroupUser = New-Object PSObject
12910 $ForeignGroupUser | Add-Member Noteproperty 'GroupDomain' $Domain
12911 $ForeignGroupUser | Add-Member Noteproperty 'GroupName' $GroupName
12912 $ForeignGroupUser | Add-Member Noteproperty 'UserDomain' $UserDomain
12913 $ForeignGroupUser | Add-Member Noteproperty 'UserName' $UserName
12914 $ForeignGroupUser | Add-Member Noteproperty 'UserDN' $_
12915 $ForeignGroupUser
12916 }
12917 }
12918 }
12919 }
12920
12921 if ($Recurse) {
12922 # get all rechable domains in the trust mesh and uniquify them
12923 if($LDAP -or $DomainController) {
12924 $DomainTrusts = Invoke-MapDomainTrust -LDAP -DomainController $DomainController -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique
12925 }
12926 else {
12927 $DomainTrusts = Invoke-MapDomainTrust -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique
12928 }
12929
12930 ForEach($DomainTrust in $DomainTrusts) {
12931 # get the trust groups for each domain in the trust mesh
12932 Write-Verbose "Enumerating trust groups in domain $DomainTrust"
12933 Get-ForeignGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -PageSize $PageSize
12934 }
12935 }
12936 else {
12937 Get-ForeignGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -PageSize $PageSize
12938 }
12939}
12940
12941
12942function Find-ManagedSecurityGroups {
12943<#
12944 .SYNOPSIS
12945
12946 This function retrieves all security groups in the domain and identifies ones that
12947 have a manager set. It also determines whether the manager has the ability to add
12948 or remove members from the group.
12949
12950 Author: Stuart Morgan (@ukstufus) <stuart.morgan@mwrinfosecurity.com>
12951 License: BSD 3-Clause
12952
12953 .EXAMPLE
12954
12955 PS C:\> Find-ManagedSecurityGroups | Export-PowerViewCSV -NoTypeInformation group-managers.csv
12956
12957 Store a list of all security groups with managers in group-managers.csv
12958
12959 .DESCRIPTION
12960
12961 Authority to manipulate the group membership of AD security groups and distribution groups
12962 can be delegated to non-administrators by setting the 'managedBy' attribute. This is typically
12963 used to delegate management authority to distribution groups, but Windows supports security groups
12964 being managed in the same way.
12965
12966 This function searches for AD groups which have a group manager set, and determines whether that
12967 user can manipulate group membership. This could be a useful method of horizontal privilege
12968 escalation, especially if the manager can manipulate the membership of a privileged group.
12969
12970 .LINK
12971
12972 https://github.com/PowerShellEmpire/Empire/pull/119
12973
12974#>
12975
12976 # Go through the list of security groups on the domain and identify those who have a manager
12977 Get-NetGroup -FullData -Filter '(managedBy=*)' | Select-Object -Unique distinguishedName,managedBy,cn | ForEach-Object {
12978
12979 # Retrieve the object that the managedBy DN refers to
12980 $group_manager = Get-ADObject -ADSPath $_.managedBy | Select-Object cn,distinguishedname,name,samaccounttype,samaccountname
12981
12982 # Create a results object to store our findings
12983 $results_object = New-Object -TypeName PSObject -Property @{
12984 'GroupCN' = $_.cn
12985 'GroupDN' = $_.distinguishedname
12986 'ManagerCN' = $group_manager.cn
12987 'ManagerDN' = $group_manager.distinguishedName
12988 'ManagerSAN' = $group_manager.samaccountname
12989 'ManagerType' = ''
12990 'CanManagerWrite' = $FALSE
12991 }
12992
12993 # Determine whether the manager is a user or a group
12994 if ($group_manager.samaccounttype -eq 0x10000000) {
12995 $results_object.ManagerType = 'Group'
12996 } elseif ($group_manager.samaccounttype -eq 0x30000000) {
12997 $results_object.ManagerType = 'User'
12998 }
12999
13000 # Find the ACLs that relate to the ability to write to the group
13001 $xacl = Get-ObjectAcl -ADSPath $_.distinguishedname -Rights WriteMembers
13002
13003 # Double-check that the manager
13004 if ($xacl.ObjectType -eq 'bf9679c0-0de6-11d0-a285-00aa003049e2' -and $xacl.AccessControlType -eq 'Allow' -and $xacl.IdentityReference.Value.Contains($group_manager.samaccountname)) {
13005 $results_object.CanManagerWrite = $TRUE
13006 }
13007 $results_object
13008 }
13009}
13010
13011
13012function Invoke-MapDomainTrust {
13013<#
13014 .SYNOPSIS
13015
13016 This function gets all trusts for the current domain,
13017 and tries to get all trusts for each domain it finds.
13018
13019 .PARAMETER LDAP
13020
13021 Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections.
13022 More likely to get around network segmentation, but not as accurate.
13023
13024 .PARAMETER DomainController
13025
13026 Domain controller to reflect LDAP queries through.
13027
13028 .PARAMETER PageSize
13029
13030 The PageSize to set for the LDAP searcher object.
13031
13032 .PARAMETER Credential
13033
13034 A [Management.Automation.PSCredential] object of alternate credentials
13035 for connection to the target domain.
13036
13037 .EXAMPLE
13038
13039 PS C:\> Invoke-MapDomainTrust | Export-CSV -NoTypeInformation trusts.csv
13040
13041 Map all reachable domain trusts and output everything to a .csv file.
13042
13043 .LINK
13044
13045 http://blog.harmj0y.net/
13046#>
13047 [CmdletBinding()]
13048 param(
13049 [Switch]
13050 $LDAP,
13051
13052 [String]
13053 $DomainController,
13054
13055 [ValidateRange(1,10000)]
13056 [Int]
13057 $PageSize = 200,
13058
13059 [Management.Automation.PSCredential]
13060 $Credential
13061 )
13062
13063 # keep track of domains seen so we don't hit infinite recursion
13064 $SeenDomains = @{}
13065
13066 # our domain status tracker
13067 $Domains = New-Object System.Collections.Stack
13068
13069 # get the current domain and push it onto the stack
13070 $CurrentDomain = (Get-NetDomain -Credential $Credential).Name
13071 $Domains.push($CurrentDomain)
13072
13073 while($Domains.Count -ne 0) {
13074
13075 $Domain = $Domains.Pop()
13076
13077 # if we haven't seen this domain before
13078 if ($Domain -and ($Domain.Trim() -ne "") -and (-not $SeenDomains.ContainsKey($Domain))) {
13079
13080 Write-Verbose "Enumerating trusts for domain '$Domain'"
13081
13082 # mark it as seen in our list
13083 $Null = $SeenDomains.add($Domain, "")
13084
13085 try {
13086 # get all the trusts for this domain
13087 if($LDAP -or $DomainController) {
13088 $Trusts = Get-NetDomainTrust -Domain $Domain -LDAP -DomainController $DomainController -PageSize $PageSize -Credential $Credential
13089 }
13090 else {
13091 $Trusts = Get-NetDomainTrust -Domain $Domain -PageSize $PageSize -Credential $Credential
13092 }
13093
13094 if($Trusts -isnot [System.Array]) {
13095 $Trusts = @($Trusts)
13096 }
13097
13098 # get any forest trusts, if they exist
13099 if(-not ($LDAP -or $DomainController) ) {
13100 $Trusts += Get-NetForestTrust -Forest $Domain -Credential $Credential
13101 }
13102
13103 if ($Trusts) {
13104 if($Trusts -isnot [System.Array]) {
13105 $Trusts = @($Trusts)
13106 }
13107
13108 # enumerate each trust found
13109 ForEach ($Trust in $Trusts) {
13110 if($Trust.SourceName -and $Trust.TargetName) {
13111 $SourceDomain = $Trust.SourceName
13112 $TargetDomain = $Trust.TargetName
13113 $TrustType = $Trust.TrustType
13114 $TrustDirection = $Trust.TrustDirection
13115 $ObjectType = $Trust.PSObject.TypeNames | Where-Object {$_ -match 'PowerView'} | Select-Object -First 1
13116
13117 # make sure we process the target
13118 $Null = $Domains.Push($TargetDomain)
13119
13120 # build the nicely-parsable custom output object
13121 $DomainTrust = New-Object PSObject
13122 $DomainTrust | Add-Member Noteproperty 'SourceDomain' "$SourceDomain"
13123 $DomainTrust | Add-Member Noteproperty 'SourceSID' $Trust.SourceSID
13124 $DomainTrust | Add-Member Noteproperty 'TargetDomain' "$TargetDomain"
13125 $DomainTrust | Add-Member Noteproperty 'TargetSID' $Trust.TargetSID
13126 $DomainTrust | Add-Member Noteproperty 'TrustType' "$TrustType"
13127 $DomainTrust | Add-Member Noteproperty 'TrustDirection' "$TrustDirection"
13128 $DomainTrust.PSObject.TypeNames.Add($ObjectType)
13129 $DomainTrust
13130 }
13131 }
13132 }
13133 }
13134 catch {
13135 Write-Verbose "[!] Error: $_"
13136 }
13137 }
13138 }
13139}
13140
13141
13142########################################################
13143#
13144# Expose the Win32API functions and datastructures below
13145# using PSReflect.
13146# Warning: Once these are executed, they are baked in
13147# and can't be changed while the script is running!
13148#
13149########################################################
13150
13151$Mod = New-InMemoryModule -ModuleName Win32
13152
13153# all of the Win32 API functions we need
13154$FunctionDefinitions = @(
13155 (func netapi32 NetShareEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
13156 (func netapi32 NetWkstaUserEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
13157 (func netapi32 NetSessionEnum ([Int]) @([String], [String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
13158 (func netapi32 NetLocalGroupGetMembers ([Int]) @([String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
13159 (func netapi32 DsGetSiteName ([Int]) @([String], [IntPtr].MakeByRefType())),
13160 (func netapi32 DsEnumerateDomainTrusts ([Int]) @([String], [UInt32], [IntPtr].MakeByRefType(), [IntPtr].MakeByRefType())),
13161 (func netapi32 NetApiBufferFree ([Int]) @([IntPtr])),
13162 (func advapi32 ConvertSidToStringSid ([Int]) @([IntPtr], [String].MakeByRefType()) -SetLastError),
13163 (func advapi32 OpenSCManagerW ([IntPtr]) @([String], [String], [Int]) -SetLastError),
13164 (func advapi32 CloseServiceHandle ([Int]) @([IntPtr])),
13165 (func wtsapi32 WTSOpenServerEx ([IntPtr]) @([String])),
13166 (func wtsapi32 WTSEnumerateSessionsEx ([Int]) @([IntPtr], [Int32].MakeByRefType(), [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError),
13167 (func wtsapi32 WTSQuerySessionInformation ([Int]) @([IntPtr], [Int], [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError),
13168 (func wtsapi32 WTSFreeMemoryEx ([Int]) @([Int32], [IntPtr], [Int32])),
13169 (func wtsapi32 WTSFreeMemory ([Int]) @([IntPtr])),
13170 (func wtsapi32 WTSCloseServer ([Int]) @([IntPtr]))
13171)
13172
13173# enum used by $WTS_SESSION_INFO_1 below
13174$WTSConnectState = psenum $Mod WTS_CONNECTSTATE_CLASS UInt16 @{
13175 Active = 0
13176 Connected = 1
13177 ConnectQuery = 2
13178 Shadow = 3
13179 Disconnected = 4
13180 Idle = 5
13181 Listen = 6
13182 Reset = 7
13183 Down = 8
13184 Init = 9
13185}
13186
13187# the WTSEnumerateSessionsEx result structure
13188$WTS_SESSION_INFO_1 = struct $Mod WTS_SESSION_INFO_1 @{
13189 ExecEnvId = field 0 UInt32
13190 State = field 1 $WTSConnectState
13191 SessionId = field 2 UInt32
13192 pSessionName = field 3 String -MarshalAs @('LPWStr')
13193 pHostName = field 4 String -MarshalAs @('LPWStr')
13194 pUserName = field 5 String -MarshalAs @('LPWStr')
13195 pDomainName = field 6 String -MarshalAs @('LPWStr')
13196 pFarmName = field 7 String -MarshalAs @('LPWStr')
13197}
13198
13199# the particular WTSQuerySessionInformation result structure
13200$WTS_CLIENT_ADDRESS = struct $mod WTS_CLIENT_ADDRESS @{
13201 AddressFamily = field 0 UInt32
13202 Address = field 1 Byte[] -MarshalAs @('ByValArray', 20)
13203}
13204
13205# the NetShareEnum result structure
13206$SHARE_INFO_1 = struct $Mod SHARE_INFO_1 @{
13207 shi1_netname = field 0 String -MarshalAs @('LPWStr')
13208 shi1_type = field 1 UInt32
13209 shi1_remark = field 2 String -MarshalAs @('LPWStr')
13210}
13211
13212# the NetWkstaUserEnum result structure
13213$WKSTA_USER_INFO_1 = struct $Mod WKSTA_USER_INFO_1 @{
13214 wkui1_username = field 0 String -MarshalAs @('LPWStr')
13215 wkui1_logon_domain = field 1 String -MarshalAs @('LPWStr')
13216 wkui1_oth_domains = field 2 String -MarshalAs @('LPWStr')
13217 wkui1_logon_server = field 3 String -MarshalAs @('LPWStr')
13218}
13219
13220# the NetSessionEnum result structure
13221$SESSION_INFO_10 = struct $Mod SESSION_INFO_10 @{
13222 sesi10_cname = field 0 String -MarshalAs @('LPWStr')
13223 sesi10_username = field 1 String -MarshalAs @('LPWStr')
13224 sesi10_time = field 2 UInt32
13225 sesi10_idle_time = field 3 UInt32
13226}
13227
13228# enum used by $LOCALGROUP_MEMBERS_INFO_2 below
13229$SID_NAME_USE = psenum $Mod SID_NAME_USE UInt16 @{
13230 SidTypeUser = 1
13231 SidTypeGroup = 2
13232 SidTypeDomain = 3
13233 SidTypeAlias = 4
13234 SidTypeWellKnownGroup = 5
13235 SidTypeDeletedAccount = 6
13236 SidTypeInvalid = 7
13237 SidTypeUnknown = 8
13238 SidTypeComputer = 9
13239}
13240
13241# the NetLocalGroupGetMembers result structure
13242$LOCALGROUP_MEMBERS_INFO_2 = struct $Mod LOCALGROUP_MEMBERS_INFO_2 @{
13243 lgrmi2_sid = field 0 IntPtr
13244 lgrmi2_sidusage = field 1 $SID_NAME_USE
13245 lgrmi2_domainandname = field 2 String -MarshalAs @('LPWStr')
13246}
13247
13248# enums used in DS_DOMAIN_TRUSTS
13249$DsDomainFlag = psenum $Mod DsDomain.Flags UInt32 @{
13250 IN_FOREST = 1
13251 DIRECT_OUTBOUND = 2
13252 TREE_ROOT = 4
13253 PRIMARY = 8
13254 NATIVE_MODE = 16
13255 DIRECT_INBOUND = 32
13256} -Bitfield
13257$DsDomainTrustType = psenum $Mod DsDomain.TrustType UInt32 @{
13258 DOWNLEVEL = 1
13259 UPLEVEL = 2
13260 MIT = 3
13261 DCE = 4
13262}
13263$DsDomainTrustAttributes = psenum $Mod DsDomain.TrustAttributes UInt32 @{
13264 NON_TRANSITIVE = 1
13265 UPLEVEL_ONLY = 2
13266 FILTER_SIDS = 4
13267 FOREST_TRANSITIVE = 8
13268 CROSS_ORGANIZATION = 16
13269 WITHIN_FOREST = 32
13270 TREAT_AS_EXTERNAL = 64
13271}
13272
13273# the DsEnumerateDomainTrusts result structure
13274$DS_DOMAIN_TRUSTS = struct $Mod DS_DOMAIN_TRUSTS @{
13275 NetbiosDomainName = field 0 String -MarshalAs @('LPWStr')
13276 DnsDomainName = field 1 String -MarshalAs @('LPWStr')
13277 Flags = field 2 $DsDomainFlag
13278 ParentIndex = field 3 UInt32
13279 TrustType = field 4 $DsDomainTrustType
13280 TrustAttributes = field 5 $DsDomainTrustAttributes
13281 DomainSid = field 6 IntPtr
13282 DomainGuid = field 7 Guid
13283}
13284
13285$Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32'
13286$Netapi32 = $Types['netapi32']
13287$Advapi32 = $Types['advapi32']
13288$Wtsapi32 = $Types['wtsapi32']