· 5 years ago · Feb 09, 2020, 08:36 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
717function Export-PowerViewCSV {
718<#
719 .SYNOPSIS
720
721 This function exports to a .csv in a thread-safe manner.
722
723 Based partially on Dmitry Sotnikov's Export-CSV code
724 at http://poshcode.org/1590
725
726 .LINK
727
728 http://poshcode.org/1590
729 http://dmitrysotnikov.wordpress.com/2010/01/19/Export-Csv-append/
730#>
731 Param(
732 [Parameter(Mandatory=$True, ValueFromPipeline=$True,
733 ValueFromPipelineByPropertyName=$True)]
734 [System.Management.Automation.PSObject]
735 $InputObject,
736
737 [Parameter(Mandatory=$True, Position=0)]
738 [Alias('PSPath')]
739 [String]
740 $OutFile
741 )
742
743 process {
744
745 $ObjectCSV = $InputObject | ConvertTo-Csv -NoTypeInformation
746
747 # mutex so threaded code doesn't stomp on the output file
748 $Mutex = New-Object System.Threading.Mutex $False,'CSVMutex';
749 $Null = $Mutex.WaitOne()
750
751 if (Test-Path -Path $OutFile) {
752 # hack to skip the first line of output if the file already exists
753 $ObjectCSV | Foreach-Object {$Start=$True}{if ($Start) {$Start=$False} else {$_}} | Out-File -Encoding 'ASCII' -Append -FilePath $OutFile
754 }
755 else {
756 $ObjectCSV | Out-File -Encoding 'ASCII' -Append -FilePath $OutFile
757 }
758
759 $Mutex.ReleaseMutex()
760 }
761}
762
763
764# stolen directly from http://obscuresecurity.blogspot.com/2014/05/touch.html
765function Set-MacAttribute {
766<#
767 .SYNOPSIS
768
769 Sets the modified, accessed and created (Mac) attributes for a file based on another file or input.
770
771 PowerSploit Function: Set-MacAttribute
772 Author: Chris Campbell (@obscuresec)
773 License: BSD 3-Clause
774 Required Dependencies: None
775 Optional Dependencies: None
776 Version: 1.0.0
777
778 .DESCRIPTION
779
780 Set-MacAttribute sets one or more Mac attributes and returns the new attribute values of the file.
781
782 .EXAMPLE
783
784 PS C:\> Set-MacAttribute -FilePath c:\test\newfile -OldFilePath c:\test\oldfile
785
786 .EXAMPLE
787
788 PS C:\> Set-MacAttribute -FilePath c:\demo\test.xt -All "01/03/2006 12:12 pm"
789
790 .EXAMPLE
791
792 PS C:\> Set-MacAttribute -FilePath c:\demo\test.txt -Modified "01/03/2006 12:12 pm" -Accessed "01/03/2006 12:11 pm" -Created "01/03/2006 12:10 pm"
793
794 .LINK
795
796 http://www.obscuresec.com/2014/05/touch.html
797#>
798 [CmdletBinding(DefaultParameterSetName = 'Touch')]
799 Param (
800
801 [Parameter(Position = 1,Mandatory = $True)]
802 [ValidateScript({Test-Path -Path $_ })]
803 [String]
804 $FilePath,
805
806 [Parameter(ParameterSetName = 'Touch')]
807 [ValidateScript({Test-Path -Path $_ })]
808 [String]
809 $OldFilePath,
810
811 [Parameter(ParameterSetName = 'Individual')]
812 [DateTime]
813 $Modified,
814
815 [Parameter(ParameterSetName = 'Individual')]
816 [DateTime]
817 $Accessed,
818
819 [Parameter(ParameterSetName = 'Individual')]
820 [DateTime]
821 $Created,
822
823 [Parameter(ParameterSetName = 'All')]
824 [DateTime]
825 $AllMacAttributes
826 )
827
828 #Helper function that returns an object with the MAC attributes of a file.
829 function Get-MacAttribute {
830
831 param($OldFileName)
832
833 if (!(Test-Path -Path $OldFileName)) {Throw 'File Not Found'}
834 $FileInfoObject = (Get-Item $OldFileName)
835
836 $ObjectProperties = @{'Modified' = ($FileInfoObject.LastWriteTime);
837 'Accessed' = ($FileInfoObject.LastAccessTime);
838 'Created' = ($FileInfoObject.CreationTime)};
839 $ResultObject = New-Object -TypeName PSObject -Property $ObjectProperties
840 Return $ResultObject
841 }
842
843 $FileInfoObject = (Get-Item -Path $FilePath)
844
845 if ($PSBoundParameters['AllMacAttributes']) {
846 $Modified = $AllMacAttributes
847 $Accessed = $AllMacAttributes
848 $Created = $AllMacAttributes
849 }
850
851 if ($PSBoundParameters['OldFilePath']) {
852 $CopyFileMac = (Get-MacAttribute $OldFilePath)
853 $Modified = $CopyFileMac.Modified
854 $Accessed = $CopyFileMac.Accessed
855 $Created = $CopyFileMac.Created
856 }
857
858 if ($Modified) {$FileInfoObject.LastWriteTime = $Modified}
859 if ($Accessed) {$FileInfoObject.LastAccessTime = $Accessed}
860 if ($Created) {$FileInfoObject.CreationTime = $Created}
861
862 Return (Get-MacAttribute $FilePath)
863}
864
865
866function Copy-ClonedFile {
867<#
868 .SYNOPSIS
869
870 Copy a source file to a destination location, matching any MAC
871 properties as appropriate.
872
873 .PARAMETER SourceFile
874
875 Source file to copy.
876
877 .PARAMETER DestFile
878
879 Destination file path to copy file to.
880
881 .EXAMPLE
882
883 PS C:\> Copy-ClonedFile -SourceFile program.exe -DestFile \\WINDOWS7\tools\program.exe
884
885 Copy the local program.exe binary to a remote location, matching the MAC properties of the remote exe.
886
887 .LINK
888
889 http://obscuresecurity.blogspot.com/2014/05/touch.html
890#>
891
892 param(
893 [Parameter(Mandatory = $True)]
894 [String]
895 [ValidateNotNullOrEmpty()]
896 $SourceFile,
897
898 [Parameter(Mandatory = $True)]
899 [String]
900 [ValidateNotNullOrEmpty()]
901 $DestFile
902 )
903
904 # clone the MAC properties
905 Set-MacAttribute -FilePath $SourceFile -OldFilePath $DestFile
906
907 # copy the file off
908 Copy-Item -Path $SourceFile -Destination $DestFile
909}
910
911
912function Get-IPAddress {
913<#
914 .SYNOPSIS
915
916 This function resolves a given hostename to its associated IPv4
917 address. If no hostname is provided, it defaults to returning
918 the IP address of the local host the script be being run on.
919
920 .EXAMPLE
921
922 PS C:\> Get-IPAddress -ComputerName SERVER
923
924 Return the IPv4 address of 'SERVER'
925#>
926
927 [CmdletBinding()]
928 param(
929 [Parameter(Position=0,ValueFromPipeline=$True)]
930 [Alias('HostName')]
931 [String]
932 $ComputerName = ''
933 )
934 process {
935 try {
936 # get the IP resolution of this specified hostname
937 $Results = @(([Net.Dns]::GetHostEntry($ComputerName)).AddressList)
938
939 if ($Results.Count -ne 0) {
940 ForEach ($Result in $Results) {
941 # make sure the returned result is IPv4
942 if ($Result.AddressFamily -eq 'InterNetwork') {
943 $Result.IPAddressToString
944 }
945 }
946 }
947 }
948 catch {
949 Write-Verbose -Message 'Could not resolve host to an IP Address.'
950 }
951 }
952 end {}
953}
954
955
956function Convert-NameToSid {
957<#
958 .SYNOPSIS
959
960 Converts a given user/group name to a security identifier (SID).
961
962 .PARAMETER ObjectName
963
964 The user/group name to convert, can be 'user' or 'DOMAIN\user' format.
965
966 .PARAMETER Domain
967
968 Specific domain for the given user account, defaults to the current domain.
969
970 .EXAMPLE
971
972 PS C:\> Convert-NameToSid 'DEV\dfm'
973#>
974 [CmdletBinding()]
975 param(
976 [Parameter(Mandatory=$True,ValueFromPipeline=$True)]
977 [String]
978 [Alias('Name')]
979 $ObjectName,
980
981 [String]
982 $Domain = (Get-NetDomain).Name
983 )
984
985 process {
986
987 $ObjectName = $ObjectName -replace "/","\"
988
989 if($ObjectName.contains("\")) {
990 # if we get a DOMAIN\user format, auto convert it
991 $Domain = $ObjectName.split("\")[0]
992 $ObjectName = $ObjectName.split("\")[1]
993 }
994
995 try {
996 $Obj = (New-Object System.Security.Principal.NTAccount($Domain,$ObjectName))
997 $Obj.Translate([System.Security.Principal.SecurityIdentifier]).Value
998 }
999 catch {
1000 Write-Verbose "Invalid object/name: $Domain\$ObjectName"
1001 $Null
1002 }
1003 }
1004}
1005
1006
1007function Convert-SidToName {
1008<#
1009 .SYNOPSIS
1010
1011 Converts a security identifier (SID) to a group/user name.
1012
1013 .PARAMETER SID
1014
1015 The SID to convert.
1016
1017 .EXAMPLE
1018
1019 PS C:\> Convert-SidToName S-1-5-21-2620891829-2411261497-1773853088-1105
1020#>
1021 [CmdletBinding()]
1022 param(
1023 [Parameter(Mandatory=$True,ValueFromPipeline=$True)]
1024 [String]
1025 $SID
1026 )
1027
1028 process {
1029 try {
1030 $SID2 = $SID.trim('*')
1031
1032 # try to resolve any built-in SIDs first
1033 # from https://support.microsoft.com/en-us/kb/243330
1034 Switch ($SID2)
1035 {
1036 'S-1-0' { 'Null Authority' }
1037 'S-1-0-0' { 'Nobody' }
1038 'S-1-1' { 'World Authority' }
1039 'S-1-1-0' { 'Everyone' }
1040 'S-1-2' { 'Local Authority' }
1041 'S-1-2-0' { 'Local' }
1042 'S-1-2-1' { 'Console Logon ' }
1043 'S-1-3' { 'Creator Authority' }
1044 'S-1-3-0' { 'Creator Owner' }
1045 'S-1-3-1' { 'Creator Group' }
1046 'S-1-3-2' { 'Creator Owner Server' }
1047 'S-1-3-3' { 'Creator Group Server' }
1048 'S-1-3-4' { 'Owner Rights' }
1049 'S-1-4' { 'Non-unique Authority' }
1050 'S-1-5' { 'NT Authority' }
1051 'S-1-5-1' { 'Dialup' }
1052 'S-1-5-2' { 'Network' }
1053 'S-1-5-3' { 'Batch' }
1054 'S-1-5-4' { 'Interactive' }
1055 'S-1-5-6' { 'Service' }
1056 'S-1-5-7' { 'Anonymous' }
1057 'S-1-5-8' { 'Proxy' }
1058 'S-1-5-9' { 'Enterprise Domain Controllers' }
1059 'S-1-5-10' { 'Principal Self' }
1060 'S-1-5-11' { 'Authenticated Users' }
1061 'S-1-5-12' { 'Restricted Code' }
1062 'S-1-5-13' { 'Terminal Server Users' }
1063 'S-1-5-14' { 'Remote Interactive Logon' }
1064 'S-1-5-15' { 'This Organization ' }
1065 'S-1-5-17' { 'This Organization ' }
1066 'S-1-5-18' { 'Local System' }
1067 'S-1-5-19' { 'NT Authority' }
1068 'S-1-5-20' { 'NT Authority' }
1069 'S-1-5-80-0' { 'All Services ' }
1070 'S-1-5-32-544' { 'BUILTIN\Administrators' }
1071 'S-1-5-32-545' { 'BUILTIN\Users' }
1072 'S-1-5-32-546' { 'BUILTIN\Guests' }
1073 'S-1-5-32-547' { 'BUILTIN\Power Users' }
1074 'S-1-5-32-548' { 'BUILTIN\Account Operators' }
1075 'S-1-5-32-549' { 'BUILTIN\Server Operators' }
1076 'S-1-5-32-550' { 'BUILTIN\Print Operators' }
1077 'S-1-5-32-551' { 'BUILTIN\Backup Operators' }
1078 'S-1-5-32-552' { 'BUILTIN\Replicators' }
1079 'S-1-5-32-554' { 'BUILTIN\Pre-Windows 2000 Compatible Access' }
1080 'S-1-5-32-555' { 'BUILTIN\Remote Desktop Users' }
1081 'S-1-5-32-556' { 'BUILTIN\Network Configuration Operators' }
1082 'S-1-5-32-557' { 'BUILTIN\Incoming Forest Trust Builders' }
1083 'S-1-5-32-558' { 'BUILTIN\Performance Monitor Users' }
1084 'S-1-5-32-559' { 'BUILTIN\Performance Log Users' }
1085 'S-1-5-32-560' { 'BUILTIN\Windows Authorization Access Group' }
1086 'S-1-5-32-561' { 'BUILTIN\Terminal Server License Servers' }
1087 'S-1-5-32-562' { 'BUILTIN\Distributed COM Users' }
1088 'S-1-5-32-569' { 'BUILTIN\Cryptographic Operators' }
1089 'S-1-5-32-573' { 'BUILTIN\Event Log Readers' }
1090 'S-1-5-32-574' { 'BUILTIN\Certificate Service DCOM Access' }
1091 'S-1-5-32-575' { 'BUILTIN\RDS Remote Access Servers' }
1092 'S-1-5-32-576' { 'BUILTIN\RDS Endpoint Servers' }
1093 'S-1-5-32-577' { 'BUILTIN\RDS Management Servers' }
1094 'S-1-5-32-578' { 'BUILTIN\Hyper-V Administrators' }
1095 'S-1-5-32-579' { 'BUILTIN\Access Control Assistance Operators' }
1096 'S-1-5-32-580' { 'BUILTIN\Access Control Assistance Operators' }
1097 Default {
1098 $Obj = (New-Object System.Security.Principal.SecurityIdentifier($SID2))
1099 $Obj.Translate( [System.Security.Principal.NTAccount]).Value
1100 }
1101 }
1102 }
1103 catch {
1104 # Write-Warning "Invalid SID: $SID"
1105 $SID
1106 }
1107 }
1108}
1109
1110
1111function Convert-NT4toCanonical {
1112<#
1113 .SYNOPSIS
1114
1115 Converts a user/group NT4 name (i.e. dev/john) to canonical format.
1116
1117 Based on Bill Stewart's code from this article:
1118 http://windowsitpro.com/active-directory/translating-active-directory-object-names-between-formats
1119
1120 .PARAMETER ObjectName
1121
1122 The user/group name to convert, needs to be in 'DOMAIN\user' format.
1123
1124 .EXAMPLE
1125
1126 PS C:\> Convert-NT4toCanonical -ObjectName "dev\dfm"
1127
1128 Returns "dev.testlab.local/Users/Dave"
1129
1130 .LINK
1131
1132 http://windowsitpro.com/active-directory/translating-active-directory-object-names-between-formats
1133#>
1134 [CmdletBinding()]
1135 param(
1136 [Parameter(Mandatory=$True,ValueFromPipeline=$True)]
1137 [String]
1138 $ObjectName
1139 )
1140
1141 process {
1142
1143 $ObjectName = $ObjectName -replace "/","\"
1144
1145 if($ObjectName.contains("\")) {
1146 # if we get a DOMAIN\user format, try to extract the domain
1147 $Domain = $ObjectName.split("\")[0]
1148 }
1149
1150 # Accessor functions to simplify calls to NameTranslate
1151 function Invoke-Method([__ComObject] $Object, [String] $Method, $Parameters) {
1152 $Output = $Object.GetType().InvokeMember($Method, "InvokeMethod", $Null, $Object, $Parameters)
1153 if ( $Output ) { $Output }
1154 }
1155 function Set-Property([__ComObject] $Object, [String] $Property, $Parameters) {
1156 [Void] $Object.GetType().InvokeMember($Property, "SetProperty", $Null, $Object, $Parameters)
1157 }
1158
1159 $Translate = New-Object -ComObject NameTranslate
1160
1161 try {
1162 Invoke-Method $Translate "Init" (1, $Domain)
1163 }
1164 catch [System.Management.Automation.MethodInvocationException] {
1165 Write-Debug "Error with translate init in Convert-NT4toCanonical: $_"
1166 }
1167
1168 Set-Property $Translate "ChaseReferral" (0x60)
1169
1170 try {
1171 Invoke-Method $Translate "Set" (3, $ObjectName)
1172 (Invoke-Method $Translate "Get" (2))
1173 }
1174 catch [System.Management.Automation.MethodInvocationException] {
1175 Write-Debug "Error with translate Set/Get in Convert-NT4toCanonical: $_"
1176 }
1177 }
1178}
1179
1180
1181function Convert-CanonicaltoNT4 {
1182<#
1183 .SYNOPSIS
1184
1185 Converts a user@fqdn to NT4 format.
1186
1187 .PARAMETER ObjectName
1188
1189 The user/group name to convert, needs to be in 'DOMAIN\user' format.
1190
1191 .LINK
1192
1193 http://windowsitpro.com/active-directory/translating-active-directory-object-names-between-formats
1194#>
1195
1196 [CmdletBinding()]
1197 param(
1198 [String] $ObjectName
1199 )
1200
1201 $Domain = ($ObjectName -split "@")[1]
1202
1203 $ObjectName = $ObjectName -replace "/","\"
1204
1205 # Accessor functions to simplify calls to NameTranslate
1206 function Invoke-Method([__ComObject] $object, [String] $method, $parameters) {
1207 $output = $object.GetType().InvokeMember($method, "InvokeMethod", $NULL, $object, $parameters)
1208 if ( $output ) { $output }
1209 }
1210 function Set-Property([__ComObject] $object, [String] $property, $parameters) {
1211 [Void] $object.GetType().InvokeMember($property, "SetProperty", $NULL, $object, $parameters)
1212 }
1213
1214 $Translate = New-Object -comobject NameTranslate
1215
1216 try {
1217 Invoke-Method $Translate "Init" (1, $Domain)
1218 }
1219 catch [System.Management.Automation.MethodInvocationException] { }
1220
1221 Set-Property $Translate "ChaseReferral" (0x60)
1222
1223 try {
1224 Invoke-Method $Translate "Set" (5, $ObjectName)
1225 (Invoke-Method $Translate "Get" (3))
1226 }
1227 catch [System.Management.Automation.MethodInvocationException] { $_ }
1228}
1229
1230
1231function ConvertFrom-UACValue {
1232<#
1233 .SYNOPSIS
1234
1235 Converts a UAC int value to human readable form.
1236
1237 .PARAMETER Value
1238
1239 The int UAC value to convert.
1240
1241 .PARAMETER ShowAll
1242
1243 Show all UAC values, with a + indicating the value is currently set.
1244
1245 .EXAMPLE
1246
1247 PS C:\> ConvertFrom-UACValue -Value 66176
1248
1249 Convert the UAC value 66176 to human readable format.
1250
1251 .EXAMPLE
1252
1253 PS C:\> Get-NetUser jason | select useraccountcontrol | ConvertFrom-UACValue
1254
1255 Convert the UAC value for 'jason' to human readable format.
1256
1257 .EXAMPLE
1258
1259 PS C:\> Get-NetUser jason | select useraccountcontrol | ConvertFrom-UACValue -ShowAll
1260
1261 Convert the UAC value for 'jason' to human readable format, showing all
1262 possible UAC values.
1263#>
1264
1265 [CmdletBinding()]
1266 param(
1267 [Parameter(ValueFromPipeline=$True)]
1268 $Value,
1269
1270 [Switch]
1271 $ShowAll
1272 )
1273
1274 begin {
1275
1276 # values from https://support.microsoft.com/en-us/kb/305144
1277 $UACValues = New-Object System.Collections.Specialized.OrderedDictionary
1278 $UACValues.Add("SCRIPT", 1)
1279 $UACValues.Add("ACCOUNTDISABLE", 2)
1280 $UACValues.Add("HOMEDIR_REQUIRED", 8)
1281 $UACValues.Add("LOCKOUT", 16)
1282 $UACValues.Add("PASSWD_NOTREQD", 32)
1283 $UACValues.Add("PASSWD_CANT_CHANGE", 64)
1284 $UACValues.Add("ENCRYPTED_TEXT_PWD_ALLOWED", 128)
1285 $UACValues.Add("TEMP_DUPLICATE_ACCOUNT", 256)
1286 $UACValues.Add("NORMAL_ACCOUNT", 512)
1287 $UACValues.Add("INTERDOMAIN_TRUST_ACCOUNT", 2048)
1288 $UACValues.Add("WORKSTATION_TRUST_ACCOUNT", 4096)
1289 $UACValues.Add("SERVER_TRUST_ACCOUNT", 8192)
1290 $UACValues.Add("DONT_EXPIRE_PASSWORD", 65536)
1291 $UACValues.Add("MNS_LOGON_ACCOUNT", 131072)
1292 $UACValues.Add("SMARTCARD_REQUIRED", 262144)
1293 $UACValues.Add("TRUSTED_FOR_DELEGATION", 524288)
1294 $UACValues.Add("NOT_DELEGATED", 1048576)
1295 $UACValues.Add("USE_DES_KEY_ONLY", 2097152)
1296 $UACValues.Add("DONT_REQ_PREAUTH", 4194304)
1297 $UACValues.Add("PASSWORD_EXPIRED", 8388608)
1298 $UACValues.Add("TRUSTED_TO_AUTH_FOR_DELEGATION", 16777216)
1299 $UACValues.Add("PARTIAL_SECRETS_ACCOUNT", 67108864)
1300
1301 }
1302
1303 process {
1304
1305 $ResultUACValues = New-Object System.Collections.Specialized.OrderedDictionary
1306
1307 if($Value -is [Int]) {
1308 $IntValue = $Value
1309 }
1310
1311 if ($Value -is [PSCustomObject]) {
1312 if($Value.useraccountcontrol) {
1313 $IntValue = $Value.useraccountcontrol
1314 }
1315 }
1316
1317 if($IntValue) {
1318
1319 if($ShowAll) {
1320 foreach ($UACValue in $UACValues.GetEnumerator()) {
1321 if( ($IntValue -band $UACValue.Value) -eq $UACValue.Value) {
1322 $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)+")
1323 }
1324 else {
1325 $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)")
1326 }
1327 }
1328 }
1329 else {
1330 foreach ($UACValue in $UACValues.GetEnumerator()) {
1331 if( ($IntValue -band $UACValue.Value) -eq $UACValue.Value) {
1332 $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)")
1333 }
1334 }
1335 }
1336 }
1337
1338 $ResultUACValues
1339 }
1340}
1341
1342
1343function Get-Proxy {
1344<#
1345 .SYNOPSIS
1346
1347 Enumerates the proxy server and WPAD conents for the current user.
1348
1349 .PARAMETER ComputerName
1350
1351 The computername to enumerate proxy settings on, defaults to local host.
1352
1353 .EXAMPLE
1354
1355 PS C:\> Get-Proxy
1356
1357 Returns the current proxy settings.
1358#>
1359 param(
1360 [Parameter(ValueFromPipeline=$True)]
1361 [ValidateNotNullOrEmpty()]
1362 [String]
1363 $ComputerName = $ENV:COMPUTERNAME
1364 )
1365
1366 process {
1367 try {
1368 $Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('CurrentUser', $ComputerName)
1369 $RegKey = $Reg.OpenSubkey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Internet Settings")
1370 $ProxyServer = $RegKey.GetValue('ProxyServer')
1371 $AutoConfigURL = $RegKey.GetValue('AutoConfigURL')
1372
1373 if($AutoConfigURL -and ($AutoConfigURL -ne "")) {
1374 try {
1375 $Wpad = (New-Object Net.Webclient).DownloadString($AutoConfigURL)
1376 }
1377 catch {
1378 $Wpad = ""
1379 }
1380 }
1381 else {
1382 $Wpad = ""
1383 }
1384
1385 if($ProxyServer -or $AutoConfigUrl) {
1386
1387 $Properties = @{
1388 'ProxyServer' = $ProxyServer
1389 'AutoConfigURL' = $AutoConfigURL
1390 'Wpad' = $Wpad
1391 }
1392
1393 New-Object -TypeName PSObject -Property $Properties
1394 }
1395 else {
1396 Write-Warning "No proxy settings found for $ComputerName"
1397 }
1398 }
1399 catch {
1400 Write-Warning "Error enumerating proxy settings for $ComputerName"
1401 }
1402 }
1403}
1404
1405
1406function Get-PathAcl {
1407
1408 [CmdletBinding()]
1409 param(
1410 [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
1411 [string]
1412 $Path,
1413
1414 [Switch]
1415 $Recurse
1416 )
1417
1418 begin {
1419
1420 function Convert-FileRight {
1421
1422 # From http://stackoverflow.com/questions/28029872/retrieving-security-descriptor-and-getting-number-for-filesystemrights
1423
1424 [CmdletBinding()]
1425 param(
1426 [Int]
1427 $FSR
1428 )
1429
1430 $AccessMask = @{
1431 [uint32]'0x80000000' = 'GenericRead'
1432 [uint32]'0x40000000' = 'GenericWrite'
1433 [uint32]'0x20000000' = 'GenericExecute'
1434 [uint32]'0x10000000' = 'GenericAll'
1435 [uint32]'0x02000000' = 'MaximumAllowed'
1436 [uint32]'0x01000000' = 'AccessSystemSecurity'
1437 [uint32]'0x00100000' = 'Synchronize'
1438 [uint32]'0x00080000' = 'WriteOwner'
1439 [uint32]'0x00040000' = 'WriteDAC'
1440 [uint32]'0x00020000' = 'ReadControl'
1441 [uint32]'0x00010000' = 'Delete'
1442 [uint32]'0x00000100' = 'WriteAttributes'
1443 [uint32]'0x00000080' = 'ReadAttributes'
1444 [uint32]'0x00000040' = 'DeleteChild'
1445 [uint32]'0x00000020' = 'Execute/Traverse'
1446 [uint32]'0x00000010' = 'WriteExtendedAttributes'
1447 [uint32]'0x00000008' = 'ReadExtendedAttributes'
1448 [uint32]'0x00000004' = 'AppendData/AddSubdirectory'
1449 [uint32]'0x00000002' = 'WriteData/AddFile'
1450 [uint32]'0x00000001' = 'ReadData/ListDirectory'
1451 }
1452
1453 $SimplePermissions = @{
1454 [uint32]'0x1f01ff' = 'FullControl'
1455 [uint32]'0x0301bf' = 'Modify'
1456 [uint32]'0x0200a9' = 'ReadAndExecute'
1457 [uint32]'0x02019f' = 'ReadAndWrite'
1458 [uint32]'0x020089' = 'Read'
1459 [uint32]'0x000116' = 'Write'
1460 }
1461
1462 $Permissions = @()
1463
1464 # get simple permission
1465 $Permissions += $SimplePermissions.Keys | % {
1466 if (($FSR -band $_) -eq $_) {
1467 $SimplePermissions[$_]
1468 $FSR = $FSR -band (-not $_)
1469 }
1470 }
1471
1472 # get remaining extended permissions
1473 $Permissions += $AccessMask.Keys |
1474 ? { $FSR -band $_ } |
1475 % { $AccessMask[$_] }
1476
1477 ($Permissions | ?{$_}) -join ","
1478 }
1479 }
1480
1481 process {
1482
1483 try {
1484 $ACL = Get-Acl -Path $Path
1485
1486 $ACL.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier]) | ForEach-Object {
1487
1488 $Names = @()
1489 if ($_.IdentityReference -match '^S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+') {
1490 $Object = Get-ADObject -SID $_.IdentityReference
1491 $Names = @()
1492 $SIDs = @($Object.objectsid)
1493
1494 if ($Recurse -and ($Object.samAccountType -ne "805306368")) {
1495 $SIDs += Get-NetGroupMember -SID $Object.objectsid | Select-Object -ExpandProperty MemberSid
1496 }
1497
1498 $SIDs | ForEach-Object {
1499 $Names += ,@($_, (Convert-SidToName $_))
1500 }
1501 }
1502 else {
1503 $Names += ,@($_.IdentityReference.Value, (Convert-SidToName $_.IdentityReference.Value))
1504 }
1505
1506 ForEach($Name in $Names) {
1507 $Out = New-Object PSObject
1508 $Out | Add-Member Noteproperty 'Path' $Path
1509 $Out | Add-Member Noteproperty 'FileSystemRights' (Convert-FileRight -FSR $_.FileSystemRights.value__)
1510 $Out | Add-Member Noteproperty 'IdentityReference' $Name[1]
1511 $Out | Add-Member Noteproperty 'IdentitySID' $Name[0]
1512 $Out | Add-Member Noteproperty 'AccessControlType' $_.AccessControlType
1513 $Out
1514 }
1515 }
1516 }
1517 catch {
1518 Write-Warning $_
1519 }
1520 }
1521}
1522
1523
1524function Get-NameField {
1525 # function that attempts to extract the appropriate field name
1526 # from various passed objects. This is so functions can have
1527 # multiple types of objects passed on the pipeline.
1528 [CmdletBinding()]
1529 param(
1530 [Parameter(Mandatory=$True,ValueFromPipeline=$True)]
1531 $Object
1532 )
1533 process {
1534 if($Object) {
1535 if ( [bool]($Object.PSobject.Properties.name -match "dnshostname") ) {
1536 # objects from Get-NetComputer
1537 $Object.dnshostname
1538 }
1539 elseif ( [bool]($Object.PSobject.Properties.name -match "name") ) {
1540 # objects from Get-NetDomainController
1541 $Object.name
1542 }
1543 else {
1544 # strings and catch alls
1545 $Object
1546 }
1547 }
1548 else {
1549 return $Null
1550 }
1551 }
1552}
1553
1554
1555function Convert-LDAPProperty {
1556 # helper to convert specific LDAP property result fields
1557 param(
1558 [Parameter(Mandatory=$True,ValueFromPipeline=$True)]
1559 [ValidateNotNullOrEmpty()]
1560 $Properties
1561 )
1562
1563 $ObjectProperties = @{}
1564
1565 $Properties.PropertyNames | ForEach-Object {
1566 if (($_ -eq "objectsid") -or ($_ -eq "sidhistory")) {
1567 # convert the SID to a string
1568 $ObjectProperties[$_] = (New-Object System.Security.Principal.SecurityIdentifier($Properties[$_][0],0)).Value
1569 }
1570 elseif($_ -eq "objectguid") {
1571 # convert the GUID to a string
1572 $ObjectProperties[$_] = (New-Object Guid (,$Properties[$_][0])).Guid
1573 }
1574 elseif( ($_ -eq "lastlogon") -or ($_ -eq "lastlogontimestamp") -or ($_ -eq "pwdlastset") -or ($_ -eq "lastlogoff") -or ($_ -eq "badPasswordTime") ) {
1575 # convert timestamps
1576 if ($Properties[$_][0] -is [System.MarshalByRefObject]) {
1577 # if we have a System.__ComObject
1578 $Temp = $Properties[$_][0]
1579 [Int32]$High = $Temp.GetType().InvokeMember("HighPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null)
1580 [Int32]$Low = $Temp.GetType().InvokeMember("LowPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null)
1581 $ObjectProperties[$_] = ([datetime]::FromFileTime([Int64]("0x{0:x8}{1:x8}" -f $High, $Low)))
1582 }
1583 else {
1584 $ObjectProperties[$_] = ([datetime]::FromFileTime(($Properties[$_][0])))
1585 }
1586 }
1587 elseif($Properties[$_][0] -is [System.MarshalByRefObject]) {
1588 # convert misc com objects
1589 $Prop = $Properties[$_]
1590 try {
1591 $Temp = $Prop[$_][0]
1592 Write-Verbose $_
1593 [Int32]$High = $Temp.GetType().InvokeMember("HighPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null)
1594 [Int32]$Low = $Temp.GetType().InvokeMember("LowPart", [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null)
1595 $ObjectProperties[$_] = [Int64]("0x{0:x8}{1:x8}" -f $High, $Low)
1596 }
1597 catch {
1598 $ObjectProperties[$_] = $Prop[$_]
1599 }
1600 }
1601 elseif($Properties[$_].count -eq 1) {
1602 $ObjectProperties[$_] = $Properties[$_][0]
1603 }
1604 else {
1605 $ObjectProperties[$_] = $Properties[$_]
1606 }
1607 }
1608
1609 New-Object -TypeName PSObject -Property $ObjectProperties
1610}
1611
1612
1613
1614########################################################
1615#
1616# Domain info functions below.
1617#
1618########################################################
1619
1620function Get-DomainSearcher {
1621<#
1622 .SYNOPSIS
1623
1624 Helper used by various functions that takes an ADSpath and
1625 domain specifier and builds the correct ADSI searcher object.
1626
1627 .PARAMETER Domain
1628
1629 The domain to use for the query, defaults to the current domain.
1630
1631 .PARAMETER DomainController
1632
1633 Domain controller to reflect LDAP queries through.
1634
1635 .PARAMETER ADSpath
1636
1637 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
1638 Useful for OU queries.
1639
1640 .PARAMETER ADSprefix
1641
1642 Prefix to set for the searcher (like "CN=Sites,CN=Configuration")
1643
1644 .PARAMETER PageSize
1645
1646 The PageSize to set for the LDAP searcher object.
1647
1648 .EXAMPLE
1649
1650 PS C:\> Get-DomainSearcher -Domain testlab.local
1651
1652 .EXAMPLE
1653
1654 PS C:\> Get-DomainSearcher -Domain testlab.local -DomainController SECONDARY.dev.testlab.local
1655#>
1656
1657 [CmdletBinding()]
1658 param(
1659 [String]
1660 $Domain,
1661
1662 [String]
1663 $DomainController,
1664
1665 [String]
1666 $ADSpath,
1667
1668 [String]
1669 $ADSprefix,
1670
1671 [ValidateRange(1,10000)]
1672 [Int]
1673 $PageSize = 200
1674 )
1675
1676 if(!$Domain) {
1677 $Domain = (Get-NetDomain).name
1678 }
1679 else {
1680 if(!$DomainController) {
1681 try {
1682 # if there's no -DomainController specified, try to pull the primary DC
1683 # to reflect queries through
1684 $DomainController = ((Get-NetDomain).PdcRoleOwner).Name
1685 }
1686 catch {
1687 throw "Get-DomainSearcher: Error in retrieving PDC for current domain"
1688 }
1689 }
1690 }
1691
1692 $SearchString = "LDAP://"
1693
1694 if($DomainController) {
1695 $SearchString += $DomainController + "/"
1696 }
1697 if($ADSprefix) {
1698 $SearchString += $ADSprefix + ","
1699 }
1700
1701 if($ADSpath) {
1702 if($ADSpath -like "GC://*") {
1703 # if we're searching the global catalog
1704 $DistinguishedName = $AdsPath
1705 $SearchString = ""
1706 }
1707 else {
1708 if($ADSpath -like "LDAP://*") {
1709 $ADSpath = $ADSpath.Substring(7)
1710 }
1711 $DistinguishedName = $ADSpath
1712 }
1713 }
1714 else {
1715 $DistinguishedName = "DC=$($Domain.Replace('.', ',DC='))"
1716 }
1717
1718 $SearchString += $DistinguishedName
1719 Write-Verbose "Get-DomainSearcher search string: $SearchString"
1720
1721 $Searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]$SearchString)
1722 $Searcher.PageSize = $PageSize
1723 $Searcher
1724}
1725
1726
1727function Get-NetDomain {
1728<#
1729 .SYNOPSIS
1730
1731 Returns a given domain object.
1732
1733 .PARAMETER Domain
1734
1735 The domain name to query for, defaults to the current domain.
1736
1737 .EXAMPLE
1738
1739 PS C:\> Get-NetDomain -Domain testlab.local
1740
1741 .LINK
1742
1743 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
1744#>
1745
1746 [CmdletBinding()]
1747 param(
1748 [Parameter(ValueFromPipeline=$True)]
1749 [String]
1750 $Domain
1751 )
1752
1753 process {
1754 if($Domain) {
1755 $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain)
1756 try {
1757 [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
1758 }
1759 catch {
1760 Write-Warning "The specified domain $Domain does not exist, could not be contacted, or there isn't an existing trust."
1761 $Null
1762 }
1763 }
1764 else {
1765 [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
1766 }
1767 }
1768}
1769
1770
1771function Get-NetForest {
1772<#
1773 .SYNOPSIS
1774
1775 Returns a given forest object.
1776
1777 .PARAMETER Forest
1778
1779 The forest name to query for, defaults to the current domain.
1780
1781 .EXAMPLE
1782
1783 PS C:\> Get-NetForest -Forest external.domain
1784#>
1785
1786 [CmdletBinding()]
1787 param(
1788 [Parameter(ValueFromPipeline=$True)]
1789 [String]
1790 $Forest
1791 )
1792
1793 process {
1794 if($Forest) {
1795 $ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Forest)
1796 try {
1797 $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
1798 }
1799 catch {
1800 Write-Debug "The specified forest $Forest does not exist, could not be contacted, or there isn't an existing trust."
1801 $Null
1802 }
1803 }
1804 else {
1805 # otherwise use the current forest
1806 $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
1807 }
1808
1809 if($ForestObject) {
1810 # get the SID of the forest root
1811 $ForestSid = (New-Object System.Security.Principal.NTAccount($ForestObject.RootDomain,"krbtgt")).Translate([System.Security.Principal.SecurityIdentifier]).Value
1812 $Parts = $ForestSid -Split "-"
1813 $ForestSid = $Parts[0..$($Parts.length-2)] -join "-"
1814 $ForestObject | Add-Member NoteProperty 'RootDomainSid' $ForestSid
1815 $ForestObject
1816 }
1817 }
1818}
1819
1820
1821function Get-NetForestDomain {
1822<#
1823 .SYNOPSIS
1824
1825 Return all domains for a given forest.
1826
1827 .PARAMETER Forest
1828
1829 The forest name to query domain for.
1830
1831 .PARAMETER Domain
1832
1833 Return domains that match this term/wildcard.
1834
1835 .EXAMPLE
1836
1837 PS C:\> Get-NetForestDomain
1838
1839 .EXAMPLE
1840
1841 PS C:\> Get-NetForestDomain -Forest external.local
1842#>
1843
1844 [CmdletBinding()]
1845 param(
1846 [Parameter(ValueFromPipeline=$True)]
1847 [String]
1848 $Forest,
1849
1850 [String]
1851 $Domain
1852 )
1853
1854 process {
1855 if($Domain) {
1856 # try to detect a wild card so we use -like
1857 if($Domain.Contains('*')) {
1858 (Get-NetForest -Forest $Forest).Domains | Where-Object {$_.Name -like $Domain}
1859 }
1860 else {
1861 # match the exact domain name if there's not a wildcard
1862 (Get-NetForest -Forest $Forest).Domains | Where-Object {$_.Name.ToLower() -eq $Domain.ToLower()}
1863 }
1864 }
1865 else {
1866 # return all domains
1867 $ForestObject = Get-NetForest -Forest $Forest
1868 if($ForestObject) {
1869 $ForestObject.Domains
1870 }
1871 }
1872 }
1873}
1874
1875
1876function Get-NetForestCatalog {
1877<#
1878 .SYNOPSIS
1879
1880 Return all global catalogs for a given forest.
1881
1882 .PARAMETER Forest
1883
1884 The forest name to query domain for.
1885
1886 .EXAMPLE
1887
1888 PS C:\> Get-NetForestCatalog
1889#>
1890
1891 [CmdletBinding()]
1892 param(
1893 [Parameter(ValueFromPipeline=$True)]
1894 [String]
1895 $Forest
1896 )
1897
1898 process {
1899 $ForestObject = Get-NetForest -Forest $Forest
1900 if($ForestObject) {
1901 $ForestObject.FindAllGlobalCatalogs()
1902 }
1903 }
1904}
1905
1906
1907function Get-NetDomainController {
1908<#
1909 .SYNOPSIS
1910
1911 Return the current domain controllers for the active domain.
1912
1913 .PARAMETER Domain
1914
1915 The domain to query for domain controllers, defaults to the current domain.
1916
1917 .PARAMETER DomainController
1918
1919 Domain controller to reflect LDAP queries through.
1920
1921 .PARAMETER LDAP
1922
1923 Switch. Use LDAP queries to determine the domain controllers.
1924
1925 .EXAMPLE
1926
1927 PS C:\> Get-NetDomainController -Domain test
1928#>
1929
1930 [CmdletBinding()]
1931 param(
1932 [Parameter(ValueFromPipeline=$True)]
1933 [String]
1934 $Domain,
1935
1936 [String]
1937 $DomainController,
1938
1939 [Switch]
1940 $LDAP
1941 )
1942
1943 process {
1944 if($LDAP -or $DomainController) {
1945 # filter string to return all domain controllers
1946 Get-NetComputer -Domain $Domain -DomainController $DomainController -FullData -Filter '(userAccountControl:1.2.840.113556.1.4.803:=8192)'
1947 }
1948 else {
1949 $FoundDomain = Get-NetDomain -Domain $Domain
1950
1951 if($FoundDomain) {
1952 $Founddomain.DomainControllers
1953 }
1954 }
1955 }
1956}
1957
1958
1959########################################################
1960#
1961# "net *" replacements and other fun start below
1962#
1963########################################################
1964
1965function Get-NetUser {
1966<#
1967 .SYNOPSIS
1968
1969 Query information for a given user or users in the domain
1970 using ADSI and LDAP. Another -Domain can be specified to
1971 query for users across a trust.
1972 Replacement for "net users /domain"
1973
1974 .PARAMETER UserName
1975
1976 Username filter string, wildcards accepted.
1977
1978 .PARAMETER Domain
1979
1980 The domain to query for users, defaults to the current domain.
1981
1982 .PARAMETER DomainController
1983
1984 Domain controller to reflect LDAP queries through.
1985
1986 .PARAMETER ADSpath
1987
1988 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
1989 Useful for OU queries.
1990
1991 .PARAMETER Filter
1992
1993 A customized ldap filter string to use, e.g. "(description=*admin*)"
1994
1995 .PARAMETER AdminCount
1996
1997 Switch. Return users with adminCount=1.
1998
1999 .PARAMETER SPN
2000
2001 Switch. Only return user objects with non-null service principal names.
2002
2003 .PARAMETER Unconstrained
2004
2005 Switch. Return users that have unconstrained delegation.
2006
2007 .PARAMETER AllowDelegation
2008
2009 Switch. Return user accounts that are not marked as 'sensitive and not allowed for delegation'
2010
2011 .PARAMETER PageSize
2012
2013 The PageSize to set for the LDAP searcher object.
2014
2015 .EXAMPLE
2016
2017 PS C:\> Get-NetUser -Domain testing
2018
2019 .EXAMPLE
2020
2021 PS C:\> Get-NetUser -ADSpath "LDAP://OU=secret,DC=testlab,DC=local"
2022#>
2023
2024 [CmdletBinding()]
2025 param(
2026 [Parameter(ValueFromPipeline=$True)]
2027 [String]
2028 $UserName,
2029
2030 [String]
2031 $Domain,
2032
2033 [String]
2034 $DomainController,
2035
2036 [String]
2037 $ADSpath,
2038
2039 [String]
2040 $Filter,
2041
2042 [Switch]
2043 $SPN,
2044
2045 [Switch]
2046 $AdminCount,
2047
2048 [Switch]
2049 $Unconstrained,
2050
2051 [Switch]
2052 $AllowDelegation,
2053
2054 [ValidateRange(1,10000)]
2055 [Int]
2056 $PageSize = 200
2057 )
2058
2059 begin {
2060 # so this isn't repeated if users are passed on the pipeline
2061 $UserSearcher = Get-DomainSearcher -Domain $Domain -ADSpath $ADSpath -DomainController $DomainController -PageSize $PageSize
2062 }
2063
2064 process {
2065 if($UserSearcher) {
2066
2067 # if we're checking for unconstrained delegation
2068 if($Unconstrained) {
2069 Write-Verbose "Checking for unconstrained delegation"
2070 $Filter += "(userAccountControl:1.2.840.113556.1.4.803:=524288)"
2071 }
2072 if($AllowDelegation) {
2073 Write-Verbose "Checking for users who can be delegated"
2074 # negation of "Accounts that are sensitive and not trusted for delegation"
2075 $Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=1048574))"
2076 }
2077 if($AdminCount) {
2078 Write-Verbose "Checking for adminCount=1"
2079 $Filter += "(admincount=1)"
2080 }
2081
2082 # check if we're using a username filter or not
2083 if($UserName) {
2084 # samAccountType=805306368 indicates user objects
2085 $UserSearcher.filter="(&(samAccountType=805306368)(samAccountName=$UserName)$Filter)"
2086 }
2087 elseif($SPN) {
2088 $UserSearcher.filter="(&(samAccountType=805306368)(servicePrincipalName=*)$Filter)"
2089 }
2090 else {
2091 # filter is something like "(samAccountName=*blah*)" if specified
2092 $UserSearcher.filter="(&(samAccountType=805306368)$Filter)"
2093 }
2094
2095 $UserSearcher.FindAll() | Where-Object {$_} | ForEach-Object {
2096 # convert/process the LDAP fields for each result
2097 Convert-LDAPProperty -Properties $_.Properties
2098 }
2099 }
2100 }
2101}
2102
2103
2104function Add-NetUser {
2105<#
2106 .SYNOPSIS
2107
2108 Adds a domain user or a local user to the current (or remote) machine,
2109 if permissions allow, utilizing the WinNT service provider and
2110 DirectoryServices.AccountManagement, respectively.
2111
2112 The default behavior is to add a user to the local machine.
2113 An optional group name to add the user to can be specified.
2114
2115 .PARAMETER UserName
2116
2117 The username to add. If not given, it defaults to 'backdoor'
2118
2119 .PARAMETER Password
2120
2121 The password to set for the added user. If not given, it defaults to 'Password123!'
2122
2123 .PARAMETER GroupName
2124
2125 Group to optionally add the user to.
2126
2127 .PARAMETER ComputerName
2128
2129 Hostname to add the local user to, defaults to 'localhost'
2130
2131 .PARAMETER Domain
2132
2133 Specified domain to add the user to.
2134
2135 .EXAMPLE
2136
2137 PS C:\> Add-NetUser -UserName john -Password 'Password123!'
2138
2139 Adds a localuser 'john' to the local machine with password of 'Password123!'
2140
2141 .EXAMPLE
2142
2143 PS C:\> Add-NetUser -UserName john -Password 'Password123!' -ComputerName server.testlab.local
2144
2145 Adds a localuser 'john' with password of 'Password123!' to server.testlab.local's local Administrators group.
2146
2147 .EXAMPLE
2148
2149 PS C:\> Add-NetUser -UserName john -Password password -GroupName "Domain Admins" -Domain ''
2150
2151 Adds the user "john" with password "password" to the current domain and adds
2152 the user to the domain group "Domain Admins"
2153
2154 .EXAMPLE
2155
2156 PS C:\> Add-NetUser -UserName john -Password password -GroupName "Domain Admins" -Domain 'testing'
2157
2158 Adds the user "john" with password "password" to the 'testing' domain and adds
2159 the user to the domain group "Domain Admins"
2160
2161 .Link
2162
2163 http://blogs.technet.com/b/heyscriptingguy/archive/2010/11/23/use-powershell-to-create-local-user-accounts.aspx
2164#>
2165
2166 [CmdletBinding()]
2167 Param (
2168 [ValidateNotNullOrEmpty()]
2169 [String]
2170 $UserName = 'backdoor',
2171
2172 [ValidateNotNullOrEmpty()]
2173 [String]
2174 $Password = 'Password123!',
2175
2176 [ValidateNotNullOrEmpty()]
2177 [String]
2178 $GroupName,
2179
2180 [ValidateNotNullOrEmpty()]
2181 [Alias('HostName')]
2182 [String]
2183 $ComputerName = 'localhost',
2184
2185 [ValidateNotNullOrEmpty()]
2186 [String]
2187 $Domain
2188 )
2189
2190 if ($Domain) {
2191
2192 $DomainObject = Get-NetDomain -Domain $Domain
2193 if(-not $DomainObject) {
2194 Write-Warning "Error in grabbing $Domain object"
2195 return $Null
2196 }
2197
2198 # add the assembly we need
2199 Add-Type -AssemblyName System.DirectoryServices.AccountManagement
2200
2201 # http://richardspowershellblog.wordpress.com/2008/05/25/system-directoryservices-accountmanagement/
2202 # get the domain context
2203 $Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Domain), $DomainObject
2204
2205 # create the user object
2206 $User = New-Object -TypeName System.DirectoryServices.AccountManagement.UserPrincipal -ArgumentList $Context
2207
2208 # set user properties
2209 $User.Name = $UserName
2210 $User.SamAccountName = $UserName
2211 $User.PasswordNotRequired = $False
2212 $User.SetPassword($Password)
2213 $User.Enabled = $True
2214
2215 Write-Verbose "Creating user $UserName to with password '$Password' in domain $Domain"
2216
2217 try {
2218 # commit the user
2219 $User.Save()
2220 "[*] User $UserName successfully created in domain $Domain"
2221 }
2222 catch {
2223 Write-Warning '[!] User already exists!'
2224 return
2225 }
2226 }
2227 else {
2228
2229 Write-Verbose "Creating user $UserName to with password '$Password' on $ComputerName"
2230
2231 # if it's not a domain add, it's a local machine add
2232 $ObjOu = [ADSI]"WinNT://$ComputerName"
2233 $ObjUser = $ObjOu.Create('User', $UserName)
2234 $ObjUser.SetPassword($Password)
2235
2236 # commit the changes to the local machine
2237 try {
2238 $Null = $ObjUser.SetInfo()
2239 "[*] User $UserName successfully created on host $ComputerName"
2240 }
2241 catch {
2242 Write-Warning '[!] Account already exists!'
2243 return
2244 }
2245 }
2246
2247 # if a group is specified, invoke Add-NetGroupUser and return its value
2248 if ($GroupName) {
2249 # if we're adding the user to a domain
2250 if ($Domain) {
2251 Add-NetGroupUser -UserName $UserName -GroupName $GroupName -Domain $Domain
2252 "[*] User $UserName successfully added to group $GroupName in domain $Domain"
2253 }
2254 # otherwise, we're adding to a local group
2255 else {
2256 Add-NetGroupUser -UserName $UserName -GroupName $GroupName -ComputerName $ComputerName
2257 "[*] User $UserName successfully added to group $GroupName on host $ComputerName"
2258 }
2259 }
2260}
2261
2262
2263function Add-NetGroupUser {
2264<#
2265 .SYNOPSIS
2266
2267 Adds a user to a domain group or a local group on the current (or remote) machine,
2268 if permissions allow, utilizing the WinNT service provider and
2269 DirectoryServices.AccountManagement, respectively.
2270
2271 .PARAMETER UserName
2272
2273 The domain username to query for.
2274
2275 .PARAMETER GroupName
2276
2277 Group to add the user to.
2278
2279 .PARAMETER ComputerName
2280
2281 Hostname to add the user to, defaults to localhost.
2282
2283 .PARAMETER Domain
2284
2285 Domain to add the user to.
2286
2287 .EXAMPLE
2288
2289 PS C:\> Add-NetGroupUser -UserName john -GroupName Administrators
2290
2291 Adds a localuser "john" to the local group "Administrators"
2292
2293 .EXAMPLE
2294
2295 PS C:\> Add-NetGroupUser -UserName john -GroupName "Domain Admins" -Domain dev.local
2296
2297 Adds the existing user "john" to the domain group "Domain Admins" in "dev.local"
2298#>
2299
2300 [CmdletBinding()]
2301 param(
2302 [Parameter(Mandatory = $True)]
2303 [ValidateNotNullOrEmpty()]
2304 [String]
2305 $UserName,
2306
2307 [Parameter(Mandatory = $True)]
2308 [ValidateNotNullOrEmpty()]
2309 [String]
2310 $GroupName,
2311
2312 [ValidateNotNullOrEmpty()]
2313 [Alias('HostName')]
2314 [String]
2315 $ComputerName,
2316
2317 [String]
2318 $Domain
2319 )
2320
2321 # add the assembly if we need it
2322 Add-Type -AssemblyName System.DirectoryServices.AccountManagement
2323
2324 # if we're adding to a remote host's local group, use the WinNT provider
2325 if($ComputerName -and ($ComputerName -ne "localhost")) {
2326 try {
2327 Write-Verbose "Adding user $UserName to $GroupName on host $ComputerName"
2328 ([ADSI]"WinNT://$ComputerName/$GroupName,group").add("WinNT://$ComputerName/$UserName,user")
2329 "[*] User $UserName successfully added to group $GroupName on $ComputerName"
2330 }
2331 catch {
2332 Write-Warning "[!] Error adding user $UserName to group $GroupName on $ComputerName"
2333 return
2334 }
2335 }
2336
2337 # otherwise it's a local machine or domain add
2338 else {
2339 try {
2340 if ($Domain) {
2341 Write-Verbose "Adding user $UserName to $GroupName on domain $Domain"
2342 $CT = [System.DirectoryServices.AccountManagement.ContextType]::Domain
2343 $DomainObject = Get-NetDomain -Domain $Domain
2344 if(-not $DomainObject) {
2345 return $Null
2346 }
2347 # get the full principal context
2348 $Context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList $CT, $DomainObject
2349 }
2350 else {
2351 # otherwise, get the local machine context
2352 Write-Verbose "Adding user $UserName to $GroupName on localhost"
2353 $Context = New-Object System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::Machine, $Env:ComputerName)
2354 }
2355
2356 # find the particular group
2357 $Group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($Context,$GroupName)
2358
2359 # add the particular user to the group
2360 $Group.Members.add($Context, [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName, $UserName)
2361
2362 # commit the changes
2363 $Group.Save()
2364 }
2365 catch {
2366 Write-Warning "Error adding $UserName to $GroupName : $_"
2367 }
2368 }
2369}
2370
2371
2372function Get-UserProperty {
2373<#
2374 .SYNOPSIS
2375
2376 Returns a list of all user object properties. If a property
2377 name is specified, it returns all [user:property] values.
2378
2379 Taken directly from @obscuresec's post:
2380 http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
2381
2382 .PARAMETER Properties
2383
2384 Property names to extract for users.
2385
2386 .PARAMETER Domain
2387
2388 The domain to query for user properties, defaults to the current domain.
2389
2390 .PARAMETER DomainController
2391
2392 Domain controller to reflect LDAP queries through.
2393
2394 .PARAMETER PageSize
2395
2396 The PageSize to set for the LDAP searcher object.
2397
2398 .EXAMPLE
2399
2400 PS C:\> Get-UserProperty -Domain testing
2401
2402 Returns all user properties for users in the 'testing' domain.
2403
2404 .EXAMPLE
2405
2406 PS C:\> Get-UserProperty -Properties ssn,lastlogon,location
2407
2408 Returns all an array of user/ssn/lastlogin/location combinations
2409 for users in the current domain.
2410
2411 .LINK
2412
2413 http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
2414#>
2415
2416 [CmdletBinding()]
2417 param(
2418 [String[]]
2419 $Properties,
2420
2421 [String]
2422 $Domain,
2423
2424 [String]
2425 $DomainController,
2426
2427 [ValidateRange(1,10000)]
2428 [Int]
2429 $PageSize = 200
2430 )
2431
2432 if($Properties) {
2433 # extract out the set of all properties for each object
2434 $Properties = ,"name" + $Properties
2435 Get-NetUser -Domain $Domain -DomainController $DomainController -PageSize $PageSize | Select-Object -Property $Properties
2436 }
2437 else {
2438 # extract out just the property names
2439 Get-NetUser -Domain $Domain -DomainController $DomainController -PageSize $PageSize | Select-Object -First 1 | Get-Member -MemberType *Property | Select-Object -Property 'Name'
2440 }
2441}
2442
2443
2444function Find-UserField {
2445<#
2446 .SYNOPSIS
2447
2448 Searches user object fields for a given word (default *pass*). Default
2449 field being searched is 'description'.
2450
2451 Taken directly from @obscuresec's post:
2452 http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
2453
2454 .PARAMETER SearchTerm
2455
2456 Term to search for, default of "pass".
2457
2458 .PARAMETER SearchField
2459
2460 User field to search, default of "description".
2461
2462 .PARAMETER ADSpath
2463
2464 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
2465 Useful for OU queries.
2466
2467 .PARAMETER Domain
2468
2469 Domain to search computer fields for, defaults to the current domain.
2470
2471 .PARAMETER DomainController
2472
2473 Domain controller to reflect LDAP queries through.
2474
2475 .PARAMETER PageSize
2476
2477 The PageSize to set for the LDAP searcher object.
2478
2479 .EXAMPLE
2480
2481 PS C:\> Find-UserField -SearchField info -SearchTerm backup
2482
2483 Find user accounts with "backup" in the "info" field.
2484#>
2485
2486 [CmdletBinding()]
2487 param(
2488 [Parameter(Position=0,ValueFromPipeline=$True)]
2489 [String]
2490 $SearchTerm = 'pass',
2491
2492 [String]
2493 $SearchField = 'description',
2494
2495 [String]
2496 $ADSpath,
2497
2498 [String]
2499 $Domain,
2500
2501 [String]
2502 $DomainController,
2503
2504 [ValidateRange(1,10000)]
2505 [Int]
2506 $PageSize = 200
2507 )
2508
2509 process {
2510 Get-NetUser -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -Filter "($SearchField=*$SearchTerm*)" -PageSize $PageSize | Select-Object samaccountname,$SearchField
2511 }
2512}
2513
2514
2515function Get-UserEvent {
2516<#
2517 .SYNOPSIS
2518
2519 Dump and parse security events relating to an account logon (ID 4624)
2520 or a TGT request event (ID 4768). Intended to be used and tested on
2521 Windows 2008 Domain Controllers.
2522 Admin Reqd? YES
2523
2524 Author: @sixdub
2525
2526 .PARAMETER ComputerName
2527
2528 The computer to get events from. Default: Localhost
2529
2530 .PARAMETER EventType
2531
2532 Either 'logon', 'tgt', or 'all'. Defaults: 'logon'
2533
2534 .PARAMETER DateStart
2535
2536 Filter out all events before this date. Default: 5 days
2537
2538 .EXAMPLE
2539
2540 PS C:\> Get-UserEvent -ComputerName DomainController.testlab.local
2541
2542 .LINK
2543
2544 http://www.sixdub.net/2014/11/07/offensive-event-parsing-bringing-home-trophies/
2545#>
2546
2547 Param(
2548 [String]
2549 $ComputerName = $Env:ComputerName,
2550
2551 [String]
2552 [ValidateSet("logon","tgt","all")]
2553 $EventType = "logon",
2554
2555 [DateTime]
2556 $DateStart=[DateTime]::Today.AddDays(-5)
2557 )
2558
2559 if($EventType.ToLower() -like "logon") {
2560 [Int32[]]$ID = @(4624)
2561 }
2562 elseif($EventType.ToLower() -like "tgt") {
2563 [Int32[]]$ID = @(4768)
2564 }
2565 else {
2566 [Int32[]]$ID = @(4624, 4768)
2567 }
2568
2569 #grab all events matching our filter for the specified host
2570 Get-WinEvent -ComputerName $ComputerName -FilterHashTable @{ LogName = 'Security'; ID=$ID; StartTime=$DateStart} -ErrorAction SilentlyContinue | ForEach-Object {
2571
2572 if($ID -contains 4624) {
2573 # first parse and check the logon event type. This could be later adapted and tested for RDP logons (type 10)
2574 if($_.message -match '(?s)(?<=Logon Type:).*?(?=(Impersonation Level:|New Logon:))') {
2575 if($Matches) {
2576 $LogonType = $Matches[0].trim()
2577 $Matches = $Null
2578 }
2579 }
2580 else {
2581 $LogonType = ""
2582 }
2583
2584 # interactive logons or domain logons
2585 if (($LogonType -eq 2) -or ($LogonType -eq 3)) {
2586 try {
2587 # parse and store the account used and the address they came from
2588 if($_.message -match '(?s)(?<=New Logon:).*?(?=Process Information:)') {
2589 if($Matches) {
2590 $UserName = $Matches[0].split("`n")[2].split(":")[1].trim()
2591 $Domain = $Matches[0].split("`n")[3].split(":")[1].trim()
2592 $Matches = $Null
2593 }
2594 }
2595 if($_.message -match '(?s)(?<=Network Information:).*?(?=Source Port:)') {
2596 if($Matches) {
2597 $Address = $Matches[0].split("`n")[2].split(":")[1].trim()
2598 $Matches = $Null
2599 }
2600 }
2601
2602 # only add if there was account information not for a machine or anonymous logon
2603 if ($UserName -and (-not $UserName.endsWith('$')) -and ($UserName -ne 'ANONYMOUS LOGON')) {
2604 $LogonEventProperties = @{
2605 'Domain' = $Domain
2606 'ComputerName' = $ComputerName
2607 'Username' = $UserName
2608 'Address' = $Address
2609 'ID' = '4624'
2610 'LogonType' = $LogonType
2611 'Time' = $_.TimeCreated
2612 }
2613 New-Object -TypeName PSObject -Property $LogonEventProperties
2614 }
2615 }
2616 catch {
2617 Write-Debug "Error parsing event logs: $_"
2618 }
2619 }
2620 }
2621 if($ID -contains 4768) {
2622 # the TGT event type
2623 try {
2624 if($_.message -match '(?s)(?<=Account Information:).*?(?=Service Information:)') {
2625 if($Matches) {
2626 $Username = $Matches[0].split("`n")[1].split(":")[1].trim()
2627 $Domain = $Matches[0].split("`n")[2].split(":")[1].trim()
2628 $Matches = $Null
2629 }
2630 }
2631
2632 if($_.message -match '(?s)(?<=Network Information:).*?(?=Additional Information:)') {
2633 if($Matches) {
2634 $Address = $Matches[0].split("`n")[1].split(":")[-1].trim()
2635 $Matches = $Null
2636 }
2637 }
2638
2639 $LogonEventProperties = @{
2640 'Domain' = $Domain
2641 'ComputerName' = $ComputerName
2642 'Username' = $UserName
2643 'Address' = $Address
2644 'ID' = '4768'
2645 'LogonType' = ''
2646 'Time' = $_.TimeCreated
2647 }
2648
2649 New-Object -TypeName PSObject -Property $LogonEventProperties
2650 }
2651 catch {
2652 Write-Debug "Error parsing event logs: $_"
2653 }
2654 }
2655 }
2656}
2657
2658
2659function Get-ObjectAcl {
2660<#
2661 .SYNOPSIS
2662 Returns the ACLs associated with a specific active directory object.
2663
2664 Thanks Sean Metcalf (@pyrotek3) for the idea and guidance.
2665
2666 .PARAMETER SamAccountName
2667
2668 Object name to filter for.
2669
2670 .PARAMETER Name
2671
2672 Object name to filter for.
2673
2674 .PARAMETER DistinguishedName
2675
2676 Object distinguished name to filter for.
2677
2678 .PARAMETER ResolveGUIDs
2679
2680 Switch. Resolve GUIDs to their display names.
2681
2682 .PARAMETER Filter
2683
2684 A customized ldap filter string to use, e.g. "(description=*admin*)"
2685
2686 .PARAMETER ADSpath
2687
2688 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
2689 Useful for OU queries.
2690
2691 .PARAMETER ADSprefix
2692
2693 Prefix to set for the searcher (like "CN=Sites,CN=Configuration")
2694
2695 .PARAMETER RightsFilter
2696
2697 Only return results with the associated rights, "All", "ResetPassword","WriteMembers"
2698
2699 .PARAMETER Domain
2700
2701 The domain to use for the query, defaults to the current domain.
2702
2703 .PARAMETER DomainController
2704
2705 Domain controller to reflect LDAP queries through.
2706
2707 .PARAMETER PageSize
2708
2709 The PageSize to set for the LDAP searcher object.
2710
2711 .EXAMPLE
2712
2713 PS C:\> Get-ObjectAcl -SamAccountName matt.admin -domain testlab.local
2714
2715 Get the ACLs for the matt.admin user in the testlab.local domain
2716
2717 .EXAMPLE
2718
2719 PS C:\> Get-ObjectAcl -SamAccountName matt.admin -domain testlab.local -ResolveGUIDs
2720
2721 Get the ACLs for the matt.admin user in the testlab.local domain and
2722 resolve relevant GUIDs to their display names.
2723#>
2724
2725 [CmdletBinding()]
2726 Param (
2727 [Parameter(ValueFromPipeline=$True)]
2728 [String]
2729 $SamAccountName,
2730
2731 [String]
2732 $Name = "*",
2733
2734 [Alias('DN')]
2735 [String]
2736 $DistinguishedName = "*",
2737
2738 [Switch]
2739 $ResolveGUIDs,
2740
2741 [String]
2742 $Filter,
2743
2744 [String]
2745 $ADSpath,
2746
2747 [String]
2748 $ADSprefix,
2749
2750 [String]
2751 [ValidateSet("All","ResetPassword","WriteMembers")]
2752 $RightsFilter,
2753
2754 [String]
2755 $Domain,
2756
2757 [String]
2758 $DomainController,
2759
2760 [ValidateRange(1,10000)]
2761 [Int]
2762 $PageSize = 200
2763 )
2764
2765 begin {
2766 $Searcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -ADSprefix $ADSprefix -PageSize $PageSize
2767
2768 # get a GUID -> name mapping
2769 if($ResolveGUIDs) {
2770 $GUIDs = Get-GUIDMap -Domain $Domain -DomainController $DomainController -PageSize $PageSize
2771 }
2772 }
2773
2774 process {
2775
2776 if ($Searcher) {
2777
2778 if($SamAccountName) {
2779 $Searcher.filter="(&(samaccountname=$SamAccountName)(name=$Name)(distinguishedname=$DistinguishedName)$Filter)"
2780 }
2781 else {
2782 $Searcher.filter="(&(name=$Name)(distinguishedname=$DistinguishedName)$Filter)"
2783 }
2784
2785 try {
2786 $Searcher.FindAll() | Where-Object {$_} | Foreach-Object {
2787 $Object = [adsi]($_.path)
2788 if($Object.distinguishedname) {
2789 $Access = $Object.PsBase.ObjectSecurity.access
2790 $Access | ForEach-Object {
2791 $_ | Add-Member NoteProperty 'ObjectDN' ($Object.distinguishedname[0])
2792
2793 if($Object.objectsid[0]){
2794 $S = (New-Object System.Security.Principal.SecurityIdentifier($Object.objectsid[0],0)).Value
2795 }
2796 else {
2797 $S = $Null
2798 }
2799
2800 $_ | Add-Member NoteProperty 'ObjectSID' $S
2801 $_
2802 }
2803 }
2804 } | ForEach-Object {
2805 if($RightsFilter) {
2806 $GuidFilter = Switch ($RightsFilter) {
2807 "ResetPassword" { "00299570-246d-11d0-a768-00aa006e0529" }
2808 "WriteMembers" { "bf9679c0-0de6-11d0-a285-00aa003049e2" }
2809 Default { "00000000-0000-0000-0000-000000000000"}
2810 }
2811 if($_.ObjectType -eq $GuidFilter) { $_ }
2812 }
2813 else {
2814 $_
2815 }
2816 } | Foreach-Object {
2817 if($GUIDs) {
2818 # if we're resolving GUIDs, map them them to the resolved hash table
2819 $AclProperties = @{}
2820 $_.psobject.properties | ForEach-Object {
2821 if( ($_.Name -eq 'ObjectType') -or ($_.Name -eq 'InheritedObjectType') ) {
2822 try {
2823 $AclProperties[$_.Name] = $GUIDS[$_.Value.toString()]
2824 }
2825 catch {
2826 $AclProperties[$_.Name] = $_.Value
2827 }
2828 }
2829 else {
2830 $AclProperties[$_.Name] = $_.Value
2831 }
2832 }
2833 New-Object -TypeName PSObject -Property $AclProperties
2834 }
2835 else { $_ }
2836 }
2837 }
2838 catch {
2839 Write-Warning $_
2840 }
2841 }
2842 }
2843}
2844
2845
2846function Add-ObjectAcl {
2847<#
2848 .SYNOPSIS
2849
2850 Adds an ACL for a specific active directory object.
2851
2852 AdminSDHolder ACL approach from Sean Metcalf (@pyrotek3)
2853 https://adsecurity.org/?p=1906
2854
2855 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.
2856
2857 'ResetPassword' doesn't need to know the user's current password
2858 'WriteMembers' allows for the modification of group membership
2859
2860 .PARAMETER TargetSamAccountName
2861
2862 Target object name to filter for.
2863
2864 .PARAMETER TargetName
2865
2866 Target object name to filter for.
2867
2868 .PARAMETER TargetDistinguishedName
2869
2870 Target object distinguished name to filter for.
2871
2872 .PARAMETER TargetFilter
2873
2874 A customized ldap filter string to use to find a target, e.g. "(description=*admin*)"
2875
2876 .PARAMETER TargetADSpath
2877
2878 The LDAP source for the target, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
2879
2880 .PARAMETER TargetADSprefix
2881
2882 Prefix to set for the target searcher (like "CN=Sites,CN=Configuration")
2883
2884 .PARAMETER PrincipalSID
2885
2886 The SID of the principal object to add for access.
2887
2888 .PARAMETER PrincipalName
2889
2890 The name of the principal object to add for access.
2891
2892 .PARAMETER PrincipalSamAccountName
2893
2894 The samAccountName of the principal object to add for access.
2895
2896 .PARAMETER Rights
2897
2898 Rights to add for the principal, "All","ResetPassword","WriteMembers","DCSync"
2899
2900 .PARAMETER Domain
2901
2902 The domain to use for the target query, defaults to the current domain.
2903
2904 .PARAMETER DomainController
2905
2906 Domain controller to reflect LDAP queries through.
2907
2908 .PARAMETER PageSize
2909
2910 The PageSize to set for the LDAP searcher object.
2911
2912 .EXAMPLE
2913
2914 Add-ObjectAcl -TargetSamAccountName matt -PrincipalSamAccountName john
2915
2916 Grants 'john' all full access rights to the 'matt' account.
2917
2918 .EXAMPLE
2919
2920 Add-ObjectAcl -TargetSamAccountName matt -PrincipalSamAccountName john -Rights ResetPassword
2921
2922 Grants 'john' the right to reset the password for the 'matt' account.
2923
2924 .LINK
2925
2926 https://adsecurity.org/?p=1906
2927
2928 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
2929#>
2930
2931 [CmdletBinding()]
2932 Param (
2933 [String]
2934 $TargetSamAccountName,
2935
2936 [String]
2937 $TargetName = "*",
2938
2939 [Alias('DN')]
2940 [String]
2941 $TargetDistinguishedName = "*",
2942
2943 [String]
2944 $TargetFilter,
2945
2946 [String]
2947 $TargetADSpath,
2948
2949 [String]
2950 $TargetADSprefix,
2951
2952 [String]
2953 [ValidatePattern('^S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+')]
2954 $PrincipalSID,
2955
2956 [String]
2957 $PrincipalName,
2958
2959 [String]
2960 $PrincipalSamAccountName,
2961
2962 [String]
2963 [ValidateSet("All","ResetPassword","WriteMembers","DCSync")]
2964 $Rights = "All",
2965
2966 [String]
2967 $RightsGUID,
2968
2969 [String]
2970 $Domain,
2971
2972 [String]
2973 $DomainController,
2974
2975 [ValidateRange(1,10000)]
2976 [Int]
2977 $PageSize = 200
2978 )
2979
2980 begin {
2981 $Searcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $TargetADSpath -ADSprefix $TargetADSprefix -PageSize $PageSize
2982
2983 if(!$PrincipalSID) {
2984 $Principal = Get-ADObject -Domain $Domain -DomainController $DomainController -Name $PrincipalName -SamAccountName $PrincipalSamAccountName -PageSize $PageSize
2985
2986 if(!$Principal) {
2987 throw "Error resolving principal"
2988 }
2989 $PrincipalSID = $Principal.objectsid
2990 }
2991 if(!$PrincipalSID) {
2992 throw "Error resolving principal"
2993 }
2994 }
2995
2996 process {
2997
2998 if ($Searcher) {
2999
3000 if($TargetSamAccountName) {
3001 $Searcher.filter="(&(samaccountname=$TargetSamAccountName)(name=$TargetName)(distinguishedname=$TargetDistinguishedName)$TargetFilter)"
3002 }
3003 else {
3004 $Searcher.filter="(&(name=$TargetName)(distinguishedname=$TargetDistinguishedName)$TargetFilter)"
3005 }
3006
3007 try {
3008 $Searcher.FindAll() | Where-Object {$_} | Foreach-Object {
3009 # 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
3010
3011 $TargetDN = $_.Properties.distinguishedname
3012
3013 $Identity = [System.Security.Principal.IdentityReference] ([System.Security.Principal.SecurityIdentifier]$PrincipalSID)
3014 $InheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] "None"
3015 $ControlType = [System.Security.AccessControl.AccessControlType] "Allow"
3016 $ACEs = @()
3017
3018 if($RightsGUID) {
3019 $GUIDs = @($RightsGUID)
3020 }
3021 else {
3022 $GUIDs = Switch ($Rights) {
3023 # ResetPassword doesn't need to know the user's current password
3024 "ResetPassword" { "00299570-246d-11d0-a768-00aa006e0529" }
3025 # allows for the modification of group membership
3026 "WriteMembers" { "bf9679c0-0de6-11d0-a285-00aa003049e2" }
3027 # 'DS-Replication-Get-Changes' = 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2
3028 # 'DS-Replication-Get-Changes-All' = 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2
3029 # 'DS-Replication-Get-Changes-In-Filtered-Set' = 89e95b76-444d-4c62-991a-0facbeda640c
3030 # when applied to a domain's ACL, allows for the use of DCSync
3031 "DCSync" { "1131f6aa-9c07-11d1-f79f-00c04fc2dcd2", "1131f6ad-9c07-11d1-f79f-00c04fc2dcd2", "89e95b76-444d-4c62-991a-0facbeda640c"}
3032 }
3033 }
3034
3035 if($GUIDs) {
3036 foreach($GUID in $GUIDs) {
3037 $NewGUID = New-Object Guid $GUID
3038 $ADRights = [System.DirectoryServices.ActiveDirectoryRights] "ExtendedRight"
3039 $ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity,$ADRights,$ControlType,$NewGUID,$InheritanceType
3040 }
3041 }
3042 else {
3043 # deault to GenericAll rights
3044 $ADRights = [System.DirectoryServices.ActiveDirectoryRights] "GenericAll"
3045 $ACEs += New-Object System.DirectoryServices.ActiveDirectoryAccessRule $Identity,$ADRights,$ControlType,$InheritanceType
3046 }
3047
3048 Write-Verbose "Granting principal $PrincipalSID '$Rights' on $($_.Properties.distinguishedname)"
3049
3050 try {
3051 # add all the new ACEs to the specified object
3052 ForEach ($ACE in $ACEs) {
3053 Write-Verbose "Granting principal $PrincipalSID '$($ACE.ObjectType)' rights on $($_.Properties.distinguishedname)"
3054 $Object = [adsi]($_.path)
3055 $Object.PsBase.ObjectSecurity.AddAccessRule($ACE)
3056 $Object.PsBase.commitchanges()
3057 }
3058 }
3059 catch {
3060 Write-Warning "Error granting principal $PrincipalSID '$Rights' on $TargetDN : $_"
3061 }
3062 }
3063 }
3064 catch {
3065 Write-Warning "Error: $_"
3066 }
3067 }
3068 }
3069}
3070
3071
3072function Invoke-ACLScanner {
3073<#
3074 .SYNOPSIS
3075 Searches for ACLs for specifable AD objects (default to all domain objects)
3076 with a domain sid of > -1000, and have modifiable rights.
3077
3078 Thanks Sean Metcalf (@pyrotek3) for the idea and guidance.
3079
3080 .PARAMETER SamAccountName
3081
3082 Object name to filter for.
3083
3084 .PARAMETER Name
3085
3086 Object name to filter for.
3087
3088 .PARAMETER DistinguishedName
3089
3090 Object distinguished name to filter for.
3091
3092 .PARAMETER Filter
3093
3094 A customized ldap filter string to use, e.g. "(description=*admin*)"
3095
3096 .PARAMETER ADSpath
3097
3098 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
3099 Useful for OU queries.
3100
3101 .PARAMETER ADSprefix
3102
3103 Prefix to set for the searcher (like "CN=Sites,CN=Configuration")
3104
3105 .PARAMETER Domain
3106
3107 The domain to use for the query, defaults to the current domain.
3108
3109 .PARAMETER DomainController
3110
3111 Domain controller to reflect LDAP queries through.
3112
3113 .PARAMETER ResolveGUIDs
3114
3115 Switch. Resolve GUIDs to their display names.
3116
3117 .PARAMETER PageSize
3118
3119 The PageSize to set for the LDAP searcher object.
3120
3121 .EXAMPLE
3122
3123 PS C:\> Invoke-ACLScanner -ResolveGUIDs | Export-CSV -NoTypeInformation acls.csv
3124
3125 Enumerate all modifable ACLs in the current domain, resolving GUIDs to display
3126 names, and export everything to a .csv
3127#>
3128
3129 [CmdletBinding()]
3130 Param (
3131 [Parameter(ValueFromPipeline=$True)]
3132 [String]
3133 $SamAccountName,
3134
3135 [String]
3136 $Name = "*",
3137
3138 [Alias('DN')]
3139 [String]
3140 $DistinguishedName = "*",
3141
3142 [String]
3143 $Filter,
3144
3145 [String]
3146 $ADSpath,
3147
3148 [String]
3149 $ADSprefix,
3150
3151 [String]
3152 $Domain,
3153
3154 [String]
3155 $DomainController,
3156
3157 [Switch]
3158 $ResolveGUIDs,
3159
3160 [ValidateRange(1,10000)]
3161 [Int]
3162 $PageSize = 200
3163 )
3164
3165 # Get all domain ACLs with the appropriate parameters
3166 Get-ObjectACL @PSBoundParameters | ForEach-Object {
3167 # add in the translated SID for the object identity
3168 $_ | Add-Member Noteproperty 'IdentitySID' ($_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value)
3169 $_
3170 } | Where-Object {
3171 # check for any ACLs with SIDs > -1000
3172 try {
3173 [int]($_.IdentitySid.split("-")[-1]) -ge 1000
3174 }
3175 catch {}
3176 } | Where-Object {
3177 # filter for modifiable rights
3178 ($_.ActiveDirectoryRights -eq "GenericAll") -or ($_.ActiveDirectoryRights -match "Write") -or ($_.ActiveDirectoryRights -match "Create") -or ($_.ActiveDirectoryRights -match "Delete") -or (($_.ActiveDirectoryRights -match "ExtendedRight") -and ($_.AccessControlType -eq "Allow"))
3179 }
3180}
3181
3182
3183function Get-GUIDMap {
3184<#
3185 .SYNOPSIS
3186
3187 Helper to build a hash table of [GUID] -> resolved names
3188
3189 Heavily adapted from http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou-permissions-report-free-powershell-script-download.aspx
3190
3191 .PARAMETER Domain
3192
3193 The domain to use for the query, defaults to the current domain.
3194
3195 .PARAMETER DomainController
3196
3197 Domain controller to reflect LDAP queries through.
3198
3199 .PARAMETER PageSize
3200
3201 The PageSize to set for the LDAP searcher object.
3202
3203 .LINK
3204
3205 http://blogs.technet.com/b/ashleymcglone/archive/2013/03/25/active-directory-ou-permissions-report-free-powershell-script-download.aspx
3206#>
3207
3208 [CmdletBinding()]
3209 Param (
3210 [String]
3211 $Domain,
3212
3213 [String]
3214 $DomainController,
3215
3216 [ValidateRange(1,10000)]
3217 [Int]
3218 $PageSize = 200
3219 )
3220
3221 $GUIDs = @{'00000000-0000-0000-0000-000000000000' = 'All'}
3222
3223 $SchemaPath = (Get-NetForest).schema.name
3224
3225 $SchemaSearcher = Get-DomainSearcher -ADSpath $SchemaPath -DomainController $DomainController -PageSize $PageSize
3226 if($SchemaSearcher) {
3227 $SchemaSearcher.filter = "(schemaIDGUID=*)"
3228 try {
3229 $SchemaSearcher.FindAll() | Where-Object {$_} | ForEach-Object {
3230 # convert the GUID
3231 $GUIDs[(New-Object Guid (,$_.properties.schemaidguid[0])).Guid] = $_.properties.name[0]
3232 }
3233 }
3234 catch {
3235 Write-Debug "Error in building GUID map: $_"
3236 }
3237 }
3238
3239 $RightsSearcher = Get-DomainSearcher -ADSpath $SchemaPath.replace("Schema","Extended-Rights") -DomainController $DomainController -PageSize $PageSize
3240 if ($RightsSearcher) {
3241 $RightsSearcher.filter = "(objectClass=controlAccessRight)"
3242 try {
3243 $RightsSearcher.FindAll() | Where-Object {$_} | ForEach-Object {
3244 # convert the GUID
3245 $GUIDs[$_.properties.rightsguid[0].toString()] = $_.properties.name[0]
3246 }
3247 }
3248 catch {
3249 Write-Debug "Error in building GUID map: $_"
3250 }
3251 }
3252
3253 $GUIDs
3254}
3255
3256
3257function Get-NetComputer {
3258<#
3259 .SYNOPSIS
3260
3261 This function utilizes adsisearcher to query the current AD context
3262 for current computer objects. Based off of Carlos Perez's Audit.psm1
3263 script in Posh-SecMod (link below).
3264
3265 .PARAMETER ComputerName
3266
3267 Return computers with a specific name, wildcards accepted.
3268
3269 .PARAMETER SPN
3270
3271 Return computers with a specific service principal name, wildcards accepted.
3272
3273 .PARAMETER OperatingSystem
3274
3275 Return computers with a specific operating system, wildcards accepted.
3276
3277 .PARAMETER ServicePack
3278
3279 Return computers with a specific service pack, wildcards accepted.
3280
3281 .PARAMETER Filter
3282
3283 A customized ldap filter string to use, e.g. "(description=*admin*)"
3284
3285 .PARAMETER Printers
3286
3287 Switch. Return only printers.
3288
3289 .PARAMETER Ping
3290
3291 Switch. Ping each host to ensure it's up before enumerating.
3292
3293 .PARAMETER FullData
3294
3295 Switch. Return full computer objects instead of just system names (the default).
3296
3297 .PARAMETER Domain
3298
3299 The domain to query for computers, defaults to the current domain.
3300
3301 .PARAMETER DomainController
3302
3303 Domain controller to reflect LDAP queries through.
3304
3305 .PARAMETER ADSpath
3306
3307 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
3308 Useful for OU queries.
3309
3310 .PARAMETER Unconstrained
3311
3312 Switch. Return computer objects that have unconstrained delegation.
3313
3314 .PARAMETER PageSize
3315
3316 The PageSize to set for the LDAP searcher object.
3317
3318 .EXAMPLE
3319
3320 PS C:\> Get-NetComputer
3321
3322 Returns the current computers in current domain.
3323
3324 .EXAMPLE
3325
3326 PS C:\> Get-NetComputer -SPN mssql*
3327
3328 Returns all MS SQL servers on the domain.
3329
3330 .EXAMPLE
3331
3332 PS C:\> Get-NetComputer -Domain testing
3333
3334 Returns the current computers in 'testing' domain.
3335
3336 .EXAMPLE
3337
3338 PS C:\> Get-NetComputer -Domain testing -FullData
3339
3340 Returns full computer objects in the 'testing' domain.
3341
3342 .LINK
3343
3344 https://github.com/darkoperator/Posh-SecMod/blob/master/Audit/Audit.psm1
3345#>
3346
3347 [CmdletBinding()]
3348 Param (
3349 [Parameter(ValueFromPipeline=$True)]
3350 [Alias('HostName')]
3351 [String]
3352 $ComputerName = '*',
3353
3354 [String]
3355 $SPN,
3356
3357 [String]
3358 $OperatingSystem,
3359
3360 [String]
3361 $ServicePack,
3362
3363 [String]
3364 $Filter,
3365
3366 [Switch]
3367 $Printers,
3368
3369 [Switch]
3370 $Ping,
3371
3372 [Switch]
3373 $FullData,
3374
3375 [String]
3376 $Domain,
3377
3378 [String]
3379 $DomainController,
3380
3381 [String]
3382 $ADSpath,
3383
3384 [Switch]
3385 $Unconstrained,
3386
3387 [ValidateRange(1,10000)]
3388 [Int]
3389 $PageSize = 200
3390 )
3391
3392 begin {
3393 # so this isn't repeated if users are passed on the pipeline
3394 $CompSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize
3395 }
3396
3397 process {
3398
3399 if ($CompSearcher) {
3400
3401 # if we're checking for unconstrained delegation
3402 if($Unconstrained) {
3403 Write-Verbose "Searching for computers with for unconstrained delegation"
3404 $Filter += "(userAccountControl:1.2.840.113556.1.4.803:=524288)"
3405 }
3406 # set the filters for the seracher if it exists
3407 if($Printers) {
3408 Write-Verbose "Searching for printers"
3409 # $CompSearcher.filter="(&(objectCategory=printQueue)$Filter)"
3410 $Filter += "(objectCategory=printQueue)"
3411 }
3412 if($SPN) {
3413 Write-Verbose "Searching for computers with SPN: $SPN"
3414 $Filter += "(servicePrincipalName=$SPN)"
3415 }
3416 if($OperatingSystem) {
3417 $Filter += "(operatingsystem=$OperatingSystem)"
3418 }
3419 if($ServicePack) {
3420 $Filter += "(operatingsystemservicepack=$ServicePack)"
3421 }
3422
3423 $CompSearcher.filter = "(&(sAMAccountType=805306369)(dnshostname=$ComputerName)$Filter)"
3424
3425 try {
3426
3427 $CompSearcher.FindAll() | Where-Object {$_} | ForEach-Object {
3428 $Up = $True
3429 if($Ping) {
3430 # TODO: how can these results be piped to ping for a speedup?
3431 $Up = Test-Connection -Count 1 -Quiet -ComputerName $_.properties.dnshostname
3432 }
3433 if($Up) {
3434 # return full data objects
3435 if ($FullData) {
3436 # convert/process the LDAP fields for each result
3437 Convert-LDAPProperty -Properties $_.Properties
3438 }
3439 else {
3440 # otherwise we're just returning the DNS host name
3441 $_.properties.dnshostname
3442 }
3443 }
3444 }
3445 }
3446 catch {
3447 Write-Warning "Error: $_"
3448 }
3449 }
3450 }
3451}
3452
3453
3454function Get-ADObject {
3455<#
3456 .SYNOPSIS
3457
3458 Takes a domain SID and returns the user, group, or computer object
3459 associated with it.
3460
3461 .PARAMETER SID
3462
3463 The SID of the domain object you're querying for.
3464
3465 .PARAMETER Name
3466
3467 The Name of the domain object you're querying for.
3468
3469 .PARAMETER SamAccountName
3470
3471 The SamAccountName of the domain object you're querying for.
3472
3473 .PARAMETER Domain
3474
3475 The domain to query for objects, defaults to the current domain.
3476
3477 .PARAMETER DomainController
3478
3479 Domain controller to reflect LDAP queries through.
3480
3481 .PARAMETER ADSpath
3482
3483 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
3484 Useful for OU queries.
3485
3486 .PARAMETER Filter
3487
3488 Additional LDAP filter string for the query.
3489
3490 .PARAMETER ReturnRaw
3491
3492 Switch. Return the raw object instead of translating its properties.
3493 Used by Set-ADObject to modify object properties.
3494
3495 .PARAMETER PageSize
3496
3497 The PageSize to set for the LDAP searcher object.
3498
3499 .EXAMPLE
3500
3501 PS C:\> Get-ADObject -SID "S-1-5-21-2620891829-2411261497-1773853088-1110"
3502
3503 Get the domain object associated with the specified SID.
3504
3505 .EXAMPLE
3506
3507 PS C:\> Get-ADObject -ADSpath "CN=AdminSDHolder,CN=System,DC=testlab,DC=local"
3508
3509 Get the AdminSDHolder object for the testlab.local domain.
3510#>
3511
3512 [CmdletBinding()]
3513 Param (
3514 [Parameter(ValueFromPipeline=$True)]
3515 [String]
3516 $SID,
3517
3518 [String]
3519 $Name,
3520
3521 [String]
3522 $SamAccountName,
3523
3524 [String]
3525 $Domain,
3526
3527 [String]
3528 $DomainController,
3529
3530 [String]
3531 $ADSpath,
3532
3533 [String]
3534 $Filter,
3535
3536 [Switch]
3537 $ReturnRaw,
3538
3539 [ValidateRange(1,10000)]
3540 [Int]
3541 $PageSize = 200
3542 )
3543 process {
3544 if($SID) {
3545 # if a SID is passed, try to resolve it to a reachable domain name for the searcher
3546 try {
3547 $Name = Convert-SidToName $SID
3548 if($Name) {
3549 $Canonical = Convert-NT4toCanonical -ObjectName $Name
3550 if($Canonical) {
3551 $Domain = $Canonical.split("/")[0]
3552 }
3553 else {
3554 Write-Warning "Error resolving SID '$SID'"
3555 return $Null
3556 }
3557 }
3558 }
3559 catch {
3560 Write-Warning "Error resolving SID '$SID' : $_"
3561 return $Null
3562 }
3563 }
3564
3565 $ObjectSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize
3566
3567 if($ObjectSearcher) {
3568
3569 if($SID) {
3570 $ObjectSearcher.filter = "(&(objectsid=$SID)$Filter)"
3571 }
3572 elseif($Name) {
3573 $ObjectSearcher.filter = "(&(name=$Name)$Filter)"
3574 }
3575 elseif($SamAccountName) {
3576 $ObjectSearcher.filter = "(&(samAccountName=$SamAccountName)$Filter)"
3577 }
3578
3579 $ObjectSearcher.FindAll() | Where-Object {$_} | ForEach-Object {
3580 if($ReturnRaw) {
3581 $_
3582 }
3583 else {
3584 # convert/process the LDAP fields for each result
3585 Convert-LDAPProperty -Properties $_.Properties
3586 }
3587 }
3588 }
3589 }
3590}
3591
3592
3593function Set-ADObject {
3594<#
3595 .SYNOPSIS
3596
3597 Takes a SID, name, or SamAccountName to query for a specified
3598 domain object, and then sets a specified 'PropertyName' to a
3599 specified 'PropertyValue'.
3600
3601 .PARAMETER SID
3602
3603 The SID of the domain object you're querying for.
3604
3605 .PARAMETER Name
3606
3607 The Name of the domain object you're querying for.
3608
3609 .PARAMETER SamAccountName
3610
3611 The SamAccountName of the domain object you're querying for.
3612
3613 .PARAMETER Domain
3614
3615 The domain to query for objects, defaults to the current domain.
3616
3617 .PARAMETER DomainController
3618
3619 Domain controller to reflect LDAP queries through.
3620
3621 .PARAMETER Filter
3622
3623 Additional LDAP filter string for the query.
3624
3625 .PARAMETER PropertyName
3626
3627 The property name to set.
3628
3629 .PARAMETER PropertyValue
3630
3631 The value to set for PropertyName
3632
3633 .PARAMETER PropertyXorValue
3634
3635 Integer value to binary xor (-bxor) with the current int value.
3636
3637 .PARAMETER ClearValue
3638
3639 Switch. Clear the value of PropertyName
3640
3641 .PARAMETER PageSize
3642
3643 The PageSize to set for the LDAP searcher object.
3644
3645 .EXAMPLE
3646
3647 PS C:\> Set-ADObject -SamAccountName matt.admin -PropertyName countrycode -PropertyValue 0
3648
3649 Set the countrycode for matt.admin to 0
3650
3651 .EXAMPLE
3652
3653 PS C:\> Set-ADObject -SamAccountName matt.admin -PropertyName useraccountcontrol -PropertyXorValue 65536
3654
3655 Set the password not to expire on matt.admin
3656#>
3657
3658 [CmdletBinding()]
3659 Param (
3660 [String]
3661 $SID,
3662
3663 [String]
3664 $Name,
3665
3666 [String]
3667 $SamAccountName,
3668
3669 [String]
3670 $Domain,
3671
3672 [String]
3673 $DomainController,
3674
3675 [String]
3676 $Filter,
3677
3678 [Parameter(Mandatory = $True)]
3679 [String]
3680 $PropertyName,
3681
3682 $PropertyValue,
3683
3684 [Int]
3685 $PropertyXorValue,
3686
3687 [Switch]
3688 $ClearValue,
3689
3690 [ValidateRange(1,10000)]
3691 [Int]
3692 $PageSize = 200
3693 )
3694
3695 $Arguments = @{
3696 'SID' = $SID
3697 'Name' = $Name
3698 'SamAccountName' = $SamAccountName
3699 'Domain' = $Domain
3700 'DomainController' = $DomainController
3701 'Filter' = $Filter
3702 'PageSize' = $PageSize
3703 }
3704 # splat the appropriate arguments to Get-ADObject
3705 $RawObject = Get-ADObject -ReturnRaw @Arguments
3706
3707 try {
3708 # get the modifiable object for this search result
3709 $Entry = $RawObject.GetDirectoryEntry()
3710
3711 if($ClearValue) {
3712 Write-Verbose "Clearing value"
3713 $Entry.$PropertyName.clear()
3714 $Entry.commitchanges()
3715 }
3716
3717 elseif($PropertyXorValue) {
3718 $TypeName = $Entry.$PropertyName[0].GetType().name
3719
3720 # UAC value references- https://support.microsoft.com/en-us/kb/305144
3721 $PropertyValue = $($Entry.$PropertyName) -bxor $PropertyXorValue
3722 $Entry.$PropertyName = $PropertyValue -as $TypeName
3723 $Entry.commitchanges()
3724 }
3725
3726 else {
3727 $Entry.put($PropertyName, $PropertyValue)
3728 $Entry.setinfo()
3729 }
3730 }
3731 catch {
3732 Write-Warning "Error setting property $PropertyName to value '$PropertyValue' for object $($RawObject.Properties.samaccountname) : $_"
3733 }
3734}
3735
3736
3737function Invoke-DowngradeAccount {
3738<#
3739 .SYNOPSIS
3740
3741 Set reversible encryption on a given account and then force the password
3742 to be set on next user login. To repair use "-Repair".
3743
3744 .PARAMETER SamAccountName
3745
3746 The SamAccountName of the domain object you're querying for.
3747
3748 .PARAMETER Name
3749
3750 The Name of the domain object you're querying for.
3751
3752 .PARAMETER Domain
3753
3754 The domain to query for objects, defaults to the current domain.
3755
3756 .PARAMETER DomainController
3757
3758 Domain controller to reflect LDAP queries through.
3759
3760 .PARAMETER Filter
3761
3762 Additional LDAP filter string for the query.
3763
3764 .PARAMETER Repair
3765
3766 Switch. Unset the reversible encryption flag and force password reset flag.
3767
3768 .EXAMPLE
3769
3770 PS> Invoke-DowngradeAccount -SamAccountName jason
3771
3772 Set reversible encryption on the 'jason' account and force the password to be changed.
3773
3774 .EXAMPLE
3775
3776 PS> Invoke-DowngradeAccount -SamAccountName jason -Repair
3777
3778 Unset reversible encryption on the 'jason' account and remove the forced password change.
3779#>
3780
3781 [CmdletBinding()]
3782 Param (
3783 [Parameter(Position=0,ValueFromPipeline=$True)]
3784 [String]
3785 $SamAccountName,
3786
3787 [String]
3788 $Name,
3789
3790 [String]
3791 $Domain,
3792
3793 [String]
3794 $DomainController,
3795
3796 [String]
3797 $Filter,
3798
3799 [Switch]
3800 $Repair
3801 )
3802
3803 process {
3804 $Arguments = @{
3805 'SamAccountName' = $SamAccountName
3806 'Name' = $Name
3807 'Domain' = $Domain
3808 'DomainController' = $DomainController
3809 'Filter' = $Filter
3810 }
3811
3812 # splat the appropriate arguments to Get-ADObject
3813 $UACValues = Get-ADObject @Arguments | select useraccountcontrol | ConvertFrom-UACValue
3814
3815 if($Repair) {
3816
3817 if($UACValues.Keys -contains "ENCRYPTED_TEXT_PWD_ALLOWED") {
3818 # if reversible encryption is set, unset it
3819 Set-ADObject @Arguments -PropertyName useraccountcontrol -PropertyXorValue 128
3820 }
3821
3822 # unset the forced password change
3823 Set-ADObject @Arguments -PropertyName pwdlastset -PropertyValue -1
3824 }
3825
3826 else {
3827
3828 if($UACValues.Keys -contains "DONT_EXPIRE_PASSWORD") {
3829 # if the password is set to never expire, unset
3830 Set-ADObject @Arguments -PropertyName useraccountcontrol -PropertyXorValue 65536
3831 }
3832
3833 if($UACValues.Keys -notcontains "ENCRYPTED_TEXT_PWD_ALLOWED") {
3834 # if reversible encryption is not set, set it
3835 Set-ADObject @Arguments -PropertyName useraccountcontrol -PropertyXorValue 128
3836 }
3837
3838 # force the password to be changed on next login
3839 Set-ADObject @Arguments -PropertyName pwdlastset -PropertyValue 0
3840 }
3841 }
3842}
3843
3844
3845function Get-ComputerProperty {
3846<#
3847 .SYNOPSIS
3848
3849 Returns a list of all computer object properties. If a property
3850 name is specified, it returns all [computer:property] values.
3851
3852 Taken directly from @obscuresec's post:
3853 http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
3854
3855 .PARAMETER Properties
3856
3857 Return property names for computers.
3858
3859 .PARAMETER Domain
3860
3861 The domain to query for computer properties, defaults to the current domain.
3862
3863 .PARAMETER DomainController
3864
3865 Domain controller to reflect LDAP queries through.
3866
3867 .PARAMETER PageSize
3868
3869 The PageSize to set for the LDAP searcher object.
3870
3871 .EXAMPLE
3872
3873 PS C:\> Get-ComputerProperty -Domain testing
3874
3875 Returns all user properties for computers in the 'testing' domain.
3876
3877 .EXAMPLE
3878
3879 PS C:\> Get-ComputerProperty -Properties ssn,lastlogon,location
3880
3881 Returns all an array of computer/ssn/lastlogin/location combinations
3882 for computers in the current domain.
3883
3884 .LINK
3885
3886 http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
3887#>
3888
3889 [CmdletBinding()]
3890 param(
3891 [String[]]
3892 $Properties,
3893
3894 [String]
3895 $Domain,
3896
3897 [String]
3898 $DomainController,
3899
3900 [ValidateRange(1,10000)]
3901 [Int]
3902 $PageSize = 200
3903 )
3904
3905 if($Properties) {
3906 # extract out the set of all properties for each object
3907 $Properties = ,"name" + $Properties | Sort-Object -Unique
3908 Get-NetComputer -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize | Select-Object -Property $Properties
3909 }
3910 else {
3911 # extract out just the property names
3912 Get-NetComputer -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize | Select-Object -first 1 | Get-Member -MemberType *Property | Select-Object -Property "Name"
3913 }
3914}
3915
3916
3917function Find-ComputerField {
3918<#
3919 .SYNOPSIS
3920
3921 Searches computer object fields for a given word (default *pass*). Default
3922 field being searched is 'description'.
3923
3924 Taken directly from @obscuresec's post:
3925 http://obscuresecurity.blogspot.com/2014/04/ADSISearcher.html
3926
3927 .PARAMETER SearchTerm
3928
3929 Term to search for, default of "pass".
3930
3931 .PARAMETER SearchField
3932
3933 User field to search in, default of "description".
3934
3935 .PARAMETER ADSpath
3936
3937 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
3938 Useful for OU queries.
3939
3940 .PARAMETER Domain
3941
3942 Domain to search computer fields for, defaults to the current domain.
3943
3944 .PARAMETER DomainController
3945
3946 Domain controller to reflect LDAP queries through.
3947
3948 .PARAMETER PageSize
3949
3950 The PageSize to set for the LDAP searcher object.
3951
3952 .EXAMPLE
3953
3954 PS C:\> Find-ComputerField -SearchTerm backup -SearchField info
3955
3956 Find computer accounts with "backup" in the "info" field.
3957#>
3958
3959 [CmdletBinding()]
3960 param(
3961 [Parameter(Position=0,ValueFromPipeline=$True)]
3962 [Alias('Term')]
3963 [String]
3964 $SearchTerm = 'pass',
3965
3966 [Alias('Field')]
3967 [String]
3968 $SearchField = 'description',
3969
3970 [String]
3971 $ADSpath,
3972
3973 [String]
3974 $Domain,
3975
3976 [String]
3977 $DomainController,
3978
3979 [ValidateRange(1,10000)]
3980 [Int]
3981 $PageSize = 200
3982 )
3983
3984 process {
3985 Get-NetComputer -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -FullData -Filter "($SearchField=*$SearchTerm*)" -PageSize $PageSize | Select-Object samaccountname,$SearchField
3986 }
3987}
3988
3989
3990function Get-NetOU {
3991<#
3992 .SYNOPSIS
3993
3994 Gets a list of all current OUs in a domain.
3995
3996 .PARAMETER OUName
3997
3998 The OU name to query for, wildcards accepted.
3999
4000 .PARAMETER GUID
4001
4002 Only return OUs with the specified GUID in their gplink property.
4003
4004 .PARAMETER Domain
4005
4006 The domain to query for OUs, defaults to the current domain.
4007
4008 .PARAMETER DomainController
4009
4010 Domain controller to reflect LDAP queries through.
4011
4012 .PARAMETER ADSpath
4013
4014 The LDAP source to search through.
4015
4016 .PARAMETER FullData
4017
4018 Switch. Return full OU objects instead of just object names (the default).
4019
4020 .PARAMETER PageSize
4021
4022 The PageSize to set for the LDAP searcher object.
4023
4024 .EXAMPLE
4025
4026 PS C:\> Get-NetOU
4027
4028 Returns the current OUs in the domain.
4029
4030 .EXAMPLE
4031
4032 PS C:\> Get-NetOU -OUName *admin* -Domain testlab.local
4033
4034 Returns all OUs with "admin" in their name in the testlab.local domain.
4035
4036 .EXAMPLE
4037
4038 PS C:\> Get-NetOU -GUID 123-...
4039
4040 Returns all OUs with linked to the specified group policy object.
4041#>
4042
4043 [CmdletBinding()]
4044 Param (
4045 [Parameter(ValueFromPipeline=$True)]
4046 [String]
4047 $OUName = '*',
4048
4049 [String]
4050 $GUID,
4051
4052 [String]
4053 $Domain,
4054
4055 [String]
4056 $DomainController,
4057
4058 [String]
4059 $ADSpath,
4060
4061 [Switch]
4062 $FullData,
4063
4064 [ValidateRange(1,10000)]
4065 [Int]
4066 $PageSize = 200
4067 )
4068
4069 begin {
4070 $OUSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize
4071 }
4072 process {
4073 if ($OUSearcher) {
4074 if ($GUID) {
4075 # if we're filtering for a GUID in .gplink
4076 $OUSearcher.filter="(&(objectCategory=organizationalUnit)(name=$OUName)(gplink=*$GUID*))"
4077 }
4078 else {
4079 $OUSearcher.filter="(&(objectCategory=organizationalUnit)(name=$OUName))"
4080 }
4081
4082 $OUSearcher.FindAll() | Where-Object {$_} | ForEach-Object {
4083 if ($FullData) {
4084 # convert/process the LDAP fields for each result
4085 Convert-LDAPProperty -Properties $_.Properties
4086 }
4087 else {
4088 # otherwise just returning the ADS paths of the OUs
4089 $_.properties.adspath
4090 }
4091 }
4092 }
4093 }
4094}
4095
4096
4097function Get-NetSite {
4098<#
4099 .SYNOPSIS
4100
4101 Gets a list of all current sites in a domain.
4102
4103 .PARAMETER SiteName
4104
4105 Site filter string, wildcards accepted.
4106
4107 .PARAMETER Domain
4108
4109 The domain to query for sites, defaults to the current domain.
4110
4111 .PARAMETER DomainController
4112
4113 Domain controller to reflect LDAP queries through.
4114
4115 .PARAMETER ADSpath
4116
4117 The LDAP source to search through.
4118
4119 .PARAMETER GUID
4120
4121 Only return site with the specified GUID in their gplink property.
4122
4123 .PARAMETER FullData
4124
4125 Switch. Return full site objects instead of just object names (the default).
4126
4127 .PARAMETER PageSize
4128
4129 The PageSize to set for the LDAP searcher object.
4130
4131 .EXAMPLE
4132
4133 PS C:\> Get-NetSite -Domain testlab.local -FullData
4134
4135 Returns the full data objects for all sites in testlab.local
4136#>
4137
4138 [CmdletBinding()]
4139 Param (
4140 [Parameter(ValueFromPipeline=$True)]
4141 [String]
4142 $SiteName = "*",
4143
4144 [String]
4145 $Domain,
4146
4147 [String]
4148 $DomainController,
4149
4150 [String]
4151 $ADSpath,
4152
4153 [String]
4154 $GUID,
4155
4156 [Switch]
4157 $FullData,
4158
4159 [ValidateRange(1,10000)]
4160 [Int]
4161 $PageSize = 200
4162 )
4163
4164 begin {
4165 $SiteSearcher = Get-DomainSearcher -ADSpath $ADSpath -Domain $Domain -DomainController $DomainController -ADSprefix "CN=Sites,CN=Configuration" -PageSize $PageSize
4166 }
4167 process {
4168 if($SiteSearcher) {
4169
4170 if ($GUID) {
4171 # if we're filtering for a GUID in .gplink
4172 $SiteSearcher.filter="(&(objectCategory=site)(name=$SiteName)(gplink=*$GUID*))"
4173 }
4174 else {
4175 $SiteSearcher.filter="(&(objectCategory=site)(name=$SiteName))"
4176 }
4177
4178 try {
4179 $SiteSearcher.FindAll() | Where-Object {$_} | ForEach-Object {
4180 if ($FullData) {
4181 # convert/process the LDAP fields for each result
4182 Convert-LDAPProperty -Properties $_.Properties
4183 }
4184 else {
4185 # otherwise just return the site name
4186 $_.properties.name
4187 }
4188 }
4189 }
4190 catch {
4191 Write-Warning $_
4192 }
4193 }
4194 }
4195}
4196
4197
4198function Get-NetSubnet {
4199<#
4200 .SYNOPSIS
4201
4202 Gets a list of all current subnets in a domain.
4203
4204 .PARAMETER SiteName
4205
4206 Only return subnets from the specified SiteName.
4207
4208 .PARAMETER Domain
4209
4210 The domain to query for subnets, defaults to the current domain.
4211
4212 .PARAMETER DomainController
4213
4214 Domain controller to reflect LDAP queries through.
4215
4216 .PARAMETER ADSpath
4217
4218 The LDAP source to search through.
4219
4220 .PARAMETER FullData
4221
4222 Switch. Return full subnet objects instead of just object names (the default).
4223
4224 .PARAMETER PageSize
4225
4226 The PageSize to set for the LDAP searcher object.
4227
4228 .EXAMPLE
4229
4230 PS C:\> Get-NetSubnet
4231
4232 Returns all subnet names in the current domain.
4233
4234 .EXAMPLE
4235
4236 PS C:\> Get-NetSubnet -Domain testlab.local -FullData
4237
4238 Returns the full data objects for all subnets in testlab.local
4239#>
4240
4241 [CmdletBinding()]
4242 Param (
4243 [Parameter(ValueFromPipeline=$True)]
4244 [String]
4245 $SiteName = "*",
4246
4247 [String]
4248 $Domain,
4249
4250 [String]
4251 $ADSpath,
4252
4253 [String]
4254 $DomainController,
4255
4256 [Switch]
4257 $FullData,
4258
4259 [ValidateRange(1,10000)]
4260 [Int]
4261 $PageSize = 200
4262 )
4263
4264 begin {
4265 $SubnetSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -ADSprefix "CN=Subnets,CN=Sites,CN=Configuration" -PageSize $PageSize
4266 }
4267
4268 process {
4269 if($SubnetSearcher) {
4270
4271 $SubnetSearcher.filter="(&(objectCategory=subnet))"
4272
4273 try {
4274 $SubnetSearcher.FindAll() | Where-Object {$_} | ForEach-Object {
4275 if ($FullData) {
4276 # convert/process the LDAP fields for each result
4277 Convert-LDAPProperty -Properties $_.Properties | Where-Object { $_.siteobject -match "CN=$SiteName" }
4278 }
4279 else {
4280 # otherwise just return the subnet name and site name
4281 if ( ($SiteName -and ($_.properties.siteobject -match "CN=$SiteName,")) -or ($SiteName -eq '*')) {
4282
4283 $SubnetProperties = @{
4284 'Subnet' = $_.properties.name[0]
4285 }
4286 try {
4287 $SubnetProperties['Site'] = ($_.properties.siteobject[0]).split(",")[0]
4288 }
4289 catch {
4290 $SubnetProperties['Site'] = 'Error'
4291 }
4292
4293 New-Object -TypeName PSObject -Property $SubnetProperties
4294 }
4295 }
4296 }
4297 }
4298 catch {
4299 Write-Warning $_
4300 }
4301 }
4302 }
4303}
4304
4305
4306function Get-DomainSID {
4307<#
4308 .SYNOPSIS
4309
4310 Gets the SID for the domain.
4311
4312 .PARAMETER Domain
4313
4314 The domain to query, defaults to the current domain.
4315
4316 .EXAMPLE
4317
4318 C:\> Get-DomainSID -Domain TEST
4319
4320 Returns SID for the domain 'TEST'
4321#>
4322
4323 param(
4324 [String]
4325 $Domain
4326 )
4327
4328 $FoundDomain = Get-NetDomain -Domain $Domain
4329
4330 if($FoundDomain) {
4331 # query for the primary domain controller so we can extract the domain SID for filtering
4332 $PrimaryDC = $FoundDomain.PdcRoleOwner
4333 $PrimaryDCSID = (Get-NetComputer -Domain $Domain -ComputerName $PrimaryDC -FullData).objectsid
4334 $Parts = $PrimaryDCSID.split("-")
4335 $Parts[0..($Parts.length -2)] -join "-"
4336 }
4337}
4338
4339
4340function Get-NetGroup {
4341<#
4342 .SYNOPSIS
4343
4344 Gets a list of all current groups in a domain, or all
4345 the groups a given user/group object belongs to.
4346
4347 .PARAMETER GroupName
4348
4349 The group name to query for, wildcards accepted.
4350
4351 .PARAMETER SID
4352
4353 The group SID to query for.
4354
4355 .PARAMETER UserName
4356
4357 The user name (or group name) to query for all effective
4358 groups of.
4359
4360 .PARAMETER Filter
4361
4362 A customized ldap filter string to use, e.g. "(description=*admin*)"
4363
4364 .PARAMETER Domain
4365
4366 The domain to query for groups, defaults to the current domain.
4367
4368 .PARAMETER DomainController
4369
4370 Domain controller to reflect LDAP queries through.
4371
4372 .PARAMETER ADSpath
4373
4374 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
4375 Useful for OU queries.
4376
4377 .PARAMETER AdminCount
4378
4379 Switch. Return group with adminCount=1.
4380
4381 .PARAMETER FullData
4382
4383 Switch. Return full group objects instead of just object names (the default).
4384
4385 .PARAMETER RawSids
4386
4387 Switch. Return raw SIDs when using "Get-NetGroup -UserName X"
4388
4389 .PARAMETER PageSize
4390
4391 The PageSize to set for the LDAP searcher object.
4392
4393 .EXAMPLE
4394
4395 PS C:\> Get-NetGroup
4396
4397 Returns the current groups in the domain.
4398
4399 .EXAMPLE
4400
4401 PS C:\> Get-NetGroup -GroupName *admin*
4402
4403 Returns all groups with "admin" in their group name.
4404
4405 .EXAMPLE
4406
4407 PS C:\> Get-NetGroup -Domain testing -FullData
4408
4409 Returns full group data objects in the 'testing' domain
4410#>
4411
4412 [CmdletBinding()]
4413 param(
4414 [Parameter(ValueFromPipeline=$True)]
4415 [String]
4416 $GroupName = '*',
4417
4418 [String]
4419 $SID,
4420
4421 [String]
4422 $UserName,
4423
4424 [String]
4425 $Filter,
4426
4427 [String]
4428 $Domain,
4429
4430 [String]
4431 $DomainController,
4432
4433 [String]
4434 $ADSpath,
4435
4436 [Switch]
4437 $AdminCount,
4438
4439 [Switch]
4440 $FullData,
4441
4442 [Switch]
4443 $RawSids,
4444
4445 [ValidateRange(1,10000)]
4446 [Int]
4447 $PageSize = 200
4448 )
4449
4450 begin {
4451 $GroupSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize
4452 }
4453
4454 process {
4455 if($GroupSearcher) {
4456
4457 if($AdminCount) {
4458 Write-Verbose "Checking for adminCount=1"
4459 $Filter += "(admincount=1)"
4460 }
4461
4462 if ($UserName) {
4463 # get the raw user object
4464 $User = Get-ADObject -SamAccountName $UserName -Domain $Domain -DomainController $DomainController -ReturnRaw -PageSize $PageSize
4465
4466 # convert the user to a directory entry
4467 $UserDirectoryEntry = $User.GetDirectoryEntry()
4468
4469 # cause the cache to calculate the token groups for the user
4470 $UserDirectoryEntry.RefreshCache("tokenGroups")
4471
4472 $UserDirectoryEntry.TokenGroups | Foreach-Object {
4473 # convert the token group sid
4474 $GroupSid = (New-Object System.Security.Principal.SecurityIdentifier($_,0)).Value
4475
4476 # ignore the built in users and default domain user group
4477 if(!($GroupSid -match '^S-1-5-32-545|-513$')) {
4478 if($FullData) {
4479 Get-ADObject -SID $GroupSid -PageSize $PageSize
4480 }
4481 else {
4482 if($RawSids) {
4483 $GroupSid
4484 }
4485 else {
4486 Convert-SidToName $GroupSid
4487 }
4488 }
4489 }
4490 }
4491 }
4492 else {
4493 if ($SID) {
4494 $GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)"
4495 }
4496 else {
4497 $GroupSearcher.filter = "(&(objectCategory=group)(name=$GroupName)$Filter)"
4498 }
4499
4500 $GroupSearcher.FindAll() | Where-Object {$_} | ForEach-Object {
4501 # if we're returning full data objects
4502 if ($FullData) {
4503 # convert/process the LDAP fields for each result
4504 Convert-LDAPProperty -Properties $_.Properties
4505 }
4506 else {
4507 # otherwise we're just returning the group name
4508 $_.properties.samaccountname
4509 }
4510 }
4511 }
4512 }
4513 }
4514}
4515
4516
4517function Get-NetGroupMember {
4518<#
4519 .SYNOPSIS
4520
4521 This function users [ADSI] and LDAP to query the current AD context
4522 or trusted domain for users in a specified group. If no GroupName is
4523 specified, it defaults to querying the "Domain Admins" group.
4524 This is a replacement for "net group 'name' /domain"
4525
4526 .PARAMETER GroupName
4527
4528 The group name to query for users.
4529
4530 .PARAMETER SID
4531
4532 The Group SID to query for users. If not given, it defaults to 512 "Domain Admins"
4533
4534 .PARAMETER Filter
4535
4536 A customized ldap filter string to use, e.g. "(description=*admin*)"
4537
4538 .PARAMETER Domain
4539
4540 The domain to query for group users, defaults to the current domain.
4541
4542 .PARAMETER DomainController
4543
4544 Domain controller to reflect LDAP queries through.
4545
4546 .PARAMETER ADSpath
4547
4548 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
4549 Useful for OU queries.
4550
4551 .PARAMETER FullData
4552
4553 Switch. Returns full data objects instead of just group/users.
4554
4555 .PARAMETER Recurse
4556
4557 Switch. If the group member is a group, recursively try to query its members as well.
4558
4559 .PARAMETER UseMatchingRule
4560
4561 Switch. Use LDAP_MATCHING_RULE_IN_CHAIN in the LDAP search query when -Recurse is specified.
4562 Much faster than manual recursion, but doesn't reveal cross-domain groups.
4563
4564 .PARAMETER PageSize
4565
4566 The PageSize to set for the LDAP searcher object.
4567
4568 .EXAMPLE
4569
4570 PS C:\> Get-NetGroupMember
4571
4572 Returns the usernames that of members of the "Domain Admins" domain group.
4573
4574 .EXAMPLE
4575
4576 PS C:\> Get-NetGroupMember -Domain testing -GroupName "Power Users"
4577
4578 Returns the usernames that of members of the "Power Users" group in the 'testing' domain.
4579
4580 .LINK
4581
4582 http://www.powershellmagazine.com/2013/05/23/pstip-retrieve-group-membership-of-an-active-directory-group-recursively/
4583#>
4584
4585 [CmdletBinding()]
4586 param(
4587 [Parameter(ValueFromPipeline=$True)]
4588 [String]
4589 $GroupName,
4590
4591 [String]
4592 $SID,
4593
4594 [String]
4595 $Domain = (Get-NetDomain).Name,
4596
4597 [String]
4598 $DomainController,
4599
4600 [String]
4601 $ADSpath,
4602
4603 [Switch]
4604 $FullData,
4605
4606 [Switch]
4607 $Recurse,
4608
4609 [Switch]
4610 $UseMatchingRule,
4611
4612 [ValidateRange(1,10000)]
4613 [Int]
4614 $PageSize = 200
4615 )
4616
4617 begin {
4618 # so this isn't repeated if users are passed on the pipeline
4619 $GroupSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize
4620
4621 if(!$DomainController) {
4622 $DomainController = ((Get-NetDomain).PdcRoleOwner).Name
4623 }
4624 }
4625
4626 process {
4627
4628 if ($GroupSearcher) {
4629
4630 if ($Recurse -and $UseMatchingRule) {
4631 # resolve the group to a distinguishedname
4632 if ($GroupName) {
4633 $Group = Get-NetGroup -GroupName $GroupName -Domain $Domain -FullData -PageSize $PageSize
4634 }
4635 elseif ($SID) {
4636 $Group = Get-NetGroup -SID $SID -Domain $Domain -FullData -PageSize $PageSize
4637 }
4638 else {
4639 # default to domain admins
4640 $SID = (Get-DomainSID -Domain $Domain) + "-512"
4641 $Group = Get-NetGroup -SID $SID -Domain $Domain -FullData -PageSize $PageSize
4642 }
4643 $GroupDN = $Group.distinguishedname
4644 $GroupFoundName = $Group.name
4645
4646 if ($GroupDN) {
4647 $GroupSearcher.filter = "(&(samAccountType=805306368)(memberof:1.2.840.113556.1.4.1941:=$GroupDN)$Filter)"
4648 $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'))
4649
4650 $Members = $GroupSearcher.FindAll()
4651 $GroupFoundName = $GroupName
4652 }
4653 else {
4654 Write-Error "Unable to find Group"
4655 }
4656 }
4657 else {
4658 if ($GroupName) {
4659 $GroupSearcher.filter = "(&(objectCategory=group)(name=$GroupName)$Filter)"
4660 }
4661 elseif ($SID) {
4662 $GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)"
4663 }
4664 else {
4665 # default to domain admins
4666 $SID = (Get-DomainSID -Domain $Domain) + "-512"
4667 $GroupSearcher.filter = "(&(objectCategory=group)(objectSID=$SID)$Filter)"
4668 }
4669
4670 $GroupSearcher.FindAll() | ForEach-Object {
4671 try {
4672 if (!($_) -or !($_.properties) -or !($_.properties.name)) { continue }
4673
4674 $GroupFoundName = $_.properties.name[0]
4675 $Members = @()
4676
4677 if ($_.properties.member.Count -eq 0) {
4678 $Finished = $False
4679 $Bottom = 0
4680 $Top = 0
4681 while(!$Finished) {
4682 $Top = $Bottom + 1499
4683 $MemberRange="member;range=$Bottom-$Top"
4684 $Bottom += 1500
4685 $GroupSearcher.PropertiesToLoad.Clear()
4686 [void]$GroupSearcher.PropertiesToLoad.Add("$MemberRange")
4687 try {
4688 $Result = $GroupSearcher.FindOne()
4689 if ($Result) {
4690 $RangedProperty = $_.Properties.PropertyNames -like "member;range=*"
4691 $Results = $_.Properties.item($RangedProperty)
4692 if ($Results.count -eq 0) {
4693 $Finished = $True
4694 }
4695 else {
4696 $Results | ForEach-Object {
4697 $Members += $_
4698 }
4699 }
4700 }
4701 else {
4702 $Finished = $True
4703 }
4704 }
4705 catch [System.Management.Automation.MethodInvocationException] {
4706 $Finished = $True
4707 }
4708 }
4709 }
4710 else {
4711 $Members = $_.properties.member
4712 }
4713 }
4714 catch {
4715 Write-Verbose $_
4716 }
4717 }
4718 }
4719
4720 $Members | Where-Object {$_} | ForEach-Object {
4721 # if we're doing the LDAP_MATCHING_RULE_IN_CHAIN recursion
4722 if ($Recurse -and $UseMatchingRule) {
4723 $Properties = $_.Properties
4724 }
4725 else {
4726 if($DomainController) {
4727 $Result = [adsi]"LDAP://$DomainController/$_"
4728 }
4729 else {
4730 $Result = [adsi]"LDAP://$_"
4731 }
4732 if($Result){
4733 $Properties = $Result.Properties
4734 }
4735 }
4736
4737 if($Properties) {
4738
4739 if($Properties.samaccounttype -notmatch '805306368') {
4740 $IsGroup = $True
4741 }
4742 else {
4743 $IsGroup = $False
4744 }
4745
4746 if ($FullData) {
4747 $GroupMember = Convert-LDAPProperty -Properties $Properties
4748 }
4749 else {
4750 $GroupMember = New-Object PSObject
4751 }
4752
4753 $GroupMember | Add-Member Noteproperty 'GroupDomain' $Domain
4754 $GroupMember | Add-Member Noteproperty 'GroupName' $GroupFoundName
4755
4756 try {
4757 $MemberDN = $Properties.distinguishedname[0]
4758
4759 # extract the FQDN from the Distinguished Name
4760 $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
4761 }
4762 catch {
4763 $MemberDN = $Null
4764 $MemberDomain = $Null
4765 }
4766
4767 if ($Properties.samaccountname) {
4768 # forest users have the samAccountName set
4769 $MemberName = $Properties.samaccountname[0]
4770 }
4771 else {
4772 # external trust users have a SID, so convert it
4773 try {
4774 $MemberName = Convert-SidToName $Properties.cn[0]
4775 }
4776 catch {
4777 # if there's a problem contacting the domain to resolve the SID
4778 $MemberName = $Properties.cn
4779 }
4780 }
4781
4782 if($Properties.objectSid) {
4783 $MemberSid = ((New-Object System.Security.Principal.SecurityIdentifier $Properties.objectSid[0],0).Value)
4784 }
4785 else {
4786 $MemberSid = $Null
4787 }
4788
4789 $GroupMember | Add-Member Noteproperty 'MemberDomain' $MemberDomain
4790 $GroupMember | Add-Member Noteproperty 'MemberName' $MemberName
4791 $GroupMember | Add-Member Noteproperty 'MemberSid' $MemberSid
4792 $GroupMember | Add-Member Noteproperty 'IsGroup' $IsGroup
4793 $GroupMember | Add-Member Noteproperty 'MemberDN' $MemberDN
4794 $GroupMember
4795
4796 # if we're doing manual recursion
4797 if ($Recurse -and !$UseMatchingRule -and $IsGroup -and $MemberName) {
4798 Get-NetGroupMember -FullData -Domain $MemberDomain -DomainController $DomainController -GroupName $MemberName -Recurse -PageSize $PageSize
4799 }
4800 }
4801
4802 }
4803 }
4804 }
4805}
4806
4807
4808function Get-NetFileServer {
4809<#
4810 .SYNOPSIS
4811
4812 Returns a list of all file servers extracted from user
4813 homedirectory, scriptpath, and profilepath fields.
4814
4815 .PARAMETER Domain
4816
4817 The domain to query for user file servers, defaults to the current domain.
4818
4819 .PARAMETER DomainController
4820
4821 Domain controller to reflect LDAP queries through.
4822
4823 .PARAMETER TargetUsers
4824
4825 An array of users to query for file servers.
4826
4827 .PARAMETER PageSize
4828
4829 The PageSize to set for the LDAP searcher object.
4830
4831 .EXAMPLE
4832
4833 PS C:\> Get-NetFileServer
4834
4835 Returns active file servers.
4836
4837 .EXAMPLE
4838
4839 PS C:\> Get-NetFileServer -Domain testing
4840
4841 Returns active file servers for the 'testing' domain.
4842#>
4843
4844 [CmdletBinding()]
4845 param(
4846 [String]
4847 $Domain,
4848
4849 [String]
4850 $DomainController,
4851
4852 [String[]]
4853 $TargetUsers,
4854
4855 [ValidateRange(1,10000)]
4856 [Int]
4857 $PageSize = 200
4858 )
4859
4860 function SplitPath {
4861 # short internal helper to split UNC server paths
4862 param([String]$Path)
4863
4864 if ($Path -and ($Path.split("\\").Count -ge 3)) {
4865 $Temp = $Path.split("\\")[2]
4866 if($Temp -and ($Temp -ne '')) {
4867 $Temp
4868 }
4869 }
4870 }
4871
4872 Get-NetUser -Domain $Domain -DomainController $DomainController -PageSize $PageSize | Where-Object {$_} | Where-Object {
4873 # filter for any target users
4874 if($TargetUsers) {
4875 $TargetUsers -Match $_.samAccountName
4876 }
4877 else { $True }
4878 } | Foreach-Object {
4879 # split out every potential file server path
4880 if($_.homedirectory) {
4881 SplitPath($_.homedirectory)
4882 }
4883 if($_.scriptpath) {
4884 SplitPath($_.scriptpath)
4885 }
4886 if($_.profilepath) {
4887 SplitPath($_.profilepath)
4888 }
4889
4890 } | Where-Object {$_} | Sort-Object -Unique
4891}
4892
4893
4894function Get-DFSshare {
4895<#
4896 .SYNOPSIS
4897
4898 Returns a list of all fault-tolerant distributed file
4899 systems for a given domain.
4900
4901 .PARAMETER Version
4902
4903 The version of DFS to query for servers.
4904 1/v1, 2/v2, or all
4905
4906 .PARAMETER Domain
4907
4908 The domain to query for user DFS shares, defaults to the current domain.
4909
4910 .PARAMETER DomainController
4911
4912 Domain controller to reflect LDAP queries through.
4913
4914 .PARAMETER ADSpath
4915
4916 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
4917 Useful for OU queries.
4918
4919 .PARAMETER PageSize
4920
4921 The PageSize to set for the LDAP searcher object.
4922
4923 .EXAMPLE
4924
4925 PS C:\> Get-DFSshare
4926
4927 Returns all distributed file system shares for the current domain.
4928
4929 .EXAMPLE
4930
4931 PS C:\> Get-DFSshare -Domain test
4932
4933 Returns all distributed file system shares for the 'test' domain.
4934#>
4935
4936 [CmdletBinding()]
4937 param(
4938 [String]
4939 [ValidateSet("All","V1","1","V2","2")]
4940 $Version = "All",
4941
4942 [String]
4943 $Domain,
4944
4945 [String]
4946 $DomainController,
4947
4948 [String]
4949 $ADSpath,
4950
4951 [ValidateRange(1,10000)]
4952 [Int]
4953 $PageSize = 200
4954 )
4955
4956 function Get-DFSshareV1 {
4957 [CmdletBinding()]
4958 param(
4959 [String]
4960 $Domain,
4961
4962 [String]
4963 $DomainController,
4964
4965 [String]
4966 $ADSpath,
4967
4968 [ValidateRange(1,10000)]
4969 [Int]
4970 $PageSize = 200
4971 )
4972
4973 $DFSsearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize
4974
4975 if($DFSsearcher) {
4976 $DFSshares = @()
4977 $DFSsearcher.filter = "(&(objectClass=fTDfs))"
4978
4979 try {
4980 $DFSSearcher.FindAll() | Where-Object {$_} | ForEach-Object {
4981 $Properties = $_.Properties
4982 $RemoteNames = $Properties.remoteservername
4983
4984 $DFSshares += $RemoteNames | ForEach-Object {
4985 try {
4986 if ( $_.Contains('\') ) {
4987 New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'RemoteServerName'=$_.split("\")[2]}
4988 }
4989 }
4990 catch {
4991 Write-Debug "Error in parsing DFS share : $_"
4992 }
4993 }
4994 }
4995 }
4996 catch {
4997 Write-Warning "Get-DFSshareV2 error : $_"
4998 }
4999 $DFSshares | Sort-Object -Property "RemoteServerName"
5000 }
5001 }
5002
5003 function Get-DFSshareV2 {
5004 [CmdletBinding()]
5005 param(
5006 [String]
5007 $Domain,
5008
5009 [String]
5010 $DomainController,
5011
5012 [String]
5013 $ADSpath,
5014
5015 [ValidateRange(1,10000)]
5016 [Int]
5017 $PageSize = 200
5018 )
5019
5020 $DFSsearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize
5021
5022 if($DFSsearcher) {
5023 $DFSshares = @()
5024 $DFSsearcher.filter = "(&(objectClass=msDFS-Linkv2))"
5025 $DFSSearcher.PropertiesToLoad.AddRange(('msdfs-linkpathv2','msDFS-TargetListv2'))
5026
5027 try {
5028 $DFSSearcher.FindAll() | Where-Object {$_} | ForEach-Object {
5029 $Properties = $_.Properties
5030 $target_list = $Properties.'msdfs-targetlistv2'[0]
5031 $xml = [xml][System.Text.Encoding]::Unicode.GetString($target_list[2..($target_list.Length-1)])
5032 $DFSshares += $xml.targets.ChildNodes | ForEach-Object {
5033 try {
5034 $Target = $_.InnerText
5035 if ( $Target.Contains('\') ) {
5036 $DFSroot = $Target.split("\")[3]
5037 $ShareName = $Properties.'msdfs-linkpathv2'[0]
5038 New-Object -TypeName PSObject -Property @{'Name'="$DFSroot$ShareName";'RemoteServerName'=$Target.split("\")[2]}
5039 }
5040 }
5041 catch {
5042 Write-Debug "Error in parsing target : $_"
5043 }
5044 }
5045 }
5046 }
5047 catch {
5048 Write-Warning "Get-DFSshareV2 error : $_"
5049 }
5050 $DFSshares | Sort-Object -Unique -Property "RemoteServerName"
5051 }
5052 }
5053
5054 $DFSshares = @()
5055
5056 if ( ($Version -eq "all") -or ($Version.endsWith("1")) ) {
5057 $DFSshares += Get-DFSshareV1 -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize
5058 }
5059 if ( ($Version -eq "all") -or ($Version.endsWith("2")) ) {
5060 $DFSshares += Get-DFSshareV2 -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize
5061 }
5062
5063 $DFSshares | Sort-Object -Property "RemoteServerName"
5064}
5065
5066
5067########################################################
5068#
5069# GPO related functions.
5070#
5071########################################################
5072
5073function Get-GptTmpl {
5074<#
5075 .SYNOPSIS
5076
5077 Helper to parse a GptTmpl.inf policy file path into a custom object.
5078
5079 .PARAMETER GptTmplPath
5080
5081 The GptTmpl.inf file path name to parse.
5082
5083 .PARAMETER UsePSDrive
5084
5085 Switch. Mount the target GptTmpl folder path as a temporary PSDrive.
5086
5087 .EXAMPLE
5088
5089 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"
5090
5091 Parse the default domain policy .inf for dev.testlab.local
5092#>
5093
5094 [CmdletBinding()]
5095 Param (
5096 [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
5097 [String]
5098 $GptTmplPath,
5099
5100 [Switch]
5101 $UsePSDrive
5102 )
5103
5104 begin {
5105 if($UsePSDrive) {
5106 # if we're PSDrives, create a temporary mount point
5107 $Parts = $GptTmplPath.split('\')
5108 $FolderPath = $Parts[0..($Parts.length-2)] -join '\'
5109 $FilePath = $Parts[-1]
5110 $RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join ''
5111
5112 Write-Verbose "Mounting path $GptTmplPath using a temp PSDrive at $RandDrive"
5113
5114 try {
5115 $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop
5116 }
5117 catch {
5118 Write-Debug "Error mounting path $GptTmplPath : $_"
5119 return $Null
5120 }
5121
5122 # so we can cd/dir the new drive
5123 $GptTmplPath = $RandDrive + ":\" + $FilePath
5124 }
5125 }
5126
5127 process {
5128 $SectionName = ''
5129 $SectionsTemp = @{}
5130 $SectionsFinal = @{}
5131
5132 try {
5133
5134 if(Test-Path $GptTmplPath) {
5135
5136 Write-Verbose "Parsing $GptTmplPath"
5137
5138 Get-Content $GptTmplPath -ErrorAction Stop | Foreach-Object {
5139 if ($_ -match '\[') {
5140 # this signifies that we're starting a new section
5141 $SectionName = $_.trim('[]') -replace ' ',''
5142 }
5143 elseif($_ -match '=') {
5144 $Parts = $_.split('=')
5145 $PropertyName = $Parts[0].trim()
5146 $PropertyValues = $Parts[1].trim()
5147
5148 if($PropertyValues -match ',') {
5149 $PropertyValues = $PropertyValues.split(',')
5150 }
5151
5152 if(!$SectionsTemp[$SectionName]) {
5153 $SectionsTemp.Add($SectionName, @{})
5154 }
5155
5156 # add the parsed property into the relevant Section name
5157 $SectionsTemp[$SectionName].Add( $PropertyName, $PropertyValues )
5158 }
5159 }
5160
5161 ForEach ($Section in $SectionsTemp.keys) {
5162 # transform each nested hash table into a custom object
5163 $SectionsFinal[$Section] = New-Object PSObject -Property $SectionsTemp[$Section]
5164 }
5165
5166 # transform the parent hash table into a custom object
5167 New-Object PSObject -Property $SectionsFinal
5168 }
5169 }
5170 catch {
5171 Write-Debug "Error parsing $GptTmplPath : $_"
5172 }
5173 }
5174
5175 end {
5176 if($UsePSDrive -and $RandDrive) {
5177 Write-Verbose "Removing temp PSDrive $RandDrive"
5178 Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive
5179 }
5180 }
5181}
5182
5183
5184function Get-GroupsXML {
5185<#
5186 .SYNOPSIS
5187
5188 Helper to parse a groups.xml file path into a custom object.
5189
5190 .PARAMETER GroupsXMLpath
5191
5192 The groups.xml file path name to parse.
5193
5194 .PARAMETER ResolveSids
5195
5196 Switch. Resolve Sids from a DC policy to object names.
5197
5198 .PARAMETER UsePSDrive
5199
5200 Switch. Mount the target groups.xml folder path as a temporary PSDrive.
5201
5202#>
5203
5204 [CmdletBinding()]
5205 Param (
5206 [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
5207 [String]
5208 $GroupsXMLPath,
5209
5210 [Switch]
5211 $ResolveSids,
5212
5213 [Switch]
5214 $UsePSDrive
5215 )
5216
5217 begin {
5218 if($UsePSDrive) {
5219 # if we're PSDrives, create a temporary mount point
5220 $Parts = $GroupsXMLPath.split('\')
5221 $FolderPath = $Parts[0..($Parts.length-2)] -join '\'
5222 $FilePath = $Parts[-1]
5223 $RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join ''
5224
5225 Write-Verbose "Mounting path $GroupsXMLPath using a temp PSDrive at $RandDrive"
5226
5227 try {
5228 $Null = New-PSDrive -Name $RandDrive -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop
5229 }
5230 catch {
5231 Write-Debug "Error mounting path $GroupsXMLPath : $_"
5232 return $Null
5233 }
5234
5235 # so we can cd/dir the new drive
5236 $GroupsXMLPath = $RandDrive + ":\" + $FilePath
5237 }
5238 }
5239
5240 process {
5241
5242 # parse the Groups.xml file if it exists
5243 if(Test-Path $GroupsXMLPath) {
5244
5245 [xml] $GroupsXMLcontent = Get-Content $GroupsXMLPath
5246
5247 # process all group properties in the XML
5248 $GroupsXMLcontent | Select-Xml "//Group" | Select-Object -ExpandProperty node | ForEach-Object {
5249
5250 $Members = @()
5251 $MemberOf = @()
5252
5253 # extract the localgroup sid for memberof
5254 $LocalSid = $_.Properties.GroupSid
5255 if(!$LocalSid) {
5256 if($_.Properties.groupName -match 'Administrators') {
5257 $LocalSid = 'S-1-5-32-544'
5258 }
5259 elseif($_.Properties.groupName -match 'Remote Desktop') {
5260 $LocalSid = 'S-1-5-32-555'
5261 }
5262 else {
5263 $LocalSid = $_.Properties.groupName
5264 }
5265 }
5266 $MemberOf = @($LocalSid)
5267
5268 $_.Properties.members | ForEach-Object {
5269 # process each member of the above local group
5270 $_ | Select-Object -ExpandProperty Member | Where-Object { $_.action -match 'ADD' } | ForEach-Object {
5271
5272 if($_.sid) {
5273 $Members += $_.sid
5274 }
5275 else {
5276 # just a straight local account name
5277 $Members += $_.name
5278 }
5279 }
5280 }
5281
5282 if ($Members -or $Memberof) {
5283 # extract out any/all filters...I hate you GPP
5284 $Filters = $_.filters | ForEach-Object {
5285 $_ | Select-Object -ExpandProperty Filter* | ForEach-Object {
5286 New-Object -TypeName PSObject -Property @{'Type' = $_.LocalName;'Value' = $_.name}
5287 }
5288 }
5289
5290 if($ResolveSids) {
5291 $Memberof = $Memberof | ForEach-Object {Convert-SidToName $_}
5292 $Members = $Members | ForEach-Object {Convert-SidToName $_}
5293 }
5294
5295 if($Memberof -isnot [system.array]) {$Memberof = @($Memberof)}
5296 if($Members -isnot [system.array]) {$Members = @($Members)}
5297
5298 $GPOProperties = @{
5299 'GPODisplayName' = $GPODisplayName
5300 'GPOName' = $GPOName
5301 'GPOPath' = $GroupsXMLPath
5302 'Filters' = $Filters
5303 'MemberOf' = $Memberof
5304 'Members' = $Members
5305 }
5306
5307 New-Object -TypeName PSObject -Property $GPOProperties
5308 }
5309 }
5310 }
5311 }
5312
5313 end {
5314 if($UsePSDrive -and $RandDrive) {
5315 Write-Verbose "Removing temp PSDrive $RandDrive"
5316 Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive
5317 }
5318 }
5319}
5320
5321
5322
5323function Get-NetGPO {
5324<#
5325 .SYNOPSIS
5326
5327 Gets a list of all current GPOs in a domain.
5328
5329 .PARAMETER GPOname
5330
5331 The GPO name to query for, wildcards accepted.
5332
5333 .PARAMETER DisplayName
5334
5335 The GPO display name to query for, wildcards accepted.
5336
5337 .PARAMETER Domain
5338
5339 The domain to query for GPOs, defaults to the current domain.
5340
5341 .PARAMETER DomainController
5342
5343 Domain controller to reflect LDAP queries through.
5344
5345 .PARAMETER ADSpath
5346
5347 The LDAP source to search through
5348 e.g. "LDAP://cn={8FF59D28-15D7-422A-BCB7-2AE45724125A},cn=policies,cn=system,DC=dev,DC=testlab,DC=local"
5349
5350 .PARAMETER PageSize
5351
5352 The PageSize to set for the LDAP searcher object.
5353
5354 .EXAMPLE
5355
5356 PS C:\> Get-NetGPO -Domain testlab.local
5357
5358 Returns the GPOs in the 'testlab.local' domain.
5359#>
5360 [CmdletBinding()]
5361 Param (
5362 [Parameter(ValueFromPipeline=$True)]
5363 [String]
5364 $GPOname = '*',
5365
5366 [String]
5367 $DisplayName,
5368
5369 [String]
5370 $Domain,
5371
5372 [String]
5373 $DomainController,
5374
5375 [String]
5376 $ADSpath,
5377
5378 [ValidateRange(1,10000)]
5379 [Int]
5380 $PageSize = 200
5381
5382 )
5383
5384 begin {
5385 $GPOSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize
5386 }
5387
5388 process {
5389 if ($GPOSearcher) {
5390 if($DisplayName) {
5391 $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(displayname=$DisplayName))"
5392 }
5393 else {
5394 $GPOSearcher.filter="(&(objectCategory=groupPolicyContainer)(name=$GPOname))"
5395 }
5396
5397 $GPOSearcher.FindAll() | Where-Object {$_} | ForEach-Object {
5398 # convert/process the LDAP fields for each result
5399 Convert-LDAPProperty -Properties $_.Properties
5400 }
5401 }
5402 }
5403}
5404
5405
5406function Get-NetGPOGroup {
5407<#
5408 .SYNOPSIS
5409
5410 Returns all GPOs in a domain that set "Restricted Groups"
5411 or use groups.xml on on target machines.
5412
5413 .PARAMETER GPOname
5414
5415 The GPO name to query for, wildcards accepted.
5416
5417 .PARAMETER DisplayName
5418
5419 The GPO display name to query for, wildcards accepted.
5420
5421 .PARAMETER ResolveSids
5422
5423 Switch. Resolve Sids from a DC policy to object names.
5424
5425 .PARAMETER Domain
5426
5427 The domain to query for GPOs, defaults to the current domain.
5428
5429 .PARAMETER DomainController
5430
5431 Domain controller to reflect LDAP queries through.
5432
5433 .PARAMETER ADSpath
5434
5435 The LDAP source to search through
5436 e.g. "LDAP://cn={8FF59D28-15D7-422A-BCB7-2AE45724125A},cn=policies,cn=system,DC=dev,DC=testlab,DC=local"
5437
5438 .PARAMETER PageSize
5439
5440 The PageSize to set for the LDAP searcher object.
5441
5442 .PARAMETER UsePSDrive
5443
5444 Switch. Mount any found policy files with temporary PSDrives.
5445
5446 .EXAMPLE
5447
5448 PS C:\> Get-NetGPOGroup
5449
5450 Get all GPOs that set local groups on the current domain.
5451#>
5452
5453 [CmdletBinding()]
5454 Param (
5455 [String]
5456 $GPOname = '*',
5457
5458 [String]
5459 $DisplayName,
5460
5461 [Switch]
5462 $ResolveSids,
5463
5464 [String]
5465 $Domain,
5466
5467 [String]
5468 $DomainController,
5469
5470 [String]
5471 $ADSpath,
5472
5473 [Switch]
5474 $UsePSDrive,
5475
5476 [ValidateRange(1,10000)]
5477 [Int]
5478 $PageSize = 200
5479 )
5480
5481 # get every GPO from the specified domain with restricted groups set
5482 Get-NetGPO -GPOName $GPOname -DisplayName $GPOname -Domain $Domain -DomainController $DomainController -ADSpath $ADSpath -PageSize $PageSize | Foreach-Object {
5483
5484 $Memberof = $Null
5485 $Members = $Null
5486 $GPOdisplayName = $_.displayname
5487 $GPOname = $_.name
5488 $GPOPath = $_.gpcfilesyspath
5489
5490 $ParseArgs = @{
5491 'GptTmplPath' = "$GPOPath\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
5492 'UsePSDrive' = $UsePSDrive
5493 }
5494
5495 # parse the GptTmpl.inf 'Restricted Groups' file if it exists
5496 $Inf = Get-GptTmpl @ParseArgs
5497
5498 if($Inf.GroupMembership) {
5499
5500 $Memberof = $Inf.GroupMembership | Get-Member *Memberof | ForEach-Object { $Inf.GroupMembership.($_.name) } | ForEach-Object { $_.trim('*') }
5501 $Members = $Inf.GroupMembership | Get-Member *Members | ForEach-Object { $Inf.GroupMembership.($_.name) } | ForEach-Object { $_.trim('*') }
5502
5503 # only return an object if Members are found
5504 if ($Members -or $Memberof) {
5505
5506 # if there is no Memberof defined, assume local admins
5507 if(!$Memberof) {
5508 $Memberof = 'S-1-5-32-544'
5509 }
5510
5511 if($ResolveSids) {
5512 $Memberof = $Memberof | ForEach-Object {Convert-SidToName $_}
5513 $Members = $Members | ForEach-Object {Convert-SidToName $_}
5514 }
5515
5516 if($Memberof -isnot [system.array]) {$Memberof = @($Memberof)}
5517 if($Members -isnot [system.array]) {$Members = @($Members)}
5518
5519 $GPOProperties = @{
5520 'GPODisplayName' = $GPODisplayName
5521 'GPOName' = $GPOName
5522 'GPOPath' = $GPOPath
5523 'Filters' = $Null
5524 'MemberOf' = $Memberof
5525 'Members' = $Members
5526 }
5527
5528 New-Object -TypeName PSObject -Property $GPOProperties
5529 }
5530 }
5531
5532 $ParseArgs = @{
5533 'GroupsXMLpath' = "$GPOPath\MACHINE\Preferences\Groups\Groups.xml"
5534 'ResolveSids' = $ResolveSids
5535 'UsePSDrive' = $UsePSDrive
5536 }
5537
5538 Get-GroupsXML @ParseArgs
5539 }
5540}
5541
5542
5543function Find-GPOLocation {
5544<#
5545 .SYNOPSIS
5546
5547 Takes a user/group name and optional domain, and determines
5548 the computers in the domain the user/group has local admin
5549 (or RDP) rights to.
5550
5551 It does this by:
5552 1. resolving the user/group to its proper sid
5553 2. enumerating all groups the user/group is a current part of
5554 and extracting all target SIDs to build a target SID list
5555 3. pulling all GPOs that set 'Restricted Groups' by calling
5556 Get-NetGPOGroup
5557 4. matching the target sid list to the queried GPO SID list
5558 to enumerate all GPO the user is effectively applied with
5559 5. enumerating all OUs and sites and applicable GPO GUIs are
5560 applied to through gplink enumerating
5561 6. querying for all computers under the given OUs or sites
5562
5563 .PARAMETER UserName
5564
5565 A (single) user name name to query for access.
5566
5567 .PARAMETER GroupName
5568
5569 A (single) group name name to query for access.
5570
5571 .PARAMETER Domain
5572
5573 Optional domain the user exists in for querying, defaults to the current domain.
5574
5575 .PARAMETER DomainController
5576
5577 Domain controller to reflect LDAP queries through.
5578
5579 .PARAMETER LocalGroup
5580
5581 The local group to check access against.
5582 Can be "Administrators" (S-1-5-32-544), "RDP/Remote Desktop Users" (S-1-5-32-555),
5583 or a custom local SID. Defaults to local 'Administrators'.
5584
5585 .PARAMETER UsePSDrive
5586
5587 Switch. Mount any found policy files with temporary PSDrives.
5588
5589 .PARAMETER PageSize
5590
5591 The PageSize to set for the LDAP searcher object.
5592
5593 .EXAMPLE
5594
5595 PS C:\> Find-GPOLocation -UserName dfm
5596
5597 Find all computers that dfm user has local administrator rights to in
5598 the current domain.
5599
5600 .EXAMPLE
5601
5602 PS C:\> Find-GPOLocation -UserName dfm -Domain dev.testlab.local
5603
5604 Find all computers that dfm user has local administrator rights to in
5605 the dev.testlab.local domain.
5606
5607 .EXAMPLE
5608
5609 PS C:\> Find-GPOLocation -UserName jason -LocalGroup RDP
5610
5611 Find all computers that jason has local RDP access rights to in the domain.
5612#>
5613
5614 [CmdletBinding()]
5615 Param (
5616 [String]
5617 $UserName,
5618
5619 [String]
5620 $GroupName,
5621
5622 [String]
5623 $Domain,
5624
5625 [String]
5626 $DomainController,
5627
5628 [String]
5629 $LocalGroup = 'Administrators',
5630
5631 [Switch]
5632 $UsePSDrive,
5633
5634 [ValidateRange(1,10000)]
5635 [Int]
5636 $PageSize = 200
5637 )
5638
5639 if($UserName) {
5640
5641 $User = Get-NetUser -UserName $UserName -Domain $Domain -DomainController $DomainController -PageSize $PageSize
5642 $UserSid = $User.objectsid
5643
5644 if(!$UserSid) {
5645 Throw "User '$UserName' not found!"
5646 }
5647
5648 $TargetSid = $UserSid
5649 $ObjectSamAccountName = $User.samaccountname
5650 $ObjectDistName = $User.distinguishedname
5651 }
5652 elseif($GroupName) {
5653
5654 $Group = Get-NetGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize
5655 $GroupSid = $Group.objectsid
5656
5657 if(!$GroupSid) {
5658 Throw "Group '$GroupName' not found!"
5659 }
5660
5661 $TargetSid = $GroupSid
5662 $ObjectSamAccountName = $Group.samaccountname
5663 $ObjectDistName = $Group.distinguishedname
5664 }
5665 else {
5666 throw "-UserName or -GroupName must be specified!"
5667 }
5668
5669 if($LocalGroup -like "*Admin*") {
5670 $LocalSID = "S-1-5-32-544"
5671 }
5672 elseif ( ($LocalGroup -like "*RDP*") -or ($LocalGroup -like "*Remote*") ) {
5673 $LocalSID = "S-1-5-32-555"
5674 }
5675 elseif ($LocalGroup -like "S-1-5*") {
5676 $LocalSID = $LocalGroup
5677 }
5678 else {
5679 throw "LocalGroup must be 'Administrators', 'RDP', or a 'S-1-5-X' type sid."
5680 }
5681
5682 Write-Verbose "LocalSid: $LocalSID"
5683 Write-Verbose "TargetSid: $TargetSid"
5684 Write-Verbose "TargetObjectDistName: $ObjectDistName"
5685
5686 if($TargetSid -isnot [system.array]) { $TargetSid = @($TargetSid) }
5687
5688 # use the tokenGroups approach from Get-NetGroup to get all effective
5689 # security SIDs this object is a part of
5690 $TargetSid += Get-NetGroup -Domain $Domain -DomainController $DomainController -PageSize $PageSize -UserName $ObjectSamAccountName -RawSids
5691
5692 if($TargetSid -isnot [system.array]) { $TargetSid = @($TargetSid) }
5693
5694 Write-Verbose "Effective target sids: $TargetSid"
5695
5696 $GPOGroupArgs = @{
5697 'Domain' = $Domain
5698 'DomainController' = $DomainController
5699 'UsePSDrive' = $UsePSDrive
5700 'PageSize' = $PageSize
5701 }
5702
5703 # get all GPO groups, and filter on ones that match our target SID list
5704 # and match the target local sid memberof list
5705 $GPOgroups = Get-NetGPOGroup @GPOGroupArgs | ForEach-Object {
5706
5707 if ($_.members) {
5708 $_.members = $_.members | Where-Object {$_} | ForEach-Object {
5709 if($_ -match "S-1-5") {
5710 $_
5711 }
5712 else {
5713 # if there are any plain group names, try to resolve them to sids
5714 Convert-NameToSid -ObjectName $_ -Domain $Domain
5715 }
5716 }
5717
5718 # stop PowerShell 2.0's string stupid unboxing
5719 if($_.members -isnot [system.array]) { $_.members = @($_.members) }
5720 if($_.memberof -isnot [system.array]) { $_.memberof = @($_.memberof) }
5721
5722 if($_.members) {
5723 try {
5724 # only return groups that contain a target sid
5725
5726 # TODO: fix stupid weird "-DifferenceObject" is null error
5727 if( (Compare-Object -ReferenceObject $_.members -DifferenceObject $TargetSid -IncludeEqual -ExcludeDifferent) ) {
5728 if ($_.memberof -contains $LocalSid) {
5729 $_
5730 }
5731 }
5732 }
5733 catch {
5734 Write-Debug "Error comparing members and $TargetSid : $_"
5735 }
5736 }
5737 }
5738 }
5739
5740 Write-Verbose "GPOgroups: $GPOgroups"
5741 $ProcessedGUIDs = @{}
5742
5743 # process the matches and build the result objects
5744 $GPOgroups | Where-Object {$_} | ForEach-Object {
5745
5746 $GPOguid = $_.GPOName
5747
5748 if( -not $ProcessedGUIDs[$GPOguid] ) {
5749 $GPOname = $_.GPODisplayName
5750 $Filters = $_.Filters
5751
5752 # find any OUs that have this GUID applied
5753 Get-NetOU -Domain $Domain -DomainController $DomainController -GUID $GPOguid -FullData -PageSize $PageSize | ForEach-Object {
5754
5755 if($Filters) {
5756 # filter for computer name/org unit if a filter is specified
5757 # TODO: handle other filters?
5758 $OUComputers = Get-NetComputer -ADSpath $_.ADSpath -FullData -PageSize $PageSize | Where-Object {
5759 $_.adspath -match ($Filters.Value)
5760 } | ForEach-Object { $_.dnshostname }
5761 }
5762 else {
5763 $OUComputers = Get-NetComputer -ADSpath $_.ADSpath -PageSize $PageSize
5764 }
5765
5766 $GPOLocation = New-Object PSObject
5767 $GPOLocation | Add-Member Noteproperty 'ObjectName' $ObjectDistName
5768 $GPOLocation | Add-Member Noteproperty 'GPOname' $GPOname
5769 $GPOLocation | Add-Member Noteproperty 'GPOguid' $GPOguid
5770 $GPOLocation | Add-Member Noteproperty 'ContainerName' $_.distinguishedname
5771 $GPOLocation | Add-Member Noteproperty 'Computers' $OUComputers
5772 $GPOLocation
5773 }
5774
5775 # find any sites that have this GUID applied
5776 # TODO: fix, this isn't the correct way to query computers from a site...
5777 # Get-NetSite -GUID $GPOguid -FullData | Foreach-Object {
5778 # if($Filters) {
5779 # # filter for computer name/org unit if a filter is specified
5780 # # TODO: handle other filters?
5781 # $SiteComptuers = Get-NetComputer -ADSpath $_.ADSpath -FullData | ? {
5782 # $_.adspath -match ($Filters.Value)
5783 # } | Foreach-Object {$_.dnshostname}
5784 # }
5785 # else {
5786 # $SiteComptuers = Get-NetComputer -ADSpath $_.ADSpath
5787 # }
5788
5789 # $SiteComptuers = Get-NetComputer -ADSpath $_.ADSpath
5790 # $out = New-Object PSObject
5791 # $out | Add-Member Noteproperty 'Object' $ObjectDistName
5792 # $out | Add-Member Noteproperty 'GPOname' $GPOname
5793 # $out | Add-Member Noteproperty 'GPOguid' $GPOguid
5794 # $out | Add-Member Noteproperty 'ContainerName' $_.distinguishedname
5795 # $out | Add-Member Noteproperty 'Computers' $OUComputers
5796 # $out
5797 # }
5798
5799 # mark off this GPO GUID so we don't process it again if there are dupes
5800 $ProcessedGUIDs[$GPOguid] = $True
5801 }
5802 }
5803
5804}
5805
5806
5807function Find-GPOComputerAdmin {
5808<#
5809 .SYNOPSIS
5810
5811 Takes a computer (or GPO) object and determines what users/groups have
5812 administrative access over it.
5813
5814 Inverse of Find-GPOLocation.
5815
5816 .PARAMETER ComputerName
5817
5818 The computer to determine local administrative access to.
5819
5820 .PARAMETER OUName
5821
5822 OU name to determine who has local adminisrtative acess to computers
5823 within it.
5824
5825 .PARAMETER Domain
5826
5827 Optional domain the computer/OU exists in, defaults to the current domain.
5828
5829 .PARAMETER DomainController
5830
5831 Domain controller to reflect LDAP queries through.
5832
5833 .PARAMETER Recurse
5834
5835 Switch. If a returned member is a group, recurse and get all members.
5836
5837 .PARAMETER LocalGroup
5838
5839 The local group to check access against.
5840 Can be "Administrators" (S-1-5-32-544), "RDP/Remote Desktop Users" (S-1-5-32-555),
5841 or a custom local SID.
5842 Defaults to local 'Administrators'.
5843
5844 .PARAMETER UsePSDrive
5845
5846 Switch. Mount any found policy files with temporary PSDrives.
5847
5848 .PARAMETER PageSize
5849
5850 The PageSize to set for the LDAP searcher object.
5851
5852 .EXAMPLE
5853
5854 PS C:\> Find-GPOComputerAdmin -ComputerName WINDOWS3.dev.testlab.local
5855
5856 Finds users who have local admin rights over WINDOWS3 through GPO correlation.
5857
5858 .EXAMPLE
5859
5860 PS C:\> Find-GPOComputerAdmin -ComputerName WINDOWS3.dev.testlab.local -LocalGroup RDP
5861
5862 Finds users who have RDP rights over WINDOWS3 through GPO correlation.
5863#>
5864
5865 [CmdletBinding()]
5866 Param (
5867 [Parameter(ValueFromPipeline=$True)]
5868 [String]
5869 $ComputerName,
5870
5871 [String]
5872 $OUName,
5873
5874 [String]
5875 $Domain,
5876
5877 [String]
5878 $DomainController,
5879
5880 [Switch]
5881 $Recurse,
5882
5883 [String]
5884 $LocalGroup = 'Administrators',
5885
5886 [Switch]
5887 $UsePSDrive,
5888
5889 [ValidateRange(1,10000)]
5890 [Int]
5891 $PageSize = 200
5892 )
5893
5894 process {
5895
5896 if(!$ComputerName -and !$OUName) {
5897 Throw "-ComputerName or -OUName must be provided"
5898 }
5899
5900 if($ComputerName) {
5901 $Computers = Get-NetComputer -ComputerName $ComputerName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize
5902
5903 if(!$Computers) {
5904 throw "Computer $Computer in domain '$Domain' not found!"
5905 }
5906
5907 ForEach($Computer in $Computers) {
5908 # extract all OUs a computer is a part of
5909 $DN = $Computer.distinguishedname
5910
5911 $TargetOUs = $DN.split(",") | Foreach-Object {
5912 if($_.startswith("OU=")) {
5913 $DN.substring($DN.indexof($_))
5914 }
5915 }
5916 }
5917 }
5918 else {
5919 $TargetOUs = @($OUName)
5920 }
5921
5922 Write-Verbose "Target OUs: $TargetOUs"
5923
5924 $TargetOUs | Where-Object {$_} | Foreach-Object {
5925
5926 $OU = $_
5927
5928 # for each OU the computer is a part of, get the full OU object
5929 $GPOgroups = Get-NetOU -Domain $Domain -DomainController $DomainController -ADSpath $_ -FullData -PageSize $PageSize | Foreach-Object {
5930 # and then get any GPO links
5931 $_.gplink.split("][") | Foreach-Object {
5932 if ($_.startswith("LDAP")) {
5933 $_.split(";")[0]
5934 }
5935 }
5936 } | Foreach-Object {
5937 $GPOGroupArgs = @{
5938 'Domain' = $Domain
5939 'DomainController' = $DomainController
5940 'ADSpath' = $_
5941 'UsePSDrive' = $UsePSDrive
5942 'PageSize' = $PageSize
5943 }
5944
5945 # for each GPO link, get any locally set user/group SIDs
5946 Get-NetGPOGroup @GPOGroupArgs
5947 }
5948
5949 # for each found GPO group, resolve the SIDs of the members
5950 $GPOgroups | Where-Object {$_} | Foreach-Object {
5951 $GPO = $_
5952 $GPO.members | Foreach-Object {
5953
5954 # resolvethis SID to a domain object
5955 $Object = Get-ADObject -Domain $Domain -DomainController $DomainController $_ -PageSize $PageSize
5956
5957 $GPOComputerAdmin = New-Object PSObject
5958 $GPOComputerAdmin | Add-Member Noteproperty 'ComputerName' $ComputerName
5959 $GPOComputerAdmin | Add-Member Noteproperty 'OU' $OU
5960 $GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPO.GPODisplayName
5961 $GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPO.GPOPath
5962 $GPOComputerAdmin | Add-Member Noteproperty 'ObjectName' $Object.name
5963 $GPOComputerAdmin | Add-Member Noteproperty 'ObjectDN' $Object.distinguishedname
5964 $GPOComputerAdmin | Add-Member Noteproperty 'ObjectSID' $_
5965 $GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $($Object.samaccounttype -notmatch '805306368')
5966 $GPOComputerAdmin
5967
5968 # if we're recursing and the current result object is a group
5969 if($Recurse -and $GPOComputerAdmin.isGroup) {
5970
5971 Get-NetGroupMember -SID $_ -FullData -Recurse -PageSize $PageSize | Foreach-Object {
5972
5973 $MemberDN = $_.distinguishedName
5974
5975 # extract the FQDN from the Distinguished Name
5976 $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
5977
5978 if ($_.samAccountType -ne "805306368") {
5979 $MemberIsGroup = $True
5980 }
5981 else {
5982 $MemberIsGroup = $False
5983 }
5984
5985 if ($_.samAccountName) {
5986 # forest users have the samAccountName set
5987 $MemberName = $_.samAccountName
5988 }
5989 else {
5990 # external trust users have a SID, so convert it
5991 try {
5992 $MemberName = Convert-SidToName $_.cn
5993 }
5994 catch {
5995 # if there's a problem contacting the domain to resolve the SID
5996 $MemberName = $_.cn
5997 }
5998 }
5999
6000 $GPOComputerAdmin = New-Object PSObject
6001 $GPOComputerAdmin | Add-Member Noteproperty 'ComputerName' $ComputerName
6002 $GPOComputerAdmin | Add-Member Noteproperty 'OU' $OU
6003 $GPOComputerAdmin | Add-Member Noteproperty 'GPODisplayName' $GPO.GPODisplayName
6004 $GPOComputerAdmin | Add-Member Noteproperty 'GPOPath' $GPO.GPOPath
6005 $GPOComputerAdmin | Add-Member Noteproperty 'ObjectName' $MemberName
6006 $GPOComputerAdmin | Add-Member Noteproperty 'ObjectDN' $MemberDN
6007 $GPOComputerAdmin | Add-Member Noteproperty 'ObjectSID' $_.objectsid
6008 $GPOComputerAdmin | Add-Member Noteproperty 'IsGroup' $MemberIsGroup
6009 $GPOComputerAdmin
6010 }
6011 }
6012 }
6013 }
6014 }
6015 }
6016}
6017
6018
6019function Get-DomainPolicy {
6020<#
6021 .SYNOPSIS
6022
6023 Returns the default domain or DC policy for a given
6024 domain or domain controller.
6025
6026 Thanks Sean Metacalf (@pyrotek3) for the idea and guidance.
6027
6028 .PARAMETER Source
6029
6030 Extract Domain or DC (domain controller) policies.
6031
6032 .PARAMETER Domain
6033
6034 The domain to query for default policies, defaults to the current domain.
6035
6036 .PARAMETER DomainController
6037
6038 Domain controller to reflect LDAP queries through.
6039
6040 .PARAMETER ResolveSids
6041
6042 Switch. Resolve Sids from a DC policy to object names.
6043
6044 .PARAMETER UsePSDrive
6045
6046 Switch. Mount any found policy files with temporary PSDrives.
6047
6048 .EXAMPLE
6049
6050 PS C:\> Get-NetGPO
6051
6052 Returns the GPOs in the current domain.
6053#>
6054
6055 [CmdletBinding()]
6056 Param (
6057 [String]
6058 [ValidateSet("Domain","DC")]
6059 $Source ="Domain",
6060
6061 [String]
6062 $Domain,
6063
6064 [String]
6065 $DomainController,
6066
6067 [Switch]
6068 $ResolveSids,
6069
6070 [Switch]
6071 $UsePSDrive
6072 )
6073
6074 if($Source -eq "Domain") {
6075 # query the given domain for the default domain policy object
6076 $GPO = Get-NetGPO -Domain $Domain -DomainController $DomainController -GPOname "{31B2F340-016D-11D2-945F-00C04FB984F9}"
6077
6078 if($GPO) {
6079 # grab the GptTmpl.inf file and parse it
6080 $GptTmplPath = $GPO.gpcfilesyspath + "\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
6081
6082 $ParseArgs = @{
6083 'GptTmplPath' = $GptTmplPath
6084 'UsePSDrive' = $UsePSDrive
6085 }
6086
6087 # parse the GptTmpl.inf
6088 Get-GptTmpl @ParseArgs
6089 }
6090
6091 }
6092 elseif($Source -eq "DC") {
6093 # query the given domain/dc for the default domain controller policy object
6094 $GPO = Get-NetGPO -Domain $Domain -DomainController $DomainController -GPOname "{6AC1786C-016F-11D2-945F-00C04FB984F9}"
6095
6096 if($GPO) {
6097 # grab the GptTmpl.inf file and parse it
6098 $GptTmplPath = $GPO.gpcfilesyspath + "\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
6099
6100 $ParseArgs = @{
6101 'GptTmplPath' = $GptTmplPath
6102 'UsePSDrive' = $UsePSDrive
6103 }
6104
6105 # parse the GptTmpl.inf
6106 Get-GptTmpl @ParseArgs | Foreach-Object {
6107 if($ResolveSids) {
6108 # if we're resolving sids in PrivilegeRights to names
6109 $Policy = New-Object PSObject
6110 $_.psobject.properties | Foreach-Object {
6111 if( $_.Name -eq 'PrivilegeRights') {
6112
6113 $PrivilegeRights = New-Object PSObject
6114 # for every nested SID member of PrivilegeRights, try to
6115 # unpack everything and resolve the SIDs as appropriate
6116 $_.Value.psobject.properties | Foreach-Object {
6117
6118 $Sids = $_.Value | Foreach-Object {
6119 try {
6120 if($_ -isnot [System.Array]) {
6121 Convert-SidToName $_
6122 }
6123 else {
6124 $_ | Foreach-Object { Convert-SidToName $_ }
6125 }
6126 }
6127 catch {
6128 Write-Debug "Error resolving SID : $_"
6129 }
6130 }
6131
6132 $PrivilegeRights | Add-Member Noteproperty $_.Name $Sids
6133 }
6134
6135 $Policy | Add-Member Noteproperty 'PrivilegeRights' $PrivilegeRights
6136 }
6137 else {
6138 $Policy | Add-Member Noteproperty $_.Name $_.Value
6139 }
6140 }
6141 $Policy
6142 }
6143 else { $_ }
6144 }
6145 }
6146 }
6147}
6148
6149
6150
6151########################################################
6152#
6153# Functions that enumerate a single host, either through
6154# WinNT, WMI, remote registry, or API calls
6155# (with PSReflect).
6156#
6157########################################################
6158
6159function Get-NetLocalGroup {
6160<#
6161 .SYNOPSIS
6162
6163 Gets a list of all current users in a specified local group,
6164 or returns the names of all local groups with -ListGroups.
6165
6166 .PARAMETER ComputerName
6167
6168 The hostname or IP to query for local group users.
6169
6170 .PARAMETER ComputerFile
6171
6172 File of hostnames/IPs to query for local group users.
6173
6174 .PARAMETER GroupName
6175
6176 The local group name to query for users. If not given, it defaults to "Administrators"
6177
6178 .PARAMETER ListGroups
6179
6180 Switch. List all the local groups instead of their members.
6181 Old Get-NetLocalGroups functionality.
6182
6183 .PARAMETER Recurse
6184
6185 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.
6186
6187 .EXAMPLE
6188
6189 PS C:\> Get-NetLocalGroup
6190
6191 Returns the usernames that of members of localgroup "Administrators" on the local host.
6192
6193 .EXAMPLE
6194
6195 PS C:\> Get-NetLocalGroup -ComputerName WINDOWSXP
6196
6197 Returns all the local administrator accounts for WINDOWSXP
6198
6199 .EXAMPLE
6200
6201 PS C:\> Get-NetLocalGroup -ComputerName WINDOWS7 -Resurse
6202
6203 Returns all effective local/domain users/groups that can access WINDOWS7 with
6204 local administrative privileges.
6205
6206 .EXAMPLE
6207
6208 PS C:\> Get-NetLocalGroup -ComputerName WINDOWS7 -ListGroups
6209
6210 Returns all local groups on the WINDOWS7 host.
6211
6212 .LINK
6213
6214 http://stackoverflow.com/questions/21288220/get-all-local-members-and-groups-displayed-together
6215 http://msdn.microsoft.com/en-us/library/aa772211(VS.85).aspx
6216#>
6217
6218 [CmdletBinding()]
6219 param(
6220 [Parameter(ValueFromPipeline=$True)]
6221 [Alias('HostName')]
6222 [String]
6223 $ComputerName = 'localhost',
6224
6225 [ValidateScript({Test-Path -Path $_ })]
6226 [Alias('HostList')]
6227 [String]
6228 $ComputerFile,
6229
6230 [String]
6231 $GroupName = 'Administrators',
6232
6233 [Switch]
6234 $ListGroups,
6235
6236 [Switch]
6237 $Recurse
6238 )
6239
6240 begin {
6241 if ((-not $ListGroups) -and (-not $GroupName)) {
6242 # resolve the SID for the local admin group - this should usually default to "Administrators"
6243 $ObjSID = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-32-544')
6244 $Objgroup = $ObjSID.Translate( [System.Security.Principal.NTAccount])
6245 $GroupName = ($Objgroup.Value).Split('\')[1]
6246 }
6247 }
6248 process {
6249
6250 $Servers = @()
6251
6252 # if we have a host list passed, grab it
6253 if($ComputerFile) {
6254 $Servers = Get-Content -Path $ComputerFile
6255 }
6256 else {
6257 # otherwise assume a single host name
6258 $Servers += Get-NameField -Object $ComputerName
6259 }
6260
6261 # query the specified group using the WINNT provider, and
6262 # extract fields as appropriate from the results
6263 ForEach($Server in $Servers) {
6264 try {
6265 if($ListGroups) {
6266 # if we're listing the group names on a remote server
6267 $Computer = [ADSI]"WinNT://$Server,computer"
6268
6269 $Computer.psbase.children | Where-Object { $_.psbase.schemaClassName -eq 'group' } | ForEach-Object {
6270 $Group = New-Object PSObject
6271 $Group | Add-Member Noteproperty 'Server' $Server
6272 $Group | Add-Member Noteproperty 'Group' ($_.name[0])
6273 $Group | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier $_.objectsid[0],0).Value)
6274 $Group | Add-Member Noteproperty 'Description' ($_.Description[0])
6275 $Group
6276 }
6277 }
6278 else {
6279 # otherwise we're listing the group members
6280 $Members = @($([ADSI]"WinNT://$Server/$GroupName").psbase.Invoke('Members'))
6281
6282 $Members | ForEach-Object {
6283
6284 $Member = New-Object PSObject
6285 $Member | Add-Member Noteproperty 'Server' $Server
6286
6287 $AdsPath = ($_.GetType().InvokeMember('Adspath', 'GetProperty', $Null, $_, $Null)).Replace('WinNT://', '')
6288
6289 # try to translate the NT4 domain to a FQDN if possible
6290 $Name = Convert-NT4toCanonical -ObjectName $AdsPath
6291 if($Name) {
6292 $FQDN = $Name.split("/")[0]
6293 $ObjName = $AdsPath.split("/")[-1]
6294 $Name = "$FQDN/$ObjName"
6295 $IsDomain = $True
6296 }
6297 else {
6298 $Name = $AdsPath
6299 $IsDomain = $False
6300 }
6301
6302 $Member | Add-Member Noteproperty 'AccountName' $Name
6303
6304 # translate the binary sid to a string
6305 $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($_.GetType().InvokeMember('ObjectSID', 'GetProperty', $Null, $_, $Null),0)).Value)
6306
6307 # if the account is local, check if it's disabled, if it's domain, always print $False
6308 # TODO: fix this occasinal error?
6309 $Member | Add-Member Noteproperty 'Disabled' $( if(-not $IsDomain) { try { $_.GetType().InvokeMember('AccountDisabled', 'GetProperty', $Null, $_, $Null) } catch { 'ERROR' } } else { $False } )
6310
6311 # check if the member is a group
6312 $IsGroup = ($_.GetType().InvokeMember('Class', 'GetProperty', $Null, $_, $Null) -eq 'group')
6313 $Member | Add-Member Noteproperty 'IsGroup' $IsGroup
6314 $Member | Add-Member Noteproperty 'IsDomain' $IsDomain
6315 if($IsGroup) {
6316 $Member | Add-Member Noteproperty 'LastLogin' ""
6317 }
6318 else {
6319 try {
6320 $Member | Add-Member Noteproperty 'LastLogin' ( $_.GetType().InvokeMember('LastLogin', 'GetProperty', $Null, $_, $Null))
6321 }
6322 catch {
6323 $Member | Add-Member Noteproperty 'LastLogin' ""
6324 }
6325 }
6326 $Member
6327
6328 # if the result is a group domain object and we're recursing,
6329 # try to resolve all the group member results
6330 if($Recurse -and $IsDomain -and $IsGroup) {
6331
6332 $FQDN = $Name.split("/")[0]
6333 $GroupName = $Name.split("/")[1].trim()
6334
6335 Get-NetGroupMember -GroupName $GroupName -Domain $FQDN -FullData -Recurse | ForEach-Object {
6336
6337 $Member = New-Object PSObject
6338 $Member | Add-Member Noteproperty 'Server' "$FQDN/$($_.GroupName)"
6339
6340 $MemberDN = $_.distinguishedName
6341 # extract the FQDN from the Distinguished Name
6342 $MemberDomain = $MemberDN.subString($MemberDN.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
6343
6344 if ($_.samAccountType -ne "805306368") {
6345 $MemberIsGroup = $True
6346 }
6347 else {
6348 $MemberIsGroup = $False
6349 }
6350
6351 if ($_.samAccountName) {
6352 # forest users have the samAccountName set
6353 $MemberName = $_.samAccountName
6354 }
6355 else {
6356 try {
6357 # external trust users have a SID, so convert it
6358 try {
6359 $MemberName = Convert-SidToName $_.cn
6360 }
6361 catch {
6362 # if there's a problem contacting the domain to resolve the SID
6363 $MemberName = $_.cn
6364 }
6365 }
6366 catch {
6367 Write-Debug "Error resolving SID : $_"
6368 }
6369 }
6370
6371 $Member | Add-Member Noteproperty 'AccountName' "$MemberDomain/$MemberName"
6372 $Member | Add-Member Noteproperty 'SID' $_.objectsid
6373 $Member | Add-Member Noteproperty 'Disabled' $False
6374 $Member | Add-Member Noteproperty 'IsGroup' $MemberIsGroup
6375 $Member | Add-Member Noteproperty 'IsDomain' $True
6376 $Member | Add-Member Noteproperty 'LastLogin' ''
6377 $Member
6378 }
6379 }
6380 }
6381 }
6382 }
6383 catch {
6384 Write-Warning "[!] Error: $_"
6385 }
6386 }
6387 }
6388}
6389
6390
6391function Get-NetShare {
6392<#
6393 .SYNOPSIS
6394
6395 This function will execute the NetShareEnum Win32API call to query
6396 a given host for open shares. This is a replacement for
6397 "net share \\hostname"
6398
6399 .PARAMETER ComputerName
6400
6401 The hostname to query for shares. Also accepts IP addresses.
6402
6403 .OUTPUTS
6404
6405 SHARE_INFO_1 structure. A representation of the SHARE_INFO_1
6406 result structure which includes the name and note for each share.
6407
6408 .EXAMPLE
6409
6410 PS C:\> Get-NetShare
6411
6412 Returns active shares on the local host.
6413
6414 .EXAMPLE
6415
6416 PS C:\> Get-NetShare -ComputerName sqlserver
6417
6418 Returns active shares on the 'sqlserver' host
6419#>
6420
6421 [CmdletBinding()]
6422 param(
6423 [Parameter(ValueFromPipeline=$True)]
6424 [Alias('HostName')]
6425 [String]
6426 $ComputerName = 'localhost'
6427 )
6428
6429 begin {
6430 if ($PSBoundParameters['Debug']) {
6431 $DebugPreference = 'Continue'
6432 }
6433 }
6434
6435 process {
6436
6437 # process multiple host object types from the pipeline
6438 $ComputerName = Get-NameField -Object $ComputerName
6439
6440 # arguments for NetShareEnum
6441 $QueryLevel = 1
6442 $PtrInfo = [IntPtr]::Zero
6443 $EntriesRead = 0
6444 $TotalRead = 0
6445 $ResumeHandle = 0
6446
6447 # get the share information
6448 $Result = $Netapi32::NetShareEnum($ComputerName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
6449
6450 # Locate the offset of the initial intPtr
6451 $Offset = $PtrInfo.ToInt64()
6452
6453 Write-Debug "Get-NetShare result: $Result"
6454
6455 # 0 = success
6456 if (($Result -eq 0) -and ($Offset -gt 0)) {
6457
6458 # Work out how mutch to increment the pointer by finding out the size of the structure
6459 $Increment = $SHARE_INFO_1::GetSize()
6460
6461 # parse all the result structures
6462 for ($i = 0; ($i -lt $EntriesRead); $i++) {
6463 # create a new int ptr at the given offset and cast
6464 # the pointer as our result structure
6465 $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
6466 $Info = $NewIntPtr -as $SHARE_INFO_1
6467 # return all the sections of the structure
6468 $Info | Select-Object *
6469 $Offset = $NewIntPtr.ToInt64()
6470 $Offset += $Increment
6471 }
6472
6473 # free up the result buffer
6474 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
6475 }
6476 else
6477 {
6478 switch ($Result) {
6479 (5) {Write-Debug 'The user does not have access to the requested information.'}
6480 (124) {Write-Debug 'The value specified for the level parameter is not valid.'}
6481 (87) {Write-Debug 'The specified parameter is not valid.'}
6482 (234) {Write-Debug 'More entries are available. Specify a large enough buffer to receive all entries.'}
6483 (8) {Write-Debug 'Insufficient memory is available.'}
6484 (2312) {Write-Debug 'A session does not exist with the computer name.'}
6485 (2351) {Write-Debug 'The computer name is not valid.'}
6486 (2221) {Write-Debug 'Username not found.'}
6487 (53) {Write-Debug 'Hostname could not be found'}
6488 }
6489 }
6490 }
6491}
6492
6493
6494function Get-NetLoggedon {
6495<#
6496 .SYNOPSIS
6497
6498 This function will execute the NetWkstaUserEnum Win32API call to query
6499 a given host for actively logged on users.
6500
6501 .PARAMETER ComputerName
6502
6503 The hostname to query for logged on users.
6504
6505 .OUTPUTS
6506
6507 WKSTA_USER_INFO_1 structure. A representation of the WKSTA_USER_INFO_1
6508 result structure which includes the username and domain of logged on users.
6509
6510 .EXAMPLE
6511
6512 PS C:\> Get-NetLoggedon
6513
6514 Returns users actively logged onto the local host.
6515
6516 .EXAMPLE
6517
6518 PS C:\> Get-NetLoggedon -ComputerName sqlserver
6519
6520 Returns users actively logged onto the 'sqlserver' host.
6521
6522 .LINK
6523
6524 http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
6525#>
6526
6527 [CmdletBinding()]
6528 param(
6529 [Parameter(ValueFromPipeline=$True)]
6530 [Alias('HostName')]
6531 [String]
6532 $ComputerName = 'localhost'
6533 )
6534
6535 begin {
6536 if ($PSBoundParameters['Debug']) {
6537 $DebugPreference = 'Continue'
6538 }
6539 }
6540
6541 process {
6542
6543 # process multiple host object types from the pipeline
6544 $ComputerName = Get-NameField -Object $ComputerName
6545
6546 # Declare the reference variables
6547 $QueryLevel = 1
6548 $PtrInfo = [IntPtr]::Zero
6549 $EntriesRead = 0
6550 $TotalRead = 0
6551 $ResumeHandle = 0
6552
6553 # get logged on user information
6554 $Result = $Netapi32::NetWkstaUserEnum($ComputerName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
6555
6556 # Locate the offset of the initial intPtr
6557 $Offset = $PtrInfo.ToInt64()
6558
6559 Write-Debug "Get-NetLoggedon result: $Result"
6560
6561 # 0 = success
6562 if (($Result -eq 0) -and ($Offset -gt 0)) {
6563
6564 # Work out how mutch to increment the pointer by finding out the size of the structure
6565 $Increment = $WKSTA_USER_INFO_1::GetSize()
6566
6567 # parse all the result structures
6568 for ($i = 0; ($i -lt $EntriesRead); $i++) {
6569 # create a new int ptr at the given offset and cast
6570 # the pointer as our result structure
6571 $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
6572 $Info = $NewIntPtr -as $WKSTA_USER_INFO_1
6573
6574 # return all the sections of the structure
6575 $Info | Select-Object *
6576 $Offset = $NewIntPtr.ToInt64()
6577 $Offset += $Increment
6578
6579 }
6580
6581 # free up the result buffer
6582 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
6583 }
6584 else
6585 {
6586 switch ($Result) {
6587 (5) {Write-Debug 'The user does not have access to the requested information.'}
6588 (124) {Write-Debug 'The value specified for the level parameter is not valid.'}
6589 (87) {Write-Debug 'The specified parameter is not valid.'}
6590 (234) {Write-Debug 'More entries are available. Specify a large enough buffer to receive all entries.'}
6591 (8) {Write-Debug 'Insufficient memory is available.'}
6592 (2312) {Write-Debug 'A session does not exist with the computer name.'}
6593 (2351) {Write-Debug 'The computer name is not valid.'}
6594 (2221) {Write-Debug 'Username not found.'}
6595 (53) {Write-Debug 'Hostname could not be found'}
6596 }
6597 }
6598 }
6599}
6600
6601
6602function Get-NetSession {
6603<#
6604 .SYNOPSIS
6605
6606 This function will execute the NetSessionEnum Win32API call to query
6607 a given host for active sessions on the host.
6608 Heavily adapted from dunedinite's post on stackoverflow (see LINK below)
6609
6610 .PARAMETER ComputerName
6611
6612 The ComputerName to query for active sessions.
6613
6614 .PARAMETER UserName
6615
6616 The user name to filter for active sessions.
6617
6618 .OUTPUTS
6619
6620 SESSION_INFO_10 structure. A representation of the SESSION_INFO_10
6621 result structure which includes the host and username associated
6622 with active sessions.
6623
6624 .EXAMPLE
6625
6626 PS C:\> Get-NetSession
6627
6628 Returns active sessions on the local host.
6629
6630 .EXAMPLE
6631
6632 PS C:\> Get-NetSession -ComputerName sqlserver
6633
6634 Returns active sessions on the 'sqlserver' host.
6635
6636 .LINK
6637
6638 http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
6639#>
6640
6641 [CmdletBinding()]
6642 param(
6643 [Parameter(ValueFromPipeline=$True)]
6644 [Alias('HostName')]
6645 [String]
6646 $ComputerName = 'localhost',
6647
6648 [String]
6649 $UserName = ''
6650 )
6651
6652 begin {
6653 if ($PSBoundParameters['Debug']) {
6654 $DebugPreference = 'Continue'
6655 }
6656 }
6657
6658 process {
6659
6660 # process multiple host object types from the pipeline
6661 $ComputerName = Get-NameField -Object $ComputerName
6662
6663 # arguments for NetSessionEnum
6664 $QueryLevel = 10
6665 $PtrInfo = [IntPtr]::Zero
6666 $EntriesRead = 0
6667 $TotalRead = 0
6668 $ResumeHandle = 0
6669
6670 # get session information
6671 $Result = $Netapi32::NetSessionEnum($ComputerName, '', $UserName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
6672
6673 # Locate the offset of the initial intPtr
6674 $Offset = $PtrInfo.ToInt64()
6675
6676 Write-Debug "Get-NetSession result: $Result"
6677
6678 # 0 = success
6679 if (($Result -eq 0) -and ($Offset -gt 0)) {
6680
6681 # Work out how mutch to increment the pointer by finding out the size of the structure
6682 $Increment = $SESSION_INFO_10::GetSize()
6683
6684 # parse all the result structures
6685 for ($i = 0; ($i -lt $EntriesRead); $i++) {
6686 # create a new int ptr at the given offset and cast
6687 # the pointer as our result structure
6688 $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
6689 $Info = $NewIntPtr -as $SESSION_INFO_10
6690
6691 # return all the sections of the structure
6692 $Info | Select-Object *
6693 $Offset = $NewIntPtr.ToInt64()
6694 $Offset += $Increment
6695
6696 }
6697 # free up the result buffer
6698 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
6699 }
6700 else
6701 {
6702 switch ($Result) {
6703 (5) {Write-Debug 'The user does not have access to the requested information.'}
6704 (124) {Write-Debug 'The value specified for the level parameter is not valid.'}
6705 (87) {Write-Debug 'The specified parameter is not valid.'}
6706 (234) {Write-Debug 'More entries are available. Specify a large enough buffer to receive all entries.'}
6707 (8) {Write-Debug 'Insufficient memory is available.'}
6708 (2312) {Write-Debug 'A session does not exist with the computer name.'}
6709 (2351) {Write-Debug 'The computer name is not valid.'}
6710 (2221) {Write-Debug 'Username not found.'}
6711 (53) {Write-Debug 'Hostname could not be found'}
6712 }
6713 }
6714 }
6715}
6716
6717
6718function Get-NetRDPSession {
6719<#
6720 .SYNOPSIS
6721
6722 This function will execute the WTSEnumerateSessionsEx and
6723 WTSQuerySessionInformation Win32API calls to query a given
6724 RDP remote service for active sessions and originating IPs.
6725 This is a replacement for qwinsta.
6726
6727 Note: only members of the Administrators or Account Operators local group
6728 can successfully execute this functionality on a remote target.
6729
6730 .PARAMETER ComputerName
6731
6732 The hostname to query for active RDP sessions.
6733
6734 .EXAMPLE
6735
6736 PS C:\> Get-NetRDPSession
6737
6738 Returns active RDP/terminal sessions on the local host.
6739
6740 .EXAMPLE
6741
6742 PS C:\> Get-NetRDPSession -ComputerName "sqlserver"
6743
6744 Returns active RDP/terminal sessions on the 'sqlserver' host.
6745#>
6746
6747 [CmdletBinding()]
6748 param(
6749 [Parameter(ValueFromPipeline=$True)]
6750 [Alias('HostName')]
6751 [String]
6752 $ComputerName = 'localhost'
6753 )
6754
6755 begin {
6756 if ($PSBoundParameters['Debug']) {
6757 $DebugPreference = 'Continue'
6758 }
6759 }
6760
6761 process {
6762
6763 # process multiple host object types from the pipeline
6764 $ComputerName = Get-NameField -Object $ComputerName
6765
6766 # open up a handle to the Remote Desktop Session host
6767 $Handle = $Wtsapi32::WTSOpenServerEx($ComputerName)
6768
6769 # if we get a non-zero handle back, everything was successful
6770 if ($Handle -ne 0) {
6771
6772 Write-Debug "WTSOpenServerEx handle: $Handle"
6773
6774 # arguments for WTSEnumerateSessionsEx
6775 $ppSessionInfo = [IntPtr]::Zero
6776 $pCount = 0
6777
6778 # get information on all current sessions
6779 $Result = $Wtsapi32::WTSEnumerateSessionsEx($Handle, [ref]1, 0, [ref]$ppSessionInfo, [ref]$pCount)
6780
6781 # Locate the offset of the initial intPtr
6782 $Offset = $ppSessionInfo.ToInt64()
6783
6784 Write-Debug "WTSEnumerateSessionsEx result: $Result"
6785 Write-Debug "pCount: $pCount"
6786
6787 if (($Result -ne 0) -and ($Offset -gt 0)) {
6788
6789 # Work out how mutch to increment the pointer by finding out the size of the structure
6790 $Increment = $WTS_SESSION_INFO_1::GetSize()
6791
6792 # parse all the result structures
6793 for ($i = 0; ($i -lt $pCount); $i++) {
6794
6795 # create a new int ptr at the given offset and cast
6796 # the pointer as our result structure
6797 $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
6798 $Info = $NewIntPtr -as $WTS_SESSION_INFO_1
6799
6800 $RDPSession = New-Object PSObject
6801
6802 if ($Info.pHostName) {
6803 $RDPSession | Add-Member Noteproperty 'ComputerName' $Info.pHostName
6804 }
6805 else {
6806 # if no hostname returned, use the specified hostname
6807 $RDPSession | Add-Member Noteproperty 'ComputerName' $ComputerName
6808 }
6809
6810 $RDPSession | Add-Member Noteproperty 'SessionName' $Info.pSessionName
6811
6812 if ($(-not $Info.pDomainName) -or ($Info.pDomainName -eq '')) {
6813 # if a domain isn't returned just use the username
6814 $RDPSession | Add-Member Noteproperty 'UserName' "$($Info.pUserName)"
6815 }
6816 else {
6817 $RDPSession | Add-Member Noteproperty 'UserName' "$($Info.pDomainName)\$($Info.pUserName)"
6818 }
6819
6820 $RDPSession | Add-Member Noteproperty 'ID' $Info.SessionID
6821 $RDPSession | Add-Member Noteproperty 'State' $Info.State
6822
6823 $ppBuffer = [IntPtr]::Zero
6824 $pBytesReturned = 0
6825
6826 # query for the source client IP with WTSQuerySessionInformation
6827 # https://msdn.microsoft.com/en-us/library/aa383861(v=vs.85).aspx
6828 $Result2 = $Wtsapi32::WTSQuerySessionInformation($Handle, $Info.SessionID, 14, [ref]$ppBuffer, [ref]$pBytesReturned)
6829
6830 $Offset2 = $ppBuffer.ToInt64()
6831 $NewIntPtr2 = New-Object System.Intptr -ArgumentList $Offset2
6832 $Info2 = $NewIntPtr2 -as $WTS_CLIENT_ADDRESS
6833
6834 $SourceIP = $Info2.Address
6835 if($SourceIP[2] -ne 0) {
6836 $SourceIP = [String]$SourceIP[2]+"."+[String]$SourceIP[3]+"."+[String]$SourceIP[4]+"."+[String]$SourceIP[5]
6837 }
6838 else {
6839 $SourceIP = $Null
6840 }
6841
6842 $RDPSession | Add-Member Noteproperty 'SourceIP' $SourceIP
6843 $RDPSession
6844
6845 # free up the memory buffer
6846 $Null = $Wtsapi32::WTSFreeMemory($ppBuffer)
6847
6848 $Offset += $Increment
6849 }
6850 # free up the memory result buffer
6851 $Null = $Wtsapi32::WTSFreeMemoryEx(2, $ppSessionInfo, $pCount)
6852 }
6853 # Close off the service handle
6854 $Null = $Wtsapi32::WTSCloseServer($Handle)
6855 }
6856 else {
6857 # otherwise it failed - get the last error
6858 # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx
6859 $Err = $Kernel32::GetLastError()
6860 Write-Verbuse "LastError: $Err"
6861 }
6862 }
6863}
6864
6865
6866function Invoke-CheckLocalAdminAccess {
6867<#
6868 .SYNOPSIS
6869
6870 This function will use the OpenSCManagerW Win32API call to to establish
6871 a handle to the remote host. If this succeeds, the current user context
6872 has local administrator acess to the target.
6873
6874 Idea stolen from the local_admin_search_enum post module in Metasploit written by:
6875 'Brandon McCann "zeknox" <bmccann[at]accuvant.com>'
6876 'Thomas McCarthy "smilingraccoon" <smilingraccoon[at]gmail.com>'
6877 'Royce Davis "r3dy" <rdavis[at]accuvant.com>'
6878
6879 .PARAMETER ComputerName
6880
6881 The hostname to query for active sessions.
6882
6883 .OUTPUTS
6884
6885 $True if the current user has local admin access to the hostname, $False otherwise
6886
6887 .EXAMPLE
6888
6889 PS C:\> Invoke-CheckLocalAdminAccess -ComputerName sqlserver
6890
6891 Returns active sessions on the local host.
6892
6893 .LINK
6894
6895 https://github.com/rapid7/metasploit-framework/blob/master/modules/post/windows/gather/local_admin_search_enum.rb
6896 http://www.powershellmagazine.com/2014/09/25/easily-defining-enums-structs-and-win32-functions-in-memory/
6897#>
6898
6899 [CmdletBinding()]
6900 param(
6901 [Parameter(ValueFromPipeline=$True)]
6902 [String]
6903 [Alias('HostName')]
6904 $ComputerName = 'localhost'
6905 )
6906
6907 begin {
6908 if ($PSBoundParameters['Debug']) {
6909 $DebugPreference = 'Continue'
6910 }
6911 }
6912
6913 process {
6914
6915 # process multiple host object types from the pipeline
6916 $ComputerName = Get-NameField -Object $ComputerName
6917
6918 # 0xF003F - SC_MANAGER_ALL_ACCESS
6919 # http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx
6920 $Handle = $Advapi32::OpenSCManagerW("\\$ComputerName", 'ServicesActive', 0xF003F)
6921
6922 Write-Debug "Invoke-CheckLocalAdminAccess handle: $Handle"
6923
6924 # if we get a non-zero handle back, everything was successful
6925 if ($Handle -ne 0) {
6926 # Close off the service handle
6927 $Null = $Advapi32::CloseServiceHandle($Handle)
6928 $True
6929 }
6930 else {
6931 # otherwise it failed - get the last error
6932 # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx
6933 $Err = $Kernel32::GetLastError()
6934 Write-Debug "Invoke-CheckLocalAdminAccess LastError: $Err"
6935 $False
6936 }
6937 }
6938}
6939
6940
6941function Get-LastLoggedOn {
6942<#
6943 .SYNOPSIS
6944
6945 This function uses remote registry functionality to return
6946 the last user logged onto a target machine.
6947
6948 Note: This function requires administrative rights on the
6949 machine you're enumerating.
6950
6951 .PARAMETER ComputerName
6952
6953 The hostname to query for the last logged on user.
6954 Defaults to the localhost.
6955
6956 .EXAMPLE
6957
6958 PS C:\> Get-LastLoggedOn
6959
6960 Returns the last user logged onto the local machine.
6961
6962 .EXAMPLE
6963
6964 PS C:\> Get-LastLoggedOn -ComputerName WINDOWS1
6965
6966 Returns the last user logged onto WINDOWS1
6967#>
6968
6969 [CmdletBinding()]
6970 param(
6971 [Parameter(ValueFromPipeline=$True)]
6972 [String]
6973 [Alias('HostName')]
6974 $ComputerName = "."
6975 )
6976
6977 process {
6978
6979 # process multiple host object types from the pipeline
6980 $ComputerName = Get-NameField -Object $ComputerName
6981
6982 # try to open up the remote registry key to grab the last logged on user
6983 try {
6984 $Reg = [WMIClass]"\\$ComputerName\root\default:stdRegProv"
6985 $HKLM = 2147483650
6986 $Key = "SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI"
6987 $Value = "LastLoggedOnUser"
6988 $Reg.GetStringValue($HKLM, $Key, $Value).sValue
6989 }
6990 catch {
6991 Write-Warning "[!] Error opening remote registry on $ComputerName. Remote registry likely not enabled."
6992 $Null
6993 }
6994 }
6995}
6996
6997
6998function Get-CachedRDPConnection {
6999<#
7000 .SYNOPSIS
7001
7002 Uses remote registry functionality to query all entries for the
7003 "Windows Remote Desktop Connection Client" on a machine, separated by
7004 user and target server.
7005
7006 Note: This function requires administrative rights on the
7007 machine you're enumerating.
7008
7009 .PARAMETER ComputerName
7010
7011 The hostname to query for RDP client information.
7012 Defaults to localhost.
7013
7014 .PARAMETER RemoteUserName
7015
7016 The "domain\username" to use for the WMI call on the remote system.
7017 If supplied, 'RemotePassword' must be supplied as well.
7018
7019 .PARAMETER RemotePassword
7020
7021 The password to use for the WMI call on a remote system.
7022
7023 .EXAMPLE
7024
7025 PS C:\> Get-CachedRDPConnection
7026
7027 Returns the RDP connection client information for the local machine.
7028
7029 .EXAMPLE
7030
7031 PS C:\> Get-CachedRDPConnection -ComputerName WINDOWS2.testlab.local
7032
7033 Returns the RDP connection client information for the WINDOWS2.testlab.local machine
7034
7035 .EXAMPLE
7036
7037 PS C:\> Get-CachedRDPConnection -ComputerName WINDOWS2.testlab.local -RemoteUserName DOMAIN\user -RemotePassword Password123!
7038
7039 Returns the RDP connection client information for the WINDOWS2.testlab.local machine using alternate credentials.
7040#>
7041
7042 [CmdletBinding()]
7043 param(
7044 [Parameter(ValueFromPipeline=$True)]
7045 [String]
7046 $ComputerName = "localhost",
7047
7048 [String]
7049 $RemoteUserName,
7050
7051 [String]
7052 $RemotePassword
7053 )
7054
7055 begin {
7056 if ($RemoteUserName -and $RemotePassword) {
7057 $Password = $RemotePassword | ConvertTo-SecureString -AsPlainText -Force
7058 $Credential = New-Object System.Management.Automation.PSCredential($RemoteUserName,$Password)
7059 }
7060
7061 # HKEY_USERS
7062 $HKU = 2147483651
7063 }
7064
7065 process {
7066
7067 try {
7068 if($Credential) {
7069 $Reg = Get-Wmiobject -List 'StdRegProv' -Namespace root\default -Computername $ComputerName -Credential $Credential -ErrorAction SilentlyContinue
7070 }
7071 else {
7072 $Reg = Get-Wmiobject -List 'StdRegProv' -Namespace root\default -Computername $ComputerName -ErrorAction SilentlyContinue
7073 }
7074 }
7075 catch {
7076 Write-Warning "Error accessing $ComputerName, likely insufficient permissions or firewall rules on host"
7077 }
7078
7079 if(!$Reg) {
7080 Write-Warning "Error accessing $ComputerName, likely insufficient permissions or firewall rules on host"
7081 }
7082 else {
7083 # extract out the SIDs of domain users in this hive
7084 $UserSIDs = ($Reg.EnumKey($HKU, "")).sNames | ? { $_ -match 'S-1-5-21-[0-9]+-[0-9]+-[0-9]+-[0-9]+$' }
7085
7086 foreach ($UserSID in $UserSIDs) {
7087
7088 try {
7089 $UserName = Convert-SidToName $UserSID
7090
7091 # pull out all the cached RDP connections
7092 $ConnectionKeys = $Reg.EnumValues($HKU,"$UserSID\Software\Microsoft\Terminal Server Client\Default").sNames
7093
7094 foreach ($Connection in $ConnectionKeys) {
7095 # make sure this key is a cached connection
7096 if($Connection -match 'MRU.*') {
7097 $TargetServer = $Reg.GetStringValue($HKU, "$UserSID\Software\Microsoft\Terminal Server Client\Default", $Connection).sValue
7098
7099 $FoundConnection = New-Object PSObject
7100 $FoundConnection | Add-Member Noteproperty 'ComputerName' $ComputerName
7101 $FoundConnection | Add-Member Noteproperty 'UserName' $UserName
7102 $FoundConnection | Add-Member Noteproperty 'UserSID' $UserSID
7103 $FoundConnection | Add-Member Noteproperty 'TargetServer' $TargetServer
7104 $FoundConnection | Add-Member Noteproperty 'UsernameHint' $Null
7105 $FoundConnection
7106 }
7107 }
7108
7109 # pull out all the cached server info with username hints
7110 $ServerKeys = $Reg.EnumKey($HKU,"$UserSID\Software\Microsoft\Terminal Server Client\Servers").sNames
7111
7112 foreach ($Server in $ServerKeys) {
7113
7114 $UsernameHint = $Reg.GetStringValue($HKU, "$UserSID\Software\Microsoft\Terminal Server Client\Servers\$Server", 'UsernameHint').sValue
7115
7116 $FoundConnection = New-Object PSObject
7117 $FoundConnection | Add-Member Noteproperty 'ComputerName' $ComputerName
7118 $FoundConnection | Add-Member Noteproperty 'UserName' $UserName
7119 $FoundConnection | Add-Member Noteproperty 'UserSID' $UserSID
7120 $FoundConnection | Add-Member Noteproperty 'TargetServer' $Server
7121 $FoundConnection | Add-Member Noteproperty 'UsernameHint' $UsernameHint
7122 $FoundConnection
7123 }
7124
7125 }
7126 catch {
7127 Write-Debug "Error: $_"
7128 }
7129 }
7130 }
7131 }
7132}
7133
7134
7135function Get-NetProcess {
7136<#
7137 .SYNOPSIS
7138
7139 Gets a list of processes/owners on a remote machine.
7140
7141 .PARAMETER ComputerName
7142
7143 The hostname to query processes. Defaults to the local host name.
7144
7145 .PARAMETER RemoteUserName
7146
7147 The "domain\username" to use for the WMI call on a remote system.
7148 If supplied, 'RemotePassword' must be supplied as well.
7149
7150 .PARAMETER RemotePassword
7151
7152 The password to use for the WMI call on a remote system.
7153
7154 .EXAMPLE
7155
7156 PS C:\> Get-NetProcess -ComputerName WINDOWS1
7157
7158 Returns the current processes for WINDOWS1
7159#>
7160
7161 [CmdletBinding()]
7162 param(
7163 [Parameter(ValueFromPipeline=$True)]
7164 [String]
7165 $ComputerName,
7166
7167 [String]
7168 $RemoteUserName,
7169
7170 [String]
7171 $RemotePassword
7172 )
7173
7174 process {
7175
7176 if($ComputerName) {
7177 # process multiple host object types from the pipeline
7178 $ComputerName = Get-NameField -Object $ComputerName
7179 }
7180 else {
7181 # default to the local hostname
7182 $ComputerName = [System.Net.Dns]::GetHostName()
7183 }
7184
7185 $Credential = $Null
7186
7187 if($RemoteUserName) {
7188 if($RemotePassword) {
7189 $Password = $RemotePassword | ConvertTo-SecureString -AsPlainText -Force
7190 $Credential = New-Object System.Management.Automation.PSCredential($RemoteUserName,$Password)
7191
7192 # try to enumerate the processes on the remote machine using the supplied credential
7193 try {
7194 Get-WMIobject -Class Win32_process -ComputerName $ComputerName -Credential $Credential | ForEach-Object {
7195 $Owner = $_.getowner();
7196 $Process = New-Object PSObject
7197 $Process | Add-Member Noteproperty 'ComputerName' $ComputerName
7198 $Process | Add-Member Noteproperty 'ProcessName' $_.ProcessName
7199 $Process | Add-Member Noteproperty 'ProcessID' $_.ProcessID
7200 $Process | Add-Member Noteproperty 'Domain' $Owner.Domain
7201 $Process | Add-Member Noteproperty 'User' $Owner.User
7202 $Process
7203 }
7204 }
7205 catch {
7206 Write-Verbose "[!] Error enumerating remote processes, access likely denied: $_"
7207 }
7208 }
7209 else {
7210 Write-Warning "[!] RemotePassword must also be supplied!"
7211 }
7212 }
7213 else {
7214 # try to enumerate the processes on the remote machine
7215 try {
7216 Get-WMIobject -Class Win32_process -ComputerName $ComputerName | ForEach-Object {
7217 $Owner = $_.getowner();
7218 $Process = New-Object PSObject
7219 $Process | Add-Member Noteproperty 'ComputerName' $ComputerName
7220 $Process | Add-Member Noteproperty 'ProcessName' $_.ProcessName
7221 $Process | Add-Member Noteproperty 'ProcessID' $_.ProcessID
7222 $Process | Add-Member Noteproperty 'Domain' $Owner.Domain
7223 $Process | Add-Member Noteproperty 'User' $Owner.User
7224 $Process
7225 }
7226 }
7227 catch {
7228 Write-Verbose "[!] Error enumerating remote processes, access likely denied: $_"
7229 }
7230 }
7231 }
7232}
7233
7234
7235function Find-InterestingFile {
7236<#
7237 .SYNOPSIS
7238
7239 This function recursively searches a given UNC path for files with
7240 specific keywords in the name (default of pass, sensitive, secret, admin,
7241 login and unattend*.xml). The output can be piped out to a csv with the
7242 -OutFile flag. By default, hidden files/folders are included in search results.
7243
7244 .PARAMETER Path
7245
7246 UNC/local path to recursively search.
7247
7248 .PARAMETER Terms
7249
7250 Terms to search for.
7251
7252 .PARAMETER OfficeDocs
7253
7254 Switch. Search for office documents (*.doc*, *.xls*, *.ppt*)
7255
7256 .PARAMETER FreshEXEs
7257
7258 Switch. Find .EXEs accessed within the last week.
7259
7260 .PARAMETER LastAccessTime
7261
7262 Only return files with a LastAccessTime greater than this date value.
7263
7264 .PARAMETER LastWriteTime
7265
7266 Only return files with a LastWriteTime greater than this date value.
7267
7268 .PARAMETER CreationTime
7269
7270 Only return files with a CreationTime greater than this date value.
7271
7272 .PARAMETER ExcludeFolders
7273
7274 Switch. Exclude folders from the search results.
7275
7276 .PARAMETER ExcludeHidden
7277
7278 Switch. Exclude hidden files and folders from the search results.
7279
7280 .PARAMETER CheckWriteAccess
7281
7282 Switch. Only returns files the current user has write access to.
7283
7284 .PARAMETER OutFile
7285
7286 Output results to a specified csv output file.
7287
7288 .PARAMETER UsePSDrive
7289
7290 Switch. Mount target remote path with temporary PSDrives.
7291
7292 .PARAMETER Credential
7293
7294 Credential to use to mount the PSDrive for searching.
7295
7296 .OUTPUTS
7297
7298 The full path, owner, lastaccess time, lastwrite time, and size for each found file.
7299
7300 .EXAMPLE
7301
7302 PS C:\> Find-InterestingFile -Path C:\Backup\
7303
7304 Returns any files on the local path C:\Backup\ that have the default
7305 search term set in the title.
7306
7307 .EXAMPLE
7308
7309 PS C:\> Find-InterestingFile -Path \\WINDOWS7\Users\ -Terms salaries,email -OutFile out.csv
7310
7311 Returns any files on the remote path \\WINDOWS7\Users\ that have 'salaries'
7312 or 'email' in the title, and writes the results out to a csv file
7313 named 'out.csv'
7314
7315 .EXAMPLE
7316
7317 PS C:\> Find-InterestingFile -Path \\WINDOWS7\Users\ -LastAccessTime (Get-Date).AddDays(-7)
7318
7319 Returns any files on the remote path \\WINDOWS7\Users\ that have the default
7320 search term set in the title and were accessed within the last week.
7321
7322 .LINK
7323
7324 http://www.harmj0y.net/blog/redteaming/file-server-triage-on-red-team-engagements/
7325#>
7326
7327 [CmdletBinding()]
7328 param(
7329 [Parameter(ValueFromPipeline=$True)]
7330 [String]
7331 $Path = '.\',
7332
7333 [String[]]
7334 $Terms,
7335
7336 [Switch]
7337 $OfficeDocs,
7338
7339 [Switch]
7340 $FreshEXEs,
7341
7342 [String]
7343 $LastAccessTime,
7344
7345 [String]
7346 $LastWriteTime,
7347
7348 [String]
7349 $CreationTime,
7350
7351 [Switch]
7352 $ExcludeFolders,
7353
7354 [Switch]
7355 $ExcludeHidden,
7356
7357 [Switch]
7358 $CheckWriteAccess,
7359
7360 [String]
7361 $OutFile,
7362
7363 [Switch]
7364 $UsePSDrive,
7365
7366 [System.Management.Automation.PSCredential]
7367 $Credential = [System.Management.Automation.PSCredential]::Empty
7368 )
7369
7370 begin {
7371 # default search terms
7372 $SearchTerms = @('pass', 'sensitive', 'admin', 'login', 'secret', 'unattend*.xml', '.vmdk', 'creds', 'credential', '.config')
7373
7374 if(!$Path.EndsWith('\')) {
7375 $Path = $Path + '\'
7376 }
7377 if($Credential -ne [System.Management.Automation.PSCredential]::Empty) { $UsePSDrive = $True }
7378
7379 # check if custom search terms were passed
7380 if ($Terms) {
7381 if($Terms -isnot [system.array]) {
7382 $Terms = @($Terms)
7383 }
7384 $SearchTerms = $Terms
7385 }
7386
7387 if(-not $SearchTerms[0].startswith("*")) {
7388 # append wildcards to the front and back of all search terms
7389 for ($i = 0; $i -lt $SearchTerms.Count; $i++) {
7390 $SearchTerms[$i] = "*$($SearchTerms[$i])*"
7391 }
7392 }
7393
7394 # search just for office documents if specified
7395 if ($OfficeDocs) {
7396 $SearchTerms = @('*.doc', '*.docx', '*.xls', '*.xlsx', '*.ppt', '*.pptx')
7397 }
7398
7399 # find .exe's accessed within the last 7 days
7400 if($FreshEXEs) {
7401 # get an access time limit of 7 days ago
7402 $LastAccessTime = (get-date).AddDays(-7).ToString('MM/dd/yyyy')
7403 $SearchTerms = '*.exe'
7404 }
7405
7406 if($UsePSDrive) {
7407 # if we're PSDrives, create a temporary mount point
7408 $Parts = $Path.split('\')
7409 $FolderPath = $Parts[0..($Parts.length-2)] -join '\'
7410 $FilePath = $Parts[-1]
7411 $RandDrive = ("abcdefghijklmnopqrstuvwxyz".ToCharArray() | Get-Random -Count 7) -join ''
7412
7413 Write-Verbose "Mounting path $Path using a temp PSDrive at $RandDrive"
7414
7415 try {
7416 $Null = New-PSDrive -Name $RandDrive -Credential $Credential -PSProvider FileSystem -Root $FolderPath -ErrorAction Stop
7417 }
7418 catch {
7419 Write-Debug "Error mounting path $Path : $_"
7420 return $Null
7421 }
7422
7423 # so we can cd/dir the new drive
7424 $Path = $RandDrive + ":\" + $FilePath
7425 }
7426 }
7427
7428 process {
7429
7430 Write-Verbose "[*] Search path $Path"
7431
7432 function Invoke-CheckWrite {
7433 # short helper to check is the current user can write to a file
7434 [CmdletBinding()]param([String]$Path)
7435 try {
7436 $Filetest = [IO.FILE]::OpenWrite($Path)
7437 $Filetest.Close()
7438 $True
7439 }
7440 catch {
7441 Write-Verbose -Message $Error[0]
7442 $False
7443 }
7444 }
7445
7446 $SearchArgs = @{
7447 'Path' = $Path
7448 'Recurse' = $True
7449 'Force' = $(-not $ExcludeHidden)
7450 'Include' = $SearchTerms
7451 'ErrorAction' = 'SilentlyContinue'
7452 }
7453
7454 Get-ChildItem @SearchArgs | ForEach-Object {
7455 Write-Verbose $_
7456 # check if we're excluding folders
7457 if(!$ExcludeFolders -or !$_.PSIsContainer) {$_}
7458 } | ForEach-Object {
7459 if($LastAccessTime -or $LastWriteTime -or $CreationTime) {
7460 if($LastAccessTime -and ($_.LastAccessTime -gt $LastAccessTime)) {$_}
7461 elseif($LastWriteTime -and ($_.LastWriteTime -gt $LastWriteTime)) {$_}
7462 elseif($CreationTime -and ($_.CreationTime -gt $CreationTime)) {$_}
7463 }
7464 else {$_}
7465 } | ForEach-Object {
7466 # filter for write access (if applicable)
7467 if((-not $CheckWriteAccess) -or (Invoke-CheckWrite -Path $_.FullName)) {$_}
7468 } | Select-Object FullName,@{Name='Owner';Expression={(Get-Acl $_.FullName).Owner}},LastAccessTime,LastWriteTime,CreationTime,Length | ForEach-Object {
7469 # check if we're outputting to the pipeline or an output file
7470 if($OutFile) {Export-PowerViewCSV -InputObject $_ -OutFile $OutFile}
7471 else {$_}
7472 }
7473 }
7474
7475 end {
7476 if($UsePSDrive -and $RandDrive) {
7477 Write-Verbose "Removing temp PSDrive $RandDrive"
7478 Get-PSDrive -Name $RandDrive -ErrorAction SilentlyContinue | Remove-PSDrive
7479 }
7480 }
7481}
7482
7483
7484########################################################
7485#
7486# 'Meta'-functions start below
7487#
7488########################################################
7489
7490function Invoke-ThreadedFunction {
7491 # Helper used by any threaded host enumeration functions
7492 [CmdletBinding()]
7493 param(
7494 [Parameter(Position=0,Mandatory=$True)]
7495 [String[]]
7496 $ComputerName,
7497
7498 [Parameter(Position=1,Mandatory=$True)]
7499 [System.Management.Automation.ScriptBlock]
7500 $ScriptBlock,
7501
7502 [Parameter(Position=2)]
7503 [Hashtable]
7504 $ScriptParameters,
7505
7506 [Int]
7507 $Threads = 20,
7508
7509 [Switch]
7510 $NoImports
7511 )
7512
7513 begin {
7514
7515 if ($PSBoundParameters['Debug']) {
7516 $DebugPreference = 'Continue'
7517 }
7518
7519 Write-Verbose "[*] Total number of hosts: $($ComputerName.count)"
7520
7521 # Adapted from:
7522 # http://powershell.org/wp/forums/topic/invpke-parallel-need-help-to-clone-the-current-runspace/
7523 $SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
7524 $SessionState.ApartmentState = [System.Threading.Thread]::CurrentThread.GetApartmentState()
7525
7526 # import the current session state's variables and functions so the chained PowerView
7527 # functionality can be used by the threaded blocks
7528 if(!$NoImports) {
7529
7530 # grab all the current variables for this runspace
7531 $MyVars = Get-Variable -Scope 2
7532
7533 # these Variables are added by Runspace.Open() Method and produce Stop errors if you add them twice
7534 $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")
7535
7536 # Add Variables from Parent Scope (current runspace) into the InitialSessionState
7537 ForEach($Var in $MyVars) {
7538 if($VorbiddenVars -NotContains $Var.Name) {
7539 $SessionState.Variables.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Var.name,$Var.Value,$Var.description,$Var.options,$Var.attributes))
7540 }
7541 }
7542
7543 # Add Functions from current runspace to the InitialSessionState
7544 ForEach($Function in (Get-ChildItem Function:)) {
7545 $SessionState.Commands.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $Function.Name, $Function.Definition))
7546 }
7547 }
7548
7549 # threading adapted from
7550 # https://github.com/darkoperator/Posh-SecMod/blob/master/Discovery/Discovery.psm1#L407
7551 # Thanks Carlos!
7552
7553 # create a pool of maxThread runspaces
7554 $Pool = [runspacefactory]::CreateRunspacePool(1, $Threads, $SessionState, $Host)
7555 $Pool.Open()
7556
7557 $Jobs = @()
7558 $PS = @()
7559 $Wait = @()
7560
7561 $Counter = 0
7562 }
7563
7564 process {
7565
7566 ForEach ($Computer in $ComputerName) {
7567
7568 # make sure we get a server name
7569 if ($Computer -ne '') {
7570 # Write-Verbose "[*] Enumerating server $Computer ($($Counter+1) of $($ComputerName.count))"
7571
7572 While ($($Pool.GetAvailableRunspaces()) -le 0) {
7573 Start-Sleep -MilliSeconds 500
7574 }
7575
7576 # create a "powershell pipeline runner"
7577 $PS += [powershell]::create()
7578
7579 $PS[$Counter].runspacepool = $Pool
7580
7581 # add the script block + arguments
7582 $Null = $PS[$Counter].AddScript($ScriptBlock).AddParameter('ComputerName', $Computer)
7583 if($ScriptParameters) {
7584 ForEach ($Param in $ScriptParameters.GetEnumerator()) {
7585 $Null = $PS[$Counter].AddParameter($Param.Name, $Param.Value)
7586 }
7587 }
7588
7589 # start job
7590 $Jobs += $PS[$Counter].BeginInvoke();
7591
7592 # store wait handles for WaitForAll call
7593 $Wait += $Jobs[$Counter].AsyncWaitHandle
7594 }
7595 $Counter = $Counter + 1
7596 }
7597 }
7598
7599 end {
7600
7601 Write-Verbose "Waiting for scanning threads to finish..."
7602
7603 $WaitTimeout = Get-Date
7604
7605 # set a 60 second timeout for the scanning threads
7606 while ($($Jobs | Where-Object {$_.IsCompleted -eq $False}).count -gt 0 -or $($($(Get-Date) - $WaitTimeout).totalSeconds) -gt 60) {
7607 Start-Sleep -MilliSeconds 500
7608 }
7609
7610 # end async call
7611 for ($y = 0; $y -lt $Counter; $y++) {
7612
7613 try {
7614 # complete async job
7615 $PS[$y].EndInvoke($Jobs[$y])
7616
7617 } catch {
7618 Write-Warning "error: $_"
7619 }
7620 finally {
7621 $PS[$y].Dispose()
7622 }
7623 }
7624
7625 $Pool.Dispose()
7626 Write-Verbose "All threads completed!"
7627 }
7628}
7629
7630
7631function Invoke-UserHunter {
7632<#
7633 .SYNOPSIS
7634
7635 Finds which machines users of a specified group are logged into.
7636
7637 Author: @harmj0y
7638 License: BSD 3-Clause
7639
7640 .DESCRIPTION
7641
7642 This function finds the local domain name for a host using Get-NetDomain,
7643 queries the domain for users of a specified group (default "domain admins")
7644 with Get-NetGroupMember or reads in a target user list, queries the domain for all
7645 active machines with Get-NetComputer or reads in a pre-populated host list,
7646 randomly shuffles the target list, then for each server it gets a list of
7647 active users with Get-NetSession/Get-NetLoggedon. The found user list is compared
7648 against the target list, and a status message is displayed for any hits.
7649 The flag -CheckAccess will check each positive host to see if the current
7650 user has local admin access to the machine.
7651
7652 .PARAMETER ComputerName
7653
7654 Host array to enumerate, passable on the pipeline.
7655
7656 .PARAMETER ComputerFile
7657
7658 File of hostnames/IPs to search.
7659
7660 .PARAMETER ComputerFilter
7661
7662 Host filter name to query AD for, wildcards accepted.
7663
7664 .PARAMETER ComputerADSpath
7665
7666 The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
7667 Useful for OU queries.
7668
7669 .PARAMETER Unconstrained
7670
7671 Switch. Only enumerate computers that have unconstrained delegation.
7672
7673 .PARAMETER GroupName
7674
7675 Group name to query for target users.
7676
7677 .PARAMETER TargetServer
7678
7679 Hunt for users who are effective local admins on a target server.
7680
7681 .PARAMETER UserName
7682
7683 Specific username to search for.
7684
7685 .PARAMETER UserFilter
7686
7687 A customized ldap filter string to use for user enumeration, e.g. "(description=*admin*)"
7688
7689 .PARAMETER UserADSpath
7690
7691 The LDAP source to search through for users, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
7692 Useful for OU queries.
7693
7694 .PARAMETER UserFile
7695
7696 File of usernames to search for.
7697
7698 .PARAMETER AdminCount
7699
7700 Switch. Hunt for users with adminCount=1.
7701
7702 .PARAMETER AllowDelegation
7703
7704 Switch. Return user accounts that are not marked as 'sensitive and not allowed for delegation'
7705
7706 .PARAMETER StopOnSuccess
7707
7708 Switch. Stop hunting after finding after finding a target user.
7709
7710 .PARAMETER NoPing
7711
7712 Don't ping each host to ensure it's up before enumerating.
7713
7714 .PARAMETER CheckAccess
7715
7716 Switch. Check if the current user has local admin access to found machines.
7717
7718 .PARAMETER Delay
7719
7720 Delay between enumerating hosts, defaults to 0
7721
7722 .PARAMETER Jitter
7723
7724 Jitter for the host delay, defaults to +/- 0.3
7725
7726 .PARAMETER Domain
7727
7728 Domain for query for machines, defaults to the current domain.
7729
7730 .PARAMETER DomainController
7731
7732 Domain controller to reflect LDAP queries through.
7733
7734 .PARAMETER ShowAll
7735
7736 Switch. Return all user location results, i.e. Invoke-UserView functionality.
7737
7738 .PARAMETER SearchForest
7739
7740 Switch. Search all domains in the forest for target users instead of just
7741 a single domain.
7742
7743 .PARAMETER Stealth
7744
7745 Switch. Only enumerate sessions from connonly used target servers.
7746
7747 .PARAMETER StealthSource
7748
7749 The source of target servers to use, 'DFS' (distributed file servers),
7750 'DC' (domain controllers), 'File' (file servers), or 'All'
7751
7752 .PARAMETER ForeignUsers
7753
7754 Switch. Only return results that are not part of searched domain.
7755
7756 .PARAMETER Threads
7757
7758 The maximum concurrent threads to execute.
7759
7760 .EXAMPLE
7761
7762 PS C:\> Invoke-UserHunter -CheckAccess
7763
7764 Finds machines on the local domain where domain admins are logged into
7765 and checks if the current user has local administrator access.
7766
7767 .EXAMPLE
7768
7769 PS C:\> Invoke-UserHunter -Domain 'testing'
7770
7771 Finds machines on the 'testing' domain where domain admins are logged into.
7772
7773 .EXAMPLE
7774
7775 PS C:\> Invoke-UserHunter -Threads 20
7776
7777 Multi-threaded user hunting, replaces Invoke-UserHunterThreaded.
7778
7779 .EXAMPLE
7780
7781 PS C:\> Invoke-UserHunter -UserFile users.txt -ComputerFile hosts.txt
7782
7783 Finds machines in hosts.txt where any members of users.txt are logged in
7784 or have sessions.
7785
7786 .EXAMPLE
7787
7788 PS C:\> Invoke-UserHunter -GroupName "Power Users" -Delay 60
7789
7790 Find machines on the domain where members of the "Power Users" groups are
7791 logged into with a 60 second (+/- *.3) randomized delay between
7792 touching each host.
7793
7794 .EXAMPLE
7795
7796 PS C:\> Invoke-UserHunter -TargetServer FILESERVER
7797
7798 Query FILESERVER for useres who are effective local administrators using
7799 Get-NetLocalGroup -Recurse, and hunt for that user set on the network.
7800
7801 .EXAMPLE
7802
7803 PS C:\> Invoke-UserHunter -SearchForest
7804
7805 Find all machines in the current forest where domain admins are logged in.
7806
7807 .EXAMPLE
7808
7809 PS C:\> Invoke-UserHunter -Stealth
7810
7811 Executes old Invoke-StealthUserHunter functionality, enumerating commonly
7812 used servers and checking just sessions for each.
7813
7814 .LINK
7815 http://blog.harmj0y.net
7816#>
7817
7818 [CmdletBinding()]
7819 param(
7820 [Parameter(Position=0,ValueFromPipeline=$True)]
7821 [Alias('Hosts')]
7822 [String[]]
7823 $ComputerName,
7824
7825 [ValidateScript({Test-Path -Path $_ })]
7826 [Alias('HostList')]
7827 [String]
7828 $ComputerFile,
7829
7830 [String]
7831 $ComputerFilter,
7832
7833 [String]
7834 $ComputerADSpath,
7835
7836 [Switch]
7837 $Unconstrained,
7838
7839 [String]
7840 $GroupName = 'Domain Admins',
7841
7842 [String]
7843 $TargetServer,
7844
7845 [String]
7846 $UserName,
7847
7848 [String]
7849 $UserFilter,
7850
7851 [String]
7852 $UserADSpath,
7853
7854 [ValidateScript({Test-Path -Path $_ })]
7855 [String]
7856 $UserFile,
7857
7858 [Switch]
7859 $AdminCount,
7860
7861 [Switch]
7862 $AllowDelegation,
7863
7864 [Switch]
7865 $CheckAccess,
7866
7867 [Switch]
7868 $StopOnSuccess,
7869
7870 [Switch]
7871 $NoPing,
7872
7873 [UInt32]
7874 $Delay = 0,
7875
7876 [Double]
7877 $Jitter = .3,
7878
7879 [String]
7880 $Domain,
7881
7882 [String]
7883 $DomainController,
7884
7885 [Switch]
7886 $ShowAll,
7887
7888 [Switch]
7889 $SearchForest,
7890
7891 [Switch]
7892 $Stealth,
7893
7894 [String]
7895 [ValidateSet("DFS","DC","File","All")]
7896 $StealthSource ="All",
7897
7898 [Switch]
7899 $ForeignUsers,
7900
7901 [ValidateRange(1,100)]
7902 [Int]
7903 $Threads
7904 )
7905
7906 begin {
7907
7908 if ($PSBoundParameters['Debug']) {
7909 $DebugPreference = 'Continue'
7910 }
7911
7912 # random object for delay
7913 $RandNo = New-Object System.Random
7914
7915 Write-Verbose "[*] Running Invoke-UserHunter with delay of $Delay"
7916
7917 if($Domain) {
7918 $TargetDomains = @($Domain)
7919 }
7920 elseif($SearchForest) {
7921 # get ALL the domains in the forest to search
7922 $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
7923 }
7924 else {
7925 # use the local domain
7926 $TargetDomains = @( (Get-NetDomain).name )
7927 }
7928
7929 #####################################################
7930 #
7931 # First we build the host target set
7932 #
7933 #####################################################
7934
7935 if(!$ComputerName) {
7936 [Array]$ComputerName = @()
7937
7938 if($ComputerFile) {
7939 # if we're using a host list, read the targets in and add them to the target list
7940 $ComputerName = Get-Content -Path $ComputerFile
7941 }
7942 elseif($Stealth) {
7943 Write-Verbose "Stealth mode! Enumerating commonly used servers"
7944 Write-Verbose "Stealth source: $StealthSource"
7945
7946 ForEach ($Domain in $TargetDomains) {
7947 if (($StealthSource -eq "File") -or ($StealthSource -eq "All")) {
7948 Write-Verbose "[*] Querying domain $Domain for File Servers..."
7949 $ComputerName += Get-NetFileServer -Domain $Domain -DomainController $DomainController
7950 }
7951 if (($StealthSource -eq "DFS") -or ($StealthSource -eq "All")) {
7952 Write-Verbose "[*] Querying domain $Domain for DFS Servers..."
7953 $ComputerName += Get-DFSshare -Domain $Domain -DomainController $DomainController | ForEach-Object {$_.RemoteServerName}
7954 }
7955 if (($StealthSource -eq "DC") -or ($StealthSource -eq "All")) {
7956 Write-Verbose "[*] Querying domain $Domain for Domain Controllers..."
7957 $ComputerName += Get-NetDomainController -LDAP -Domain $Domain -DomainController $DomainController | ForEach-Object { $_.dnshostname}
7958 }
7959 }
7960 }
7961 else {
7962 ForEach ($Domain in $TargetDomains) {
7963 Write-Verbose "[*] Querying domain $Domain for hosts"
7964
7965 $Arguments = @{
7966 'Domain' = $Domain
7967 'DomainController' = $DomainController
7968 'ADSpath' = $ADSpath
7969 'Filter' = $ComputerFilter
7970 'Unconstrained' = $Unconstrained
7971 }
7972
7973 $ComputerName += Get-NetComputer @Arguments
7974 }
7975 }
7976
7977 # remove any null target hosts, uniquify the list and shuffle it
7978 $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
7979 if($($ComputerName.Count) -eq 0) {
7980 throw "No hosts found!"
7981 }
7982 }
7983
7984 #####################################################
7985 #
7986 # Now we build the user target set
7987 #
7988 #####################################################
7989
7990 # users we're going to be searching for
7991 $TargetUsers = @()
7992
7993 # get the current user so we can ignore it in the results
7994 $CurrentUser = ([Environment]::UserName).toLower()
7995
7996 # if we're showing all results, skip username enumeration
7997 if($ShowAll -or $ForeignUsers) {
7998 $User = New-Object PSObject
7999 $User | Add-Member Noteproperty 'MemberDomain' $Null
8000 $User | Add-Member Noteproperty 'MemberName' '*'
8001 $TargetUsers = @($User)
8002
8003 if($ForeignUsers) {
8004 # if we're searching for user results not in the primary domain
8005 $krbtgtName = Convert-CanonicaltoNT4 -ObjectName "krbtgt@$($Domain)"
8006 $DomainShortName = $krbtgtName.split("\")[0]
8007 }
8008 }
8009 # if we want to hunt for the effective domain users who can access a target server
8010 elseif($TargetServer) {
8011 Write-Verbose "Querying target server '$TargetServer' for local users"
8012 $TargetUsers = Get-NetLocalGroup $TargetServer -Recurse | Where-Object {(-not $_.IsGroup) -and $_.IsDomain } | ForEach-Object {
8013 $User = New-Object PSObject
8014 $User | Add-Member Noteproperty 'MemberDomain' ($_.AccountName).split("/")[0].toLower()
8015 $User | Add-Member Noteproperty 'MemberName' ($_.AccountName).split("/")[1].toLower()
8016 $User
8017 } | Where-Object {$_}
8018 }
8019 # if we get a specific username, only use that
8020 elseif($UserName) {
8021 Write-Verbose "[*] Using target user '$UserName'..."
8022 $User = New-Object PSObject
8023 $User | Add-Member Noteproperty 'MemberDomain' $TargetDomains[0]
8024 $User | Add-Member Noteproperty 'MemberName' $UserName.ToLower()
8025 $TargetUsers = @($User)
8026 }
8027 # read in a target user list if we have one
8028 elseif($UserFile) {
8029 $TargetUsers = Get-Content -Path $UserFile | ForEach-Object {
8030 $User = New-Object PSObject
8031 $User | Add-Member Noteproperty 'MemberDomain' $TargetDomains[0]
8032 $User | Add-Member Noteproperty 'MemberName' $_
8033 $User
8034 } | Where-Object {$_}
8035 }
8036 elseif($UserADSpath -or $UserFilter -or $AdminCount) {
8037 ForEach ($Domain in $TargetDomains) {
8038
8039 $Arguments = @{
8040 'Domain' = $Domain
8041 'DomainController' = $DomainController
8042 'ADSpath' = $UserADSpath
8043 'Filter' = $UserFilter
8044 'AdminCount' = $AdminCount
8045 'AllowDelegation' = $AllowDelegation
8046 }
8047
8048 Write-Verbose "[*] Querying domain $Domain for users"
8049 $TargetUsers += Get-NetUser @Arguments | ForEach-Object {
8050 $User = New-Object PSObject
8051 $User | Add-Member Noteproperty 'MemberDomain' $Domain
8052 $User | Add-Member Noteproperty 'MemberName' $_.samaccountname
8053 $User
8054 } | Where-Object {$_}
8055
8056 }
8057 }
8058 else {
8059 ForEach ($Domain in $TargetDomains) {
8060 Write-Verbose "[*] Querying domain $Domain for users of group '$GroupName'"
8061 $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController
8062 }
8063 }
8064
8065 if (( (-not $ShowAll) -and (-not $ForeignUsers) ) -and ((!$TargetUsers) -or ($TargetUsers.Count -eq 0))) {
8066 throw "[!] No users found to search for!"
8067 }
8068
8069 # script block that enumerates a server
8070 $HostEnumBlock = {
8071 param($ComputerName, $Ping, $TargetUsers, $CurrentUser, $Stealth, $DomainShortName)
8072
8073 # optionally check if the server is up first
8074 $Up = $True
8075 if($Ping) {
8076 $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
8077 }
8078 if($Up) {
8079 if(!$DomainShortName) {
8080 # if we're not searching for foreign users, check session information
8081 $Sessions = Get-NetSession -ComputerName $ComputerName
8082 ForEach ($Session in $Sessions) {
8083 $UserName = $Session.sesi10_username
8084 $CName = $Session.sesi10_cname
8085
8086 if($CName -and $CName.StartsWith("\\")) {
8087 $CName = $CName.TrimStart("\")
8088 }
8089
8090 # make sure we have a result
8091 if (($UserName) -and ($UserName.trim() -ne '') -and (!($UserName -match $CurrentUser))) {
8092
8093 $TargetUsers | Where-Object {$UserName -like $_.MemberName} | ForEach-Object {
8094
8095 $IP = Get-IPAddress -ComputerName $ComputerName
8096 $FoundUser = New-Object PSObject
8097 $FoundUser | Add-Member Noteproperty 'UserDomain' $_.MemberDomain
8098 $FoundUser | Add-Member Noteproperty 'UserName' $UserName
8099 $FoundUser | Add-Member Noteproperty 'ComputerName' $ComputerName
8100 $FoundUser | Add-Member Noteproperty 'IP' $IP
8101 $FoundUser | Add-Member Noteproperty 'SessionFrom' $CName
8102
8103 # see if we're checking to see if we have local admin access on this machine
8104 if ($CheckAccess) {
8105 $Admin = Invoke-CheckLocalAdminAccess -ComputerName $CName
8106 $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Admin
8107 }
8108 else {
8109 $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Null
8110 }
8111 $FoundUser
8112 }
8113 }
8114 }
8115 }
8116 if(!$Stealth) {
8117 # if we're not 'stealthy', enumerate loggedon users as well
8118 $LoggedOn = Get-NetLoggedon -ComputerName $ComputerName
8119 ForEach ($User in $LoggedOn) {
8120 $UserName = $User.wkui1_username
8121 # TODO: translate domain to authoratative name
8122 # then match domain name ?
8123 $UserDomain = $User.wkui1_logon_domain
8124
8125 # make sure wet have a result
8126 if (($UserName) -and ($UserName.trim() -ne '')) {
8127
8128 $TargetUsers | Where-Object {$UserName -like $_.MemberName} | ForEach-Object {
8129
8130 $Proceed = $True
8131 if($DomainShortName) {
8132 if ($DomainShortName.ToLower() -ne $UserDomain.ToLower()) {
8133 $Proceed = $True
8134 }
8135 else {
8136 $Proceed = $False
8137 }
8138 }
8139 if($Proceed) {
8140 $IP = Get-IPAddress -ComputerName $ComputerName
8141 $FoundUser = New-Object PSObject
8142 $FoundUser | Add-Member Noteproperty 'UserDomain' $UserDomain
8143 $FoundUser | Add-Member Noteproperty 'UserName' $UserName
8144 $FoundUser | Add-Member Noteproperty 'ComputerName' $ComputerName
8145 $FoundUser | Add-Member Noteproperty 'IP' $IP
8146 $FoundUser | Add-Member Noteproperty 'SessionFrom' $Null
8147
8148 # see if we're checking to see if we have local admin access on this machine
8149 if ($CheckAccess) {
8150 $Admin = Invoke-CheckLocalAdminAccess -ComputerName $ComputerName
8151 $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Admin
8152 }
8153 else {
8154 $FoundUser | Add-Member Noteproperty 'LocalAdmin' $Null
8155 }
8156 $FoundUser
8157 }
8158 }
8159 }
8160 }
8161 }
8162 }
8163 }
8164
8165 }
8166
8167 process {
8168
8169 if($Threads) {
8170 Write-Verbose "Using threading with threads = $Threads"
8171
8172 # if we're using threading, kick off the script block with Invoke-ThreadedFunction
8173 $ScriptParams = @{
8174 'Ping' = $(-not $NoPing)
8175 'TargetUsers' = $TargetUsers
8176 'CurrentUser' = $CurrentUser
8177 'Stealth' = $Stealth
8178 'DomainShortName' = $DomainShortName
8179 }
8180
8181 # kick off the threaded script block + arguments
8182 Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams
8183 }
8184
8185 else {
8186 if(-not $NoPing -and ($ComputerName.count -ne 1)) {
8187 # ping all hosts in parallel
8188 $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
8189 $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
8190 }
8191
8192 Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
8193 $Counter = 0
8194
8195 ForEach ($Computer in $ComputerName) {
8196
8197 $Counter = $Counter + 1
8198
8199 # sleep for our semi-randomized interval
8200 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
8201
8202 Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
8203 $Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $TargetUsers, $CurrentUser, $Stealth, $DomainShortName
8204 $Result
8205
8206 if($Result -and $StopOnSuccess) {
8207 Write-Verbose "[*] Target user found, returning early"
8208 return
8209 }
8210 }
8211 }
8212
8213 }
8214}
8215
8216
8217function Invoke-StealthUserHunter {
8218 [CmdletBinding()]
8219 param(
8220 [Parameter(Position=0,ValueFromPipeline=$True)]
8221 [Alias('Hosts')]
8222 [String[]]
8223 $ComputerName,
8224
8225 [ValidateScript({Test-Path -Path $_ })]
8226 [Alias('HostList')]
8227 [String]
8228 $ComputerFile,
8229
8230 [String]
8231 $ComputerFilter,
8232
8233 [String]
8234 $ComputerADSpath,
8235
8236 [String]
8237 $GroupName = 'Domain Admins',
8238
8239 [String]
8240 $TargetServer,
8241
8242 [String]
8243 $UserName,
8244
8245 [String]
8246 $UserFilter,
8247
8248 [String]
8249 $UserADSpath,
8250
8251 [ValidateScript({Test-Path -Path $_ })]
8252 [String]
8253 $UserFile,
8254
8255 [Switch]
8256 $CheckAccess,
8257
8258 [Switch]
8259 $StopOnSuccess,
8260
8261 [Switch]
8262 $NoPing,
8263
8264 [UInt32]
8265 $Delay = 0,
8266
8267 [Double]
8268 $Jitter = .3,
8269
8270 [String]
8271 $Domain,
8272
8273 [Switch]
8274 $ShowAll,
8275
8276 [Switch]
8277 $SearchForest,
8278
8279 [String]
8280 [ValidateSet("DFS","DC","File","All")]
8281 $StealthSource ="All"
8282 )
8283 # kick off Invoke-UserHunter with stealth options
8284 Invoke-UserHunter -Stealth @PSBoundParameters
8285}
8286
8287
8288function Invoke-ProcessHunter {
8289<#
8290 .SYNOPSIS
8291
8292 Query the process lists of remote machines, searching for
8293 processes with a specific name or owned by a specific user.
8294 Thanks to @paulbrandau for the approach idea.
8295
8296 Author: @harmj0y
8297 License: BSD 3-Clause
8298
8299 .PARAMETER ComputerName
8300
8301 Host array to enumerate, passable on the pipeline.
8302
8303 .PARAMETER ComputerFile
8304
8305 File of hostnames/IPs to search.
8306
8307 .PARAMETER ComputerFilter
8308
8309 Host filter name to query AD for, wildcards accepted.
8310
8311 .PARAMETER ComputerADSpath
8312
8313 The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
8314 Useful for OU queries.
8315
8316 .PARAMETER ProcessName
8317
8318 The name of the process to hunt, or a comma separated list of names.
8319
8320 .PARAMETER GroupName
8321
8322 Group name to query for target users.
8323
8324 .PARAMETER TargetServer
8325
8326 Hunt for users who are effective local admins on a target server.
8327
8328 .PARAMETER UserName
8329
8330 Specific username to search for.
8331
8332 .PARAMETER UserFilter
8333
8334 A customized ldap filter string to use for user enumeration, e.g. "(description=*admin*)"
8335
8336 .PARAMETER UserADSpath
8337
8338 The LDAP source to search through for users, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
8339 Useful for OU queries.
8340
8341 .PARAMETER UserFile
8342
8343 File of usernames to search for.
8344
8345 .PARAMETER RemoteUserName
8346
8347 The "domain\username" to use for the WMI call on a remote system.
8348 If supplied, 'RemotePassword' must be supplied as well.
8349
8350 .PARAMETER RemotePassword
8351
8352 The password to use for the WMI call on a remote system.
8353
8354 .PARAMETER StopOnSuccess
8355
8356 Switch. Stop hunting after finding after finding a target user/process.
8357
8358 .PARAMETER NoPing
8359
8360 Switch. Don't ping each host to ensure it's up before enumerating.
8361
8362 .PARAMETER Delay
8363
8364 Delay between enumerating hosts, defaults to 0
8365
8366 .PARAMETER Jitter
8367
8368 Jitter for the host delay, defaults to +/- 0.3
8369
8370 .PARAMETER Domain
8371
8372 Domain for query for machines, defaults to the current domain.
8373
8374 .PARAMETER DomainController
8375
8376 Domain controller to reflect LDAP queries through.
8377
8378 .PARAMETER ShowAll
8379
8380 Switch. Return all user location results.
8381
8382 .PARAMETER SearchForest
8383
8384 Switch. Search all domains in the forest for target users instead of just
8385 a single domain.
8386
8387 .PARAMETER Threads
8388
8389 The maximum concurrent threads to execute.
8390
8391 .EXAMPLE
8392
8393 PS C:\> Invoke-ProcessHunter -Domain 'testing'
8394
8395 Finds machines on the 'testing' domain where domain admins have a
8396 running process.
8397
8398 .EXAMPLE
8399
8400 PS C:\> Invoke-ProcessHunter -Threads 20
8401
8402 Multi-threaded process hunting, replaces Invoke-ProcessHunterThreaded.
8403
8404 .EXAMPLE
8405
8406 PS C:\> Invoke-ProcessHunter -UserFile users.txt -ComputerFile hosts.txt
8407
8408 Finds machines in hosts.txt where any members of users.txt have running
8409 processes.
8410
8411 .EXAMPLE
8412
8413 PS C:\> Invoke-ProcessHunter -GroupName "Power Users" -Delay 60
8414
8415 Find machines on the domain where members of the "Power Users" groups have
8416 running processes with a 60 second (+/- *.3) randomized delay between
8417 touching each host.
8418
8419 .LINK
8420
8421 http://blog.harmj0y.net
8422#>
8423
8424 [CmdletBinding()]
8425 param(
8426 [Parameter(Position=0,ValueFromPipeline=$True)]
8427 [Alias('Hosts')]
8428 [String[]]
8429 $ComputerName,
8430
8431 [ValidateScript({Test-Path -Path $_ })]
8432 [Alias('HostList')]
8433 [String]
8434 $ComputerFile,
8435
8436 [String]
8437 $ComputerFilter,
8438
8439 [String]
8440 $ComputerADSpath,
8441
8442 [String]
8443 $ProcessName,
8444
8445 [String]
8446 $GroupName = 'Domain Admins',
8447
8448 [String]
8449 $TargetServer,
8450
8451 [String]
8452 $UserName,
8453
8454 [String]
8455 $UserFilter,
8456
8457 [String]
8458 $UserADSpath,
8459
8460 [ValidateScript({Test-Path -Path $_ })]
8461 [String]
8462 $UserFile,
8463
8464 [String]
8465 $RemoteUserName,
8466
8467 [String]
8468 $RemotePassword,
8469
8470 [Switch]
8471 $StopOnSuccess,
8472
8473 [Switch]
8474 $NoPing,
8475
8476 [UInt32]
8477 $Delay = 0,
8478
8479 [Double]
8480 $Jitter = .3,
8481
8482 [String]
8483 $Domain,
8484
8485 [String]
8486 $DomainController,
8487
8488 [Switch]
8489 $ShowAll,
8490
8491 [Switch]
8492 $SearchForest,
8493
8494 [ValidateRange(1,100)]
8495 [Int]
8496 $Threads
8497 )
8498
8499 begin {
8500
8501 if ($PSBoundParameters['Debug']) {
8502 $DebugPreference = 'Continue'
8503 }
8504
8505 # random object for delay
8506 $RandNo = New-Object System.Random
8507
8508 Write-Verbose "[*] Running Invoke-ProcessHunter with delay of $Delay"
8509
8510 if($Domain) {
8511 $TargetDomains = @($Domain)
8512 }
8513 elseif($SearchForest) {
8514 # get ALL the domains in the forest to search
8515 $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
8516 }
8517 else {
8518 # use the local domain
8519 $TargetDomains = @( (Get-NetDomain).name )
8520 }
8521
8522 #####################################################
8523 #
8524 # First we build the host target set
8525 #
8526 #####################################################
8527
8528 if(!$ComputerName) {
8529 # if we're using a host list, read the targets in and add them to the target list
8530 if($ComputerFile) {
8531 $ComputerName = Get-Content -Path $ComputerFile
8532 }
8533 else {
8534 [array]$ComputerName = @()
8535 ForEach ($Domain in $TargetDomains) {
8536 Write-Verbose "[*] Querying domain $Domain for hosts"
8537 $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Filter $ComputerFilter -ADSpath $ComputerADSpath
8538 }
8539 }
8540
8541 # remove any null target hosts, uniquify the list and shuffle it
8542 $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
8543 if($($ComputerName.Count) -eq 0) {
8544 throw "No hosts found!"
8545 }
8546 }
8547
8548 #####################################################
8549 #
8550 # Now we build the user target set
8551 #
8552 #####################################################
8553
8554 if(!$ProcessName) {
8555 Write-Verbose "No process name specified, building a target user set"
8556
8557 # users we're going to be searching for
8558 $TargetUsers = @()
8559
8560 # if we want to hunt for the effective domain users who can access a target server
8561 if($TargetServer) {
8562 Write-Verbose "Querying target server '$TargetServer' for local users"
8563 $TargetUsers = Get-NetLocalGroup $TargetServer -Recurse | Where-Object {(-not $_.IsGroup) -and $_.IsDomain } | ForEach-Object {
8564 ($_.AccountName).split("/")[1].toLower()
8565 } | Where-Object {$_}
8566 }
8567 # if we get a specific username, only use that
8568 elseif($UserName) {
8569 Write-Verbose "[*] Using target user '$UserName'..."
8570 $TargetUsers = @( $UserName.ToLower() )
8571 }
8572 # read in a target user list if we have one
8573 elseif($UserFile) {
8574 $TargetUsers = Get-Content -Path $UserFile | Where-Object {$_}
8575 }
8576 elseif($UserADSpath -or $UserFilter) {
8577 ForEach ($Domain in $TargetDomains) {
8578 Write-Verbose "[*] Querying domain $Domain for users"
8579 $TargetUsers += Get-NetUser -Domain $Domain -DomainController $DomainController -ADSpath $UserADSpath -Filter $UserFilter | ForEach-Object {
8580 $_.samaccountname
8581 } | Where-Object {$_}
8582 }
8583 }
8584 else {
8585 ForEach ($Domain in $TargetDomains) {
8586 Write-Verbose "[*] Querying domain $Domain for users of group '$GroupName'"
8587 $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController| Foreach-Object {
8588 $_.MemberName
8589 }
8590 }
8591 }
8592
8593 if ((-not $ShowAll) -and ((!$TargetUsers) -or ($TargetUsers.Count -eq 0))) {
8594 throw "[!] No users found to search for!"
8595 }
8596 }
8597
8598 # script block that enumerates a server
8599 $HostEnumBlock = {
8600 param($ComputerName, $Ping, $ProcessName, $TargetUsers, $RemoteUserName, $RemotePassword)
8601
8602 # optionally check if the server is up first
8603 $Up = $True
8604 if($Ping) {
8605 $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
8606 }
8607 if($Up) {
8608 # try to enumerate all active processes on the remote host
8609 # and search for a specific process name
8610 if($RemoteUserName -and $RemotePassword) {
8611 $Processes = Get-NetProcess -RemoteUserName $RemoteUserName -RemotePassword $RemotePassword -ComputerName $ComputerName -ErrorAction SilentlyContinue
8612 }
8613 else {
8614 $Processes = Get-NetProcess -ComputerName $ComputerName -ErrorAction SilentlyContinue
8615 }
8616
8617 ForEach ($Process in $Processes) {
8618 # if we're hunting for a process name or comma-separated names
8619 if($ProcessName) {
8620 $ProcessName.split(",") | ForEach-Object {
8621 if ($Process.ProcessName -match $_) {
8622 $Process
8623 }
8624 }
8625 }
8626 # if the session user is in the target list, display some output
8627 elseif ($TargetUsers -contains $Process.User) {
8628 $Process
8629 }
8630 }
8631 }
8632 }
8633
8634 }
8635
8636 process {
8637
8638 if($Threads) {
8639 Write-Verbose "Using threading with threads = $Threads"
8640
8641 # if we're using threading, kick off the script block with Invoke-ThreadedFunction
8642 $ScriptParams = @{
8643 'Ping' = $(-not $NoPing)
8644 'ProcessName' = $ProcessName
8645 'TargetUsers' = $TargetUsers
8646 'RemoteUserName' = $RemoteUserName
8647 'RemotePassword' = $RemotePassword
8648 }
8649
8650 # kick off the threaded script block + arguments
8651 Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams
8652 }
8653
8654 else {
8655 if(-not $NoPing -and ($ComputerName.count -ne 1)) {
8656 # ping all hosts in parallel
8657 $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
8658 $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
8659 }
8660
8661 Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
8662 $Counter = 0
8663
8664 ForEach ($Computer in $ComputerName) {
8665
8666 $Counter = $Counter + 1
8667
8668 # sleep for our semi-randomized interval
8669 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
8670
8671 Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
8672 $Result = Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $ProcessName, $TargetUsers, $RemoteUserName, $RemotePassword
8673 $Result
8674
8675 if($Result -and $StopOnSuccess) {
8676 Write-Verbose "[*] Target user/process found, returning early"
8677 return
8678 }
8679 }
8680 }
8681
8682 }
8683}
8684
8685
8686function Invoke-EventHunter {
8687<#
8688 .SYNOPSIS
8689
8690 Queries all domain controllers on the network for account
8691 logon events (ID 4624) and TGT request events (ID 4768),
8692 searching for target users.
8693
8694 Note: Domain Admin (or equiv) rights are needed to query
8695 this information from the DCs.
8696
8697 Author: @sixdub, @harmj0y
8698 License: BSD 3-Clause
8699
8700 .PARAMETER ComputerName
8701
8702 Host array to enumerate, passable on the pipeline.
8703
8704 .PARAMETER ComputerFile
8705
8706 File of hostnames/IPs to search.
8707
8708 .PARAMETER ComputerFilter
8709
8710 Host filter name to query AD for, wildcards accepted.
8711
8712 .PARAMETER ComputerADSpath
8713
8714 The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
8715 Useful for OU queries.
8716
8717 .PARAMETER GroupName
8718
8719 Group name to query for target users.
8720
8721 .PARAMETER TargetServer
8722
8723 Hunt for users who are effective local admins on a target server.
8724
8725 .PARAMETER UserName
8726
8727 Specific username to search for.
8728
8729 .PARAMETER UserFilter
8730
8731 A customized ldap filter string to use for user enumeration, e.g. "(description=*admin*)"
8732
8733 .PARAMETER UserADSpath
8734
8735 The LDAP source to search through for users, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
8736 Useful for OU queries.
8737
8738 .PARAMETER UserFile
8739
8740 File of usernames to search for.
8741
8742 .PARAMETER NoPing
8743
8744 Don't ping each host to ensure it's up before enumerating.
8745
8746 .PARAMETER Domain
8747
8748 Domain for query for machines, defaults to the current domain.
8749
8750 .PARAMETER DomainController
8751
8752 Domain controller to reflect LDAP queries through.
8753
8754 .PARAMETER SearchDays
8755
8756 Number of days back to search logs for. Default 3.
8757
8758 .PARAMETER SearchForest
8759
8760 Switch. Search all domains in the forest for target users instead of just
8761 a single domain.
8762
8763 .PARAMETER Threads
8764
8765 The maximum concurrent threads to execute.
8766
8767 .EXAMPLE
8768
8769 PS C:\> Invoke-EventHunter
8770
8771 .LINK
8772
8773 http://blog.harmj0y.net
8774#>
8775
8776 [CmdletBinding()]
8777 param(
8778 [Parameter(Position=0,ValueFromPipeline=$True)]
8779 [Alias('Hosts')]
8780 [String[]]
8781 $ComputerName,
8782
8783 [ValidateScript({Test-Path -Path $_ })]
8784 [Alias('HostList')]
8785 [String]
8786 $ComputerFile,
8787
8788 [String]
8789 $ComputerFilter,
8790
8791 [String]
8792 $ComputerADSpath,
8793
8794 [String]
8795 $GroupName = 'Domain Admins',
8796
8797 [String]
8798 $TargetServer,
8799
8800 [String]
8801 $UserName,
8802
8803 [String]
8804 $UserFilter,
8805
8806 [String]
8807 $UserADSpath,
8808
8809 [ValidateScript({Test-Path -Path $_ })]
8810 [String]
8811 $UserFile,
8812
8813 [String]
8814 $Domain,
8815
8816 [String]
8817 $DomainController,
8818
8819 [Int32]
8820 $SearchDays = 3,
8821
8822 [Switch]
8823 $SearchForest,
8824
8825 [ValidateRange(1,100)]
8826 [Int]
8827 $Threads
8828 )
8829
8830 begin {
8831
8832 if ($PSBoundParameters['Debug']) {
8833 $DebugPreference = 'Continue'
8834 }
8835
8836 # random object for delay
8837 $RandNo = New-Object System.Random
8838
8839 Write-Verbose "[*] Running Invoke-EventHunter"
8840
8841 if($Domain) {
8842 $TargetDomains = @($Domain)
8843 }
8844 elseif($SearchForest) {
8845 # get ALL the domains in the forest to search
8846 $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
8847 }
8848 else {
8849 # use the local domain
8850 $TargetDomains = @( (Get-NetDomain).name )
8851 }
8852
8853 #####################################################
8854 #
8855 # First we build the host target set
8856 #
8857 #####################################################
8858
8859 if(!$ComputerName) {
8860 # if we're using a host list, read the targets in and add them to the target list
8861 if($ComputerFile) {
8862 $ComputerName = Get-Content -Path $ComputerFile
8863 }
8864 elseif($ComputerFilter -or $ComputerADSpath) {
8865 [array]$ComputerName = @()
8866 ForEach ($Domain in $TargetDomains) {
8867 Write-Verbose "[*] Querying domain $Domain for hosts"
8868 $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Filter $ComputerFilter -ADSpath $ComputerADSpath
8869 }
8870 }
8871 else {
8872 # if a computer specifier isn't given, try to enumerate all domain controllers
8873 [array]$ComputerName = @()
8874 ForEach ($Domain in $TargetDomains) {
8875 Write-Verbose "[*] Querying domain $Domain for domain controllers"
8876 $ComputerName += Get-NetDomainController -LDAP -Domain $Domain -DomainController $DomainController | ForEach-Object { $_.dnshostname}
8877 }
8878 }
8879
8880 # remove any null target hosts, uniquify the list and shuffle it
8881 $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
8882 if($($ComputerName.Count) -eq 0) {
8883 throw "No hosts found!"
8884 }
8885 }
8886
8887 #####################################################
8888 #
8889 # Now we build the user target set
8890 #
8891 #####################################################
8892
8893 # users we're going to be searching for
8894 $TargetUsers = @()
8895
8896 # if we want to hunt for the effective domain users who can access a target server
8897 if($TargetServer) {
8898 Write-Verbose "Querying target server '$TargetServer' for local users"
8899 $TargetUsers = Get-NetLocalGroup $TargetServer -Recurse | Where-Object {(-not $_.IsGroup) -and $_.IsDomain } | ForEach-Object {
8900 ($_.AccountName).split("/")[1].toLower()
8901 } | Where-Object {$_}
8902 }
8903 # if we get a specific username, only use that
8904 elseif($UserName) {
8905 Write-Verbose "[*] Using target user '$UserName'..."
8906 $TargetUsers = @( $UserName.ToLower() )
8907 }
8908 # read in a target user list if we have one
8909 elseif($UserFile) {
8910 $TargetUsers = Get-Content -Path $UserFile | Where-Object {$_}
8911 }
8912 elseif($UserADSpath -or $UserFilter) {
8913 ForEach ($Domain in $TargetDomains) {
8914 Write-Verbose "[*] Querying domain $Domain for users"
8915 $TargetUsers += Get-NetUser -Domain $Domain -DomainController $DomainController -ADSpath $UserADSpath -Filter $UserFilter | ForEach-Object {
8916 $_.samaccountname
8917 } | Where-Object {$_}
8918 }
8919 }
8920 else {
8921 ForEach ($Domain in $TargetDomains) {
8922 Write-Verbose "[*] Querying domain $Domain for users of group '$GroupName'"
8923 $TargetUsers += Get-NetGroupMember -GroupName $GroupName -Domain $Domain -DomainController $DomainController | Foreach-Object {
8924 $_.MemberName
8925 }
8926 }
8927 }
8928
8929 if (((!$TargetUsers) -or ($TargetUsers.Count -eq 0))) {
8930 throw "[!] No users found to search for!"
8931 }
8932
8933 # script block that enumerates a server
8934 $HostEnumBlock = {
8935 param($ComputerName, $Ping, $TargetUsers, $SearchDays)
8936
8937 # optionally check if the server is up first
8938 $Up = $True
8939 if($Ping) {
8940 $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
8941 }
8942 if($Up) {
8943 # try to enumerate
8944 Get-UserEvent -ComputerName $ComputerName -EventType 'all' -DateStart ([DateTime]::Today.AddDays(-$SearchDays)) | Where-Object {
8945 # filter for the target user set
8946 $TargetUsers -contains $_.UserName
8947 }
8948 }
8949 }
8950
8951 }
8952
8953 process {
8954
8955 if($Threads) {
8956 Write-Verbose "Using threading with threads = $Threads"
8957
8958 # if we're using threading, kick off the script block with Invoke-ThreadedFunction
8959 $ScriptParams = @{
8960 'Ping' = $(-not $NoPing)
8961 'TargetUsers' = $TargetUsers
8962 'SearchDays' = $SearchDays
8963 }
8964
8965 # kick off the threaded script block + arguments
8966 Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams
8967 }
8968
8969 else {
8970 if(-not $NoPing -and ($ComputerName.count -ne 1)) {
8971 # ping all hosts in parallel
8972 $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
8973 $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
8974 }
8975
8976 Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
8977 $Counter = 0
8978
8979 ForEach ($Computer in $ComputerName) {
8980
8981 $Counter = $Counter + 1
8982
8983 # sleep for our semi-randomized interval
8984 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
8985
8986 Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
8987 Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $(-not $NoPing), $TargetUsers, $SearchDays
8988 }
8989 }
8990
8991 }
8992}
8993
8994
8995function Invoke-ShareFinder {
8996<#
8997 .SYNOPSIS
8998
8999 This function finds the local domain name for a host using Get-NetDomain,
9000 queries the domain for all active machines with Get-NetComputer, then for
9001 each server it lists of active shares with Get-NetShare. Non-standard shares
9002 can be filtered out with -Exclude* flags.
9003
9004 Author: @harmj0y
9005 License: BSD 3-Clause
9006
9007 .PARAMETER ComputerName
9008
9009 Host array to enumerate, passable on the pipeline.
9010
9011 .PARAMETER ComputerFile
9012
9013 File of hostnames/IPs to search.
9014
9015 .PARAMETER ComputerFilter
9016
9017 Host filter name to query AD for, wildcards accepted.
9018
9019 .PARAMETER ComputerADSpath
9020
9021 The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
9022 Useful for OU queries.
9023
9024 .PARAMETER ExcludeStandard
9025
9026 Switch. Exclude standard shares from display (C$, IPC$, print$ etc.)
9027
9028 .PARAMETER ExcludePrint
9029
9030 Switch. Exclude the print$ share.
9031
9032 .PARAMETER ExcludeIPC
9033
9034 Switch. Exclude the IPC$ share.
9035
9036 .PARAMETER CheckShareAccess
9037
9038 Switch. Only display found shares that the local user has access to.
9039
9040 .PARAMETER CheckAdmin
9041
9042 Switch. Only display ADMIN$ shares the local user has access to.
9043
9044 .PARAMETER NoPing
9045
9046 Switch. Don't ping each host to ensure it's up before enumerating.
9047
9048 .PARAMETER Delay
9049
9050 Delay between enumerating hosts, defaults to 0.
9051
9052 .PARAMETER Jitter
9053
9054 Jitter for the host delay, defaults to +/- 0.3.
9055
9056 .PARAMETER Domain
9057
9058 Domain to query for machines, defaults to the current domain.
9059
9060 .PARAMETER DomainController
9061
9062 Domain controller to reflect LDAP queries through.
9063
9064 .PARAMETER SearchForest
9065
9066 Switch. Search all domains in the forest for target users instead of just
9067 a single domain.
9068
9069 .PARAMETER Threads
9070
9071 The maximum concurrent threads to execute.
9072
9073 .EXAMPLE
9074
9075 PS C:\> Invoke-ShareFinder -ExcludeStandard
9076
9077 Find non-standard shares on the domain.
9078
9079 .EXAMPLE
9080
9081 PS C:\> Invoke-ShareFinder -Threads 20
9082
9083 Multi-threaded share finding, replaces Invoke-ShareFinderThreaded.
9084
9085 .EXAMPLE
9086
9087 PS C:\> Invoke-ShareFinder -Delay 60
9088
9089 Find shares on the domain with a 60 second (+/- *.3)
9090 randomized delay between touching each host.
9091
9092 .EXAMPLE
9093
9094 PS C:\> Invoke-ShareFinder -ComputerFile hosts.txt
9095
9096 Find shares for machines in the specified hosts file.
9097
9098 .LINK
9099 http://blog.harmj0y.net
9100#>
9101
9102 [CmdletBinding()]
9103 param(
9104 [Parameter(Position=0,ValueFromPipeline=$True)]
9105 [Alias('Hosts')]
9106 [String[]]
9107 $ComputerName,
9108
9109 [ValidateScript({Test-Path -Path $_ })]
9110 [Alias('HostList')]
9111 [String]
9112 $ComputerFile,
9113
9114 [String]
9115 $ComputerFilter,
9116
9117 [String]
9118 $ComputerADSpath,
9119
9120 [Switch]
9121 $ExcludeStandard,
9122
9123 [Switch]
9124 $ExcludePrint,
9125
9126 [Switch]
9127 $ExcludeIPC,
9128
9129 [Switch]
9130 $NoPing,
9131
9132 [Switch]
9133 $CheckShareAccess,
9134
9135 [Switch]
9136 $CheckAdmin,
9137
9138 [UInt32]
9139 $Delay = 0,
9140
9141 [Double]
9142 $Jitter = .3,
9143
9144 [String]
9145 $Domain,
9146
9147 [String]
9148 $DomainController,
9149
9150 [Switch]
9151 $SearchForest,
9152
9153 [ValidateRange(1,100)]
9154 [Int]
9155 $Threads
9156 )
9157
9158 begin {
9159 if ($PSBoundParameters['Debug']) {
9160 $DebugPreference = 'Continue'
9161 }
9162
9163 # random object for delay
9164 $RandNo = New-Object System.Random
9165
9166 Write-Verbose "[*] Running Invoke-ShareFinder with delay of $Delay"
9167
9168 # figure out the shares we want to ignore
9169 [String[]] $ExcludedShares = @('')
9170
9171 if ($ExcludePrint) {
9172 $ExcludedShares = $ExcludedShares + "PRINT$"
9173 }
9174 if ($ExcludeIPC) {
9175 $ExcludedShares = $ExcludedShares + "IPC$"
9176 }
9177 if ($ExcludeStandard) {
9178 $ExcludedShares = @('', "ADMIN$", "IPC$", "C$", "PRINT$")
9179 }
9180
9181 if(!$ComputerName) {
9182
9183 if($Domain) {
9184 $TargetDomains = @($Domain)
9185 }
9186 elseif($SearchForest) {
9187 # get ALL the domains in the forest to search
9188 $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
9189 }
9190 else {
9191 # use the local domain
9192 $TargetDomains = @( (Get-NetDomain).name )
9193 }
9194
9195 # if we're using a host file list, read the targets in and add them to the target list
9196 if($ComputerFile) {
9197 $ComputerName = Get-Content -Path $ComputerFile
9198 }
9199 else {
9200 [array]$ComputerName = @()
9201 ForEach ($Domain in $TargetDomains) {
9202 Write-Verbose "[*] Querying domain $Domain for hosts"
9203 $ComputerName += Get-NetComputer -Domain $Domain -DomainController $DomainController -Filter $ComputerFilter -ADSpath $ComputerADSpath
9204 }
9205 }
9206
9207 # remove any null target hosts, uniquify the list and shuffle it
9208 $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
9209 if($($ComputerName.count) -eq 0) {
9210 throw "No hosts found!"
9211 }
9212 }
9213
9214 # script block that enumerates a server
9215 $HostEnumBlock = {
9216 param($ComputerName, $Ping, $CheckShareAccess, $ExcludedShares, $CheckAdmin)
9217
9218 # optionally check if the server is up first
9219 $Up = $True
9220 if($Ping) {
9221 $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
9222 }
9223 if($Up) {
9224 # get the shares for this host and check what we find
9225 $Shares = Get-NetShare -ComputerName $ComputerName
9226 ForEach ($Share in $Shares) {
9227 Write-Debug "[*] Server share: $Share"
9228 $NetName = $Share.shi1_netname
9229 $Remark = $Share.shi1_remark
9230 $Path = '\\'+$ComputerName+'\'+$NetName
9231
9232 # make sure we get a real share name back
9233 if (($NetName) -and ($NetName.trim() -ne '')) {
9234 # if we're just checking for access to ADMIN$
9235 if($CheckAdmin) {
9236 if($NetName.ToUpper() -eq "ADMIN$") {
9237 try {
9238 $Null = [IO.Directory]::GetFiles($Path)
9239 "\\$ComputerName\$NetName `t- $Remark"
9240 }
9241 catch {
9242 Write-Debug "Error accessing path $Path : $_"
9243 }
9244 }
9245 }
9246 # skip this share if it's in the exclude list
9247 elseif ($ExcludedShares -NotContains $NetName.ToUpper()) {
9248 # see if we want to check access to this share
9249 if($CheckShareAccess) {
9250 # check if the user has access to this path
9251 try {
9252 $Null = [IO.Directory]::GetFiles($Path)
9253 "\\$ComputerName\$NetName `t- $Remark"
9254 }
9255 catch {
9256 Write-Debug "Error accessing path $Path : $_"
9257 }
9258 }
9259 else {
9260 "\\$ComputerName\$NetName `t- $Remark"
9261 }
9262 }
9263 }
9264 }
9265 }
9266 }
9267
9268 }
9269
9270 process {
9271
9272 if($Threads) {
9273 Write-Verbose "Using threading with threads = $Threads"
9274
9275 # if we're using threading, kick off the script block with Invoke-ThreadedFunction
9276 $ScriptParams = @{
9277 'Ping' = $(-not $NoPing)
9278 'CheckShareAccess' = $CheckShareAccess
9279 'ExcludedShares' = $ExcludedShares
9280 'CheckAdmin' = $CheckAdmin
9281 }
9282
9283 # kick off the threaded script block + arguments
9284 Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams
9285 }
9286
9287 else {
9288 if(-not $NoPing -and ($ComputerName.count -ne 1)) {
9289 # ping all hosts in parallel
9290 $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
9291 $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
9292 }
9293
9294 Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
9295 $Counter = 0
9296
9297 ForEach ($Computer in $ComputerName) {
9298
9299 $Counter = $Counter + 1
9300
9301 # sleep for our semi-randomized interval
9302 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
9303
9304 Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
9305 Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $CheckShareAccess, $ExcludedShares, $CheckAdmin
9306 }
9307 }
9308
9309 }
9310}
9311
9312
9313function Invoke-FileFinder {
9314<#
9315 .SYNOPSIS
9316
9317 Finds sensitive files on the domain.
9318
9319 Author: @harmj0y
9320 License: BSD 3-Clause
9321
9322 .DESCRIPTION
9323
9324 This function finds the local domain name for a host using Get-NetDomain,
9325 queries the domain for all active machines with Get-NetComputer, grabs
9326 the readable shares for each server, and recursively searches every
9327 share for files with specific keywords in the name.
9328 If a share list is passed, EVERY share is enumerated regardless of
9329 other options.
9330
9331 .PARAMETER ComputerName
9332
9333 Host array to enumerate, passable on the pipeline.
9334
9335 .PARAMETER ComputerFile
9336
9337 File of hostnames/IPs to search.
9338
9339 .PARAMETER ComputerFilter
9340
9341 Host filter name to query AD for, wildcards accepted.
9342
9343 .PARAMETER ComputerADSpath
9344
9345 The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
9346 Useful for OU queries.
9347
9348 .PARAMETER ShareList
9349
9350 List if \\HOST\shares to search through.
9351
9352 .PARAMETER Terms
9353
9354 Terms to search for.
9355
9356 .PARAMETER OfficeDocs
9357
9358 Switch. Search for office documents (*.doc*, *.xls*, *.ppt*)
9359
9360 .PARAMETER FreshEXEs
9361
9362 Switch. Find .EXEs accessed within the last week.
9363
9364 .PARAMETER LastAccessTime
9365
9366 Only return files with a LastAccessTime greater than this date value.
9367
9368 .PARAMETER LastWriteTime
9369
9370 Only return files with a LastWriteTime greater than this date value.
9371
9372 .PARAMETER CreationTime
9373
9374 Only return files with a CreationDate greater than this date value.
9375
9376 .PARAMETER IncludeC
9377
9378 Switch. Include any C$ shares in recursive searching (default ignore).
9379
9380 .PARAMETER IncludeAdmin
9381
9382 Switch. Include any ADMIN$ shares in recursive searching (default ignore).
9383
9384 .PARAMETER ExcludeFolders
9385
9386 Switch. Exclude folders from the search results.
9387
9388 .PARAMETER ExcludeHidden
9389
9390 Switch. Exclude hidden files and folders from the search results.
9391
9392 .PARAMETER CheckWriteAccess
9393
9394 Switch. Only returns files the current user has write access to.
9395
9396 .PARAMETER OutFile
9397
9398 Output results to a specified csv output file.
9399
9400 .PARAMETER NoClobber
9401
9402 Switch. Don't overwrite any existing output file.
9403
9404 .PARAMETER NoPing
9405
9406 Switch. Don't ping each host to ensure it's up before enumerating.
9407
9408 .PARAMETER Delay
9409
9410 Delay between enumerating hosts, defaults to 0
9411
9412 .PARAMETER Jitter
9413
9414 Jitter for the host delay, defaults to +/- 0.3
9415
9416 .PARAMETER Domain
9417
9418 Domain to query for machines, defaults to the current domain.
9419
9420 .PARAMETER DomainController
9421
9422 Domain controller to reflect LDAP queries through.
9423
9424 .PARAMETER SearchForest
9425
9426 Search all domains in the forest for target users instead of just
9427 a single domain.
9428
9429 .PARAMETER SearchSYSVOL
9430
9431 Switch. Search for login scripts on the SYSVOL of the primary DCs for each specified domain.
9432
9433 .PARAMETER Threads
9434
9435 The maximum concurrent threads to execute.
9436
9437 .PARAMETER UsePSDrive
9438
9439 Switch. Mount target remote path with temporary PSDrives.
9440
9441 .PARAMETER Credential
9442
9443 Credential to use to mount the PSDrive for searching.
9444
9445 .EXAMPLE
9446
9447 PS C:\> Invoke-FileFinder
9448
9449 Find readable files on the domain with 'pass', 'sensitive',
9450 'secret', 'admin', 'login', or 'unattend*.xml' in the name,
9451
9452 .EXAMPLE
9453
9454 PS C:\> Invoke-FileFinder -Domain testing
9455
9456 Find readable files on the 'testing' domain with 'pass', 'sensitive',
9457 'secret', 'admin', 'login', or 'unattend*.xml' in the name,
9458
9459 .EXAMPLE
9460
9461 PS C:\> Invoke-FileFinder -IncludeC
9462
9463 Find readable files on the domain with 'pass', 'sensitive',
9464 'secret', 'admin', 'login' or 'unattend*.xml' in the name,
9465 including C$ shares.
9466
9467 .EXAMPLE
9468
9469 PS C:\> Invoke-FileFinder -ShareList shares.txt -Terms accounts,ssn -OutFile out.csv
9470
9471 Enumerate a specified share list for files with 'accounts' or
9472 'ssn' in the name, and write everything to "out.csv"
9473
9474 .LINK
9475 http://www.harmj0y.net/blog/redteaming/file-server-triage-on-red-team-engagements/
9476
9477#>
9478
9479 [CmdletBinding()]
9480 param(
9481 [Parameter(Position=0,ValueFromPipeline=$True)]
9482 [Alias('Hosts')]
9483 [String[]]
9484 $ComputerName,
9485
9486 [ValidateScript({Test-Path -Path $_ })]
9487 [Alias('HostList')]
9488 [String]
9489 $ComputerFile,
9490
9491 [String]
9492 $ComputerFilter,
9493
9494 [String]
9495 $ComputerADSpath,
9496
9497 [ValidateScript({Test-Path -Path $_ })]
9498 [String]
9499 $ShareList,
9500
9501 [Switch]
9502 $OfficeDocs,
9503
9504 [Switch]
9505 $FreshEXEs,
9506
9507 [String[]]
9508 $Terms,
9509
9510 [ValidateScript({Test-Path -Path $_ })]
9511 [String]
9512 $TermList,
9513
9514 [String]
9515 $LastAccessTime,
9516
9517 [String]
9518 $LastWriteTime,
9519
9520 [String]
9521 $CreationTime,
9522
9523 [Switch]
9524 $IncludeC,
9525
9526 [Switch]
9527 $IncludeAdmin,
9528
9529 [Switch]
9530 $ExcludeFolders,
9531
9532 [Switch]
9533 $ExcludeHidden,
9534
9535 [Switch]
9536 $CheckWriteAccess,
9537
9538 [String]
9539 $OutFile,
9540
9541 [Switch]
9542 $NoClobber,
9543
9544 [Switch]
9545 $NoPing,
9546
9547 [UInt32]
9548 $Delay = 0,
9549
9550 [Double]
9551 $Jitter = .3,
9552
9553 [String]
9554 $Domain,
9555
9556 [String]
9557 $DomainController,
9558
9559 [Switch]
9560 $SearchForest,
9561
9562 [Switch]
9563 $SearchSYSVOL,
9564
9565 [ValidateRange(1,100)]
9566 [Int]
9567 $Threads,
9568
9569 [Switch]
9570 $UsePSDrive,
9571
9572 [System.Management.Automation.PSCredential]
9573 $Credential = [System.Management.Automation.PSCredential]::Empty
9574 )
9575
9576 begin {
9577 if ($PSBoundParameters['Debug']) {
9578 $DebugPreference = 'Continue'
9579 }
9580
9581 # random object for delay
9582 $RandNo = New-Object System.Random
9583
9584 Write-Verbose "[*] Running Invoke-FileFinder with delay of $Delay"
9585
9586 $Shares = @()
9587
9588 # figure out the shares we want to ignore
9589 [String[]] $ExcludedShares = @("C$", "ADMIN$")
9590
9591 # see if we're specifically including any of the normally excluded sets
9592 if ($IncludeC) {
9593 if ($IncludeAdmin) {
9594 $ExcludedShares = @()
9595 }
9596 else {
9597 $ExcludedShares = @("ADMIN$")
9598 }
9599 }
9600
9601 if ($IncludeAdmin) {
9602 if ($IncludeC) {
9603 $ExcludedShares = @()
9604 }
9605 else {
9606 $ExcludedShares = @("C$")
9607 }
9608 }
9609
9610 # delete any existing output file if it already exists
9611 if(!$NoClobber) {
9612 if ($OutFile -and (Test-Path -Path $OutFile)) { Remove-Item -Path $OutFile }
9613 }
9614
9615 # if there's a set of terms specified to search for
9616 if ($TermList) {
9617 ForEach ($Term in Get-Content -Path $TermList) {
9618 if (($Term -ne $Null) -and ($Term.trim() -ne '')) {
9619 $Terms += $Term
9620 }
9621 }
9622 }
9623
9624 if($Domain) {
9625 $TargetDomains = @($Domain)
9626 }
9627 elseif($SearchForest) {
9628 # get ALL the domains in the forest to search
9629 $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
9630 }
9631 else {
9632 # use the local domain
9633 $TargetDomains = @( (Get-NetDomain).name )
9634 }
9635
9636 # if we're hard-passed a set of shares
9637 if($ShareList) {
9638 ForEach ($Item in Get-Content -Path $ShareList) {
9639 if (($Item -ne $Null) -and ($Item.trim() -ne '')) {
9640 # exclude any "[tab]- commants", i.e. the output from Invoke-ShareFinder
9641 $Share = $Item.Split("`t")[0]
9642 $Shares += $Share
9643 }
9644 }
9645 }
9646 if($SearchSYSVOL) {
9647 ForEach ($Domain in $TargetDomains) {
9648 $DCSearchPath = "\\$Domain\SYSVOL\"
9649 Write-Verbose "[*] Adding share search path $DCSearchPath"
9650 $Shares += $DCSearchPath
9651 }
9652 if(!$Terms) {
9653 # search for interesting scripts on SYSVOL
9654 $Terms = @('.vbs', '.bat', '.ps1')
9655 }
9656 }
9657 else {
9658 # if we're using a host list, read the targets in and add them to the target list
9659 if($ComputerFile) {
9660 $ComputerName = Get-Content -Path $ComputerFile
9661 }
9662 else {
9663 [array]$ComputerName = @()
9664 ForEach ($Domain in $TargetDomains) {
9665 Write-Verbose "[*] Querying domain $Domain for hosts"
9666 $ComputerName += Get-NetComputer -Filter $ComputerFilter -ADSpath $ComputerADSpath -Domain $Domain -DomainController $DomainController
9667 }
9668 }
9669
9670 # remove any null target hosts, uniquify the list and shuffle it
9671 $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
9672 if($($ComputerName.Count) -eq 0) {
9673 throw "No hosts found!"
9674 }
9675 }
9676
9677 # script block that enumerates shares and files on a server
9678 $HostEnumBlock = {
9679 param($ComputerName, $Ping, $ExcludedShares, $Terms, $ExcludeFolders, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $OutFile, $UsePSDrive, $Credential)
9680
9681 Write-Verbose "ComputerName: $ComputerName"
9682 Write-Verbose "ExcludedShares: $ExcludedShares"
9683 $SearchShares = @()
9684
9685 if($ComputerName.StartsWith("\\")) {
9686 # if a share is passed as the server
9687 $SearchShares += $ComputerName
9688 }
9689 else {
9690 # if we're enumerating the shares on the target server first
9691 $Up = $True
9692 if($Ping) {
9693 $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
9694 }
9695 if($Up) {
9696 # get the shares for this host and display what we find
9697 $Shares = Get-NetShare -ComputerName $ComputerName
9698 ForEach ($Share in $Shares) {
9699
9700 $NetName = $Share.shi1_netname
9701 $Path = '\\'+$ComputerName+'\'+$NetName
9702
9703 # make sure we get a real share name back
9704 if (($NetName) -and ($NetName.trim() -ne '')) {
9705
9706 # skip this share if it's in the exclude list
9707 if ($ExcludedShares -NotContains $NetName.ToUpper()) {
9708 # check if the user has access to this path
9709 try {
9710 $Null = [IO.Directory]::GetFiles($Path)
9711 $SearchShares += $Path
9712 }
9713 catch {
9714 Write-Debug "[!] No access to $Path"
9715 }
9716 }
9717 }
9718 }
9719 }
9720 }
9721
9722 ForEach($Share in $SearchShares) {
9723 $SearchArgs = @{
9724 'Path' = $Share
9725 'Terms' = $Terms
9726 'OfficeDocs' = $OfficeDocs
9727 'FreshEXEs' = $FreshEXEs
9728 'LastAccessTime' = $LastAccessTime
9729 'LastWriteTime' = $LastWriteTime
9730 'CreationTime' = $CreationTime
9731 'ExcludeFolders' = $ExcludeFolders
9732 'ExcludeHidden' = $ExcludeHidden
9733 'CheckWriteAccess' = $CheckWriteAccess
9734 'OutFile' = $OutFile
9735 'UsePSDrive' = $UsePSDrive
9736 'Credential' = $Credential
9737 }
9738
9739 Find-InterestingFile @SearchArgs
9740 }
9741 }
9742 }
9743
9744 process {
9745
9746 if($Threads) {
9747 Write-Verbose "Using threading with threads = $Threads"
9748
9749 # if we're using threading, kick off the script block with Invoke-ThreadedFunction
9750 $ScriptParams = @{
9751 'Ping' = $(-not $NoPing)
9752 'ExcludedShares' = $ExcludedShares
9753 'Terms' = $Terms
9754 'ExcludeFolders' = $ExcludeFolders
9755 'OfficeDocs' = $OfficeDocs
9756 'ExcludeHidden' = $ExcludeHidden
9757 'FreshEXEs' = $FreshEXEs
9758 'CheckWriteAccess' = $CheckWriteAccess
9759 'OutFile' = $OutFile
9760 'UsePSDrive' = $UsePSDrive
9761 'Credential' = $Credential
9762 }
9763
9764 # kick off the threaded script block + arguments
9765 if($Shares) {
9766 # pass the shares as the hosts so the threaded function code doesn't have to be hacked up
9767 Invoke-ThreadedFunction -ComputerName $Shares -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams
9768 }
9769 else {
9770 Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams
9771 }
9772 }
9773
9774 else {
9775 if($Shares){
9776 $ComputerName = $Shares
9777 }
9778 elseif(-not $NoPing -and ($ComputerName.count -gt 1)) {
9779 # ping all hosts in parallel
9780 $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
9781 $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
9782 }
9783
9784 Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
9785 $Counter = 0
9786
9787 $ComputerName | Where-Object {$_} | ForEach-Object {
9788 Write-Verbose "Computer: $_"
9789 $Counter = $Counter + 1
9790
9791 # sleep for our semi-randomized interval
9792 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
9793
9794 Write-Verbose "[*] Enumerating server $_ ($Counter of $($ComputerName.count))"
9795
9796 Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $_, $False, $ExcludedShares, $Terms, $ExcludeFolders, $OfficeDocs, $ExcludeHidden, $FreshEXEs, $CheckWriteAccess, $OutFile, $UsePSDrive, $Credential
9797 }
9798 }
9799 }
9800}
9801
9802
9803function Find-LocalAdminAccess {
9804<#
9805 .SYNOPSIS
9806
9807 Finds machines on the local domain where the current user has
9808 local administrator access. Uses multithreading to
9809 speed up enumeration.
9810
9811 Author: @harmj0y
9812 License: BSD 3-Clause
9813
9814 .DESCRIPTION
9815
9816 This function finds the local domain name for a host using Get-NetDomain,
9817 queries the domain for all active machines with Get-NetComputer, then for
9818 each server it checks if the current user has local administrator
9819 access using Invoke-CheckLocalAdminAccess.
9820
9821 Idea stolen from the local_admin_search_enum post module in
9822 Metasploit written by:
9823 'Brandon McCann "zeknox" <bmccann[at]accuvant.com>'
9824 'Thomas McCarthy "smilingraccoon" <smilingraccoon[at]gmail.com>'
9825 'Royce Davis "r3dy" <rdavis[at]accuvant.com>'
9826
9827 .PARAMETER ComputerName
9828
9829 Host array to enumerate, passable on the pipeline.
9830
9831 .PARAMETER ComputerFile
9832
9833 File of hostnames/IPs to search.
9834
9835 .PARAMETER ComputerFilter
9836
9837 Host filter name to query AD for, wildcards accepted.
9838
9839 .PARAMETER ComputerADSpath
9840
9841 The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
9842 Useful for OU queries.
9843
9844 .PARAMETER NoPing
9845
9846 Switch. Don't ping each host to ensure it's up before enumerating.
9847
9848 .PARAMETER Delay
9849
9850 Delay between enumerating hosts, defaults to 0
9851
9852 .PARAMETER Jitter
9853
9854 Jitter for the host delay, defaults to +/- 0.3
9855
9856 .PARAMETER Domain
9857
9858 Domain to query for machines, defaults to the current domain.
9859
9860 .PARAMETER DomainController
9861
9862 Domain controller to reflect LDAP queries through.
9863
9864 .PARAMETER SearchForest
9865
9866 Switch. Search all domains in the forest for target users instead of just
9867 a single domain.
9868
9869 .PARAMETER Threads
9870
9871 The maximum concurrent threads to execute.
9872
9873 .EXAMPLE
9874
9875 PS C:\> Find-LocalAdminAccess
9876
9877 Find machines on the local domain where the current user has local
9878 administrator access.
9879
9880 .EXAMPLE
9881
9882 PS C:\> Find-LocalAdminAccess -Threads 10
9883
9884 Multi-threaded access hunting, replaces Find-LocalAdminAccessThreaded.
9885
9886 .EXAMPLE
9887
9888 PS C:\> Find-LocalAdminAccess -Domain testing
9889
9890 Find machines on the 'testing' domain where the current user has
9891 local administrator access.
9892
9893 .EXAMPLE
9894
9895 PS C:\> Find-LocalAdminAccess -ComputerFile hosts.txt
9896
9897 Find which machines in the host list the current user has local
9898 administrator access.
9899
9900 .LINK
9901
9902 https://github.com/rapid7/metasploit-framework/blob/master/modules/post/windows/gather/local_admin_search_enum.rb
9903 http://www.harmj0y.net/blog/penetesting/finding-local-admin-with-the-veil-framework/
9904#>
9905
9906 [CmdletBinding()]
9907 param(
9908 [Parameter(Position=0,ValueFromPipeline=$True)]
9909 [Alias('Hosts')]
9910 [String[]]
9911 $ComputerName,
9912
9913 [ValidateScript({Test-Path -Path $_ })]
9914 [Alias('HostList')]
9915 [String]
9916 $ComputerFile,
9917
9918 [String]
9919 $ComputerFilter,
9920
9921 [String]
9922 $ComputerADSpath,
9923
9924 [Switch]
9925 $NoPing,
9926
9927 [UInt32]
9928 $Delay = 0,
9929
9930 [Double]
9931 $Jitter = .3,
9932
9933 [String]
9934 $Domain,
9935
9936 [String]
9937 $DomainController,
9938
9939 [Switch]
9940 $SearchForest,
9941
9942 [ValidateRange(1,100)]
9943 [Int]
9944 $Threads
9945 )
9946
9947 begin {
9948 if ($PSBoundParameters['Debug']) {
9949 $DebugPreference = 'Continue'
9950 }
9951
9952 # random object for delay
9953 $RandNo = New-Object System.Random
9954
9955 Write-Verbose "[*] Running Find-LocalAdminAccess with delay of $Delay"
9956
9957 if(!$ComputerName) {
9958 if($Domain) {
9959 $TargetDomains = @($Domain)
9960 }
9961 elseif($SearchForest) {
9962 # get ALL the domains in the forest to search
9963 $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
9964 }
9965 else {
9966 # use the local domain
9967 $TargetDomains = @( (Get-NetDomain).name )
9968 }
9969
9970 # if we're using a host list, read the targets in and add them to the target list
9971 if($ComputerFile) {
9972 $ComputerName = Get-Content -Path $ComputerFile
9973 }
9974 else {
9975 [array]$ComputerName = @()
9976 ForEach ($Domain in $TargetDomains) {
9977 Write-Verbose "[*] Querying domain $Domain for hosts"
9978 $ComputerName += Get-NetComputer -Filter $ComputerFilter -ADSpath $ComputerADSpath -Domain $Domain -DomainController $DomainController
9979 }
9980 }
9981
9982 # remove any null target hosts, uniquify the list and shuffle it
9983 $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
9984 if($($ComputerName.Count) -eq 0) {
9985 throw "No hosts found!"
9986 }
9987 }
9988
9989 # script block that enumerates a server
9990 $HostEnumBlock = {
9991 param($ComputerName, $Ping)
9992
9993 $Up = $True
9994 if($Ping) {
9995 $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
9996 }
9997 if($Up) {
9998 # check if the current user has local admin access to this server
9999 $Access = Invoke-CheckLocalAdminAccess -ComputerName $ComputerName
10000 if ($Access) {
10001 $ComputerName
10002 }
10003 }
10004 }
10005
10006 }
10007
10008 process {
10009
10010 if($Threads) {
10011 Write-Verbose "Using threading with threads = $Threads"
10012
10013 # if we're using threading, kick off the script block with Invoke-ThreadedFunction
10014 $ScriptParams = @{
10015 'Ping' = $(-not $NoPing)
10016 }
10017
10018 # kick off the threaded script block + arguments
10019 Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams
10020 }
10021
10022 else {
10023 if(-not $NoPing -and ($ComputerName.count -ne 1)) {
10024 # ping all hosts in parallel
10025 $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
10026 $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
10027 }
10028
10029 Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
10030 $Counter = 0
10031
10032 ForEach ($Computer in $ComputerName) {
10033
10034 $Counter = $Counter + 1
10035
10036 # sleep for our semi-randomized interval
10037 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
10038
10039 Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
10040 Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $OutFile, $DomainSID, $TrustGroupsSIDs
10041 }
10042 }
10043 }
10044}
10045
10046
10047function Get-ExploitableSystem {
10048<#
10049 .Synopsis
10050
10051 This module will query Active Directory for the hostname, OS version, and service pack level
10052 for each computer account. That information is then cross-referenced against a list of common
10053 Metasploit exploits that can be used during penetration testing.
10054
10055 .DESCRIPTION
10056
10057 This module will query Active Directory for the hostname, OS version, and service pack level
10058 for each computer account. That information is then cross-referenced against a list of common
10059 Metasploit exploits that can be used during penetration testing. The script filters out disabled
10060 domain computers and provides the computer's last logon time to help determine if it's been
10061 decommissioned. Also, since the script uses data tables to output affected systems the results
10062 can be easily piped to other commands such as test-connection or a Export-Csv.
10063
10064 .PARAMETER ComputerName
10065
10066 Return computers with a specific name, wildcards accepted.
10067
10068 .PARAMETER SPN
10069
10070 Return computers with a specific service principal name, wildcards accepted.
10071
10072 .PARAMETER OperatingSystem
10073
10074 Return computers with a specific operating system, wildcards accepted.
10075
10076 .PARAMETER ServicePack
10077
10078 Return computers with a specific service pack, wildcards accepted.
10079
10080 .PARAMETER Filter
10081
10082 A customized ldap filter string to use, e.g. "(description=*admin*)"
10083
10084 .PARAMETER Ping
10085
10086 Switch. Ping each host to ensure it's up before enumerating.
10087
10088 .PARAMETER Domain
10089
10090 The domain to query for computers, defaults to the current domain.
10091
10092 .PARAMETER DomainController
10093
10094 Domain controller to reflect LDAP queries through.
10095
10096 .PARAMETER ADSpath
10097
10098 The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
10099 Useful for OU queries.
10100
10101 .PARAMETER Unconstrained
10102
10103 Switch. Return computer objects that have unconstrained delegation.
10104
10105 .PARAMETER PageSize
10106
10107 The PageSize to set for the LDAP searcher object.
10108
10109 .EXAMPLE
10110
10111 The example below shows the standard command usage. Disabled system are excluded by default, but
10112 the "LastLgon" column can be used to determine which systems are live. Usually, if a system hasn't
10113 logged on for two or more weeks it's been decommissioned.
10114 PS C:\> Get-ExploitableSystem -DomainController 192.168.1.1 -Credential demo.com\user | Format-Table -AutoSize
10115 [*] Grabbing computer accounts from Active Directory...
10116 [*] Loading exploit list for critical missing patches...
10117 [*] Checking computers for vulnerable OS and SP levels...
10118 [+] Found 5 potentially vulnerable systems!
10119 ComputerName OperatingSystem ServicePack LastLogon MsfModule CVE
10120 ------------ --------------- ----------- --------- --------- ---
10121 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....
10122 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....
10123 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....
10124 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....
10125 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....
10126 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....
10127 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....
10128 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....
10129 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....
10130 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....
10131 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....
10132 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....
10133 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....
10134 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....
10135
10136 .EXAMPLE
10137
10138 PS C:\> Get-ExploitableSystem | Export-Csv c:\temp\output.csv -NoTypeInformation
10139
10140 How to write the output to a csv file.
10141
10142 .EXAMPLE
10143
10144 PS C:\> Get-ExploitableSystem -Domain testlab.local -Ping
10145
10146 Return a set of live hosts from the testlab.local domain
10147
10148 .LINK
10149
10150 http://www.netspi.com
10151 https://github.com/nullbind/Powershellery/blob/master/Stable-ish/ADS/Get-ExploitableSystems.psm1
10152
10153 .NOTES
10154
10155 Author: Scott Sutherland - 2015, NetSPI
10156 Modifications to integrate into PowerView by @harmj0y
10157 Version: Get-ExploitableSystem.psm1 v1.1
10158 Comments: The technique used to query LDAP was based on the "Get-AuditDSComputerAccount"
10159 function found in Carols Perez's PoshSec-Mod project. The general idea is based off of
10160 Will Schroeder's "Invoke-FindVulnSystems" function from the PowerView toolkit.
10161#>
10162 [CmdletBinding()]
10163 Param(
10164 [Parameter(ValueFromPipeline=$True)]
10165 [Alias('HostName')]
10166 [String]
10167 $ComputerName = '*',
10168
10169 [String]
10170 $SPN,
10171
10172 [String]
10173 $OperatingSystem = '*',
10174
10175 [String]
10176 $ServicePack = '*',
10177
10178 [String]
10179 $Filter,
10180
10181 [Switch]
10182 $Ping,
10183
10184 [String]
10185 $Domain,
10186
10187 [String]
10188 $DomainController,
10189
10190 [String]
10191 $ADSpath,
10192
10193 [Switch]
10194 $Unconstrained,
10195
10196 [ValidateRange(1,10000)]
10197 [Int]
10198 $PageSize = 200
10199 )
10200
10201 Write-Verbose "[*] Grabbing computer accounts from Active Directory..."
10202
10203 # Create data table for hostnames, os, and service packs from LDAP
10204 $TableAdsComputers = New-Object System.Data.DataTable
10205 $Null = $TableAdsComputers.Columns.Add('Hostname')
10206 $Null = $TableAdsComputers.Columns.Add('OperatingSystem')
10207 $Null = $TableAdsComputers.Columns.Add('ServicePack')
10208 $Null = $TableAdsComputers.Columns.Add('LastLogon')
10209
10210 Get-NetComputer -FullData @PSBoundParameters | ForEach-Object {
10211
10212 $CurrentHost = $_.dnshostname
10213 $CurrentOs = $_.operatingsystem
10214 $CurrentSp = $_.operatingsystemservicepack
10215 $CurrentLast = $_.lastlogon
10216 $CurrentUac = $_.useraccountcontrol
10217
10218 $CurrentUacBin = [convert]::ToString($_.useraccountcontrol,2)
10219
10220 # Check the 2nd to last value to determine if its disabled
10221 $DisableOffset = $CurrentUacBin.Length - 2
10222 $CurrentDisabled = $CurrentUacBin.Substring($DisableOffset,1)
10223
10224 # Add computer to list if it's enabled
10225 if ($CurrentDisabled -eq 0) {
10226 # Add domain computer to data table
10227 $Null = $TableAdsComputers.Rows.Add($CurrentHost,$CurrentOS,$CurrentSP,$CurrentLast)
10228 }
10229 }
10230
10231 # Status user
10232 Write-Verbose "[*] Loading exploit list for critical missing patches..."
10233
10234 # ----------------------------------------------------------------
10235 # Setup data table for list of msf exploits
10236 # ----------------------------------------------------------------
10237
10238 # Create data table for list of patches levels with a MSF exploit
10239 $TableExploits = New-Object System.Data.DataTable
10240 $Null = $TableExploits.Columns.Add('OperatingSystem')
10241 $Null = $TableExploits.Columns.Add('ServicePack')
10242 $Null = $TableExploits.Columns.Add('MsfModule')
10243 $Null = $TableExploits.Columns.Add('CVE')
10244
10245 # Add exploits to data table
10246 $Null = $TableExploits.Rows.Add("Windows 7","","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
10247 $Null = $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
10248 $Null = $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
10249 $Null = $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109")
10250 $Null = $TableExploits.Rows.Add("Windows Server 2000","Server Pack 1","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
10251 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
10252 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
10253 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109")
10254 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/")
10255 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 2","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
10256 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
10257 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
10258 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109")
10259 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 3","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
10260 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
10261 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
10262 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/dcerpc/ms07_029_msdns_zonename","http://www.cvedetails.com/cve/2007-1748")
10263 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/")
10264 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
10265 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms06_066_nwapi","http://www.cvedetails.com/cve/2006-4688")
10266 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms06_070_wkssvc","http://www.cvedetails.com/cve/2006-4691")
10267 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
10268 $Null = $TableExploits.Rows.Add("Windows Server 2000","Service Pack 4","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
10269 $Null = $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
10270 $Null = $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
10271 $Null = $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/iis/ms03_007_ntdll_webdav","http://www.cvedetails.com/cve/2003-0109")
10272 $Null = $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/smb/ms05_039_pnp","http://www.cvedetails.com/cve/2005-1983")
10273 $Null = $TableExploits.Rows.Add("Windows Server 2000","","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
10274 $Null = $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/dcerpc/ms07_029_msdns_zonename","http://www.cvedetails.com/cve/2007-1748")
10275 $Null = $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
10276 $Null = $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/smb/ms06_066_nwapi","http://www.cvedetails.com/cve/2006-4688")
10277 $Null = $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
10278 $Null = $TableExploits.Rows.Add("Windows Server 2003","Server Pack 1","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
10279 $Null = $TableExploits.Rows.Add("Windows Server 2003","Service Pack 2","exploit/windows/dcerpc/ms07_029_msdns_zonename","http://www.cvedetails.com/cve/2007-1748")
10280 $Null = $TableExploits.Rows.Add("Windows Server 2003","Service Pack 2","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
10281 $Null = $TableExploits.Rows.Add("Windows Server 2003","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
10282 $Null = $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
10283 $Null = $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
10284 $Null = $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
10285 $Null = $TableExploits.Rows.Add("Windows Server 2003","","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
10286 $Null = $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
10287 $Null = $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/")
10288 $Null = $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
10289 $Null = $TableExploits.Rows.Add("Windows Server 2003 R2","","exploit/windows/wins/ms04_045_wins","http://www.cvedetails.com/cve/2004-1080/")
10290 $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")
10291 $Null = $TableExploits.Rows.Add("Windows Server 2008","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
10292 $Null = $TableExploits.Rows.Add("Windows Server 2008","","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
10293 $Null = $TableExploits.Rows.Add("Windows Server 2008","","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103")
10294 $Null = $TableExploits.Rows.Add("Windows Server 2008","","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
10295 $Null = $TableExploits.Rows.Add("Windows Server 2008 R2","","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
10296 $Null = $TableExploits.Rows.Add("Windows Vista","Server Pack 1","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
10297 $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")
10298 $Null = $TableExploits.Rows.Add("Windows Vista","Server Pack 1","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
10299 $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")
10300 $Null = $TableExploits.Rows.Add("Windows Vista","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
10301 $Null = $TableExploits.Rows.Add("Windows Vista","","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
10302 $Null = $TableExploits.Rows.Add("Windows Vista","","exploit/windows/smb/ms09_050_smb2_negotiate_func_index","http://www.cvedetails.com/cve/2009-3103")
10303 $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
10304 $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
10305 $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/smb/ms04_011_lsass","http://www.cvedetails.com/cve/2003-0533/")
10306 $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/smb/ms05_039_pnp","http://www.cvedetails.com/cve/2005-1983")
10307 $Null = $TableExploits.Rows.Add("Windows XP","Server Pack 1","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
10308 $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
10309 $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
10310 $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms06_066_nwapi","http://www.cvedetails.com/cve/2006-4688")
10311 $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms06_070_wkssvc","http://www.cvedetails.com/cve/2006-4691")
10312 $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
10313 $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 2","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
10314 $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 3","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
10315 $Null = $TableExploits.Rows.Add("Windows XP","Service Pack 3","exploit/windows/smb/ms10_061_spoolss","http://www.cvedetails.com/cve/2010-2729")
10316 $Null = $TableExploits.Rows.Add("Windows XP","","exploit/windows/dcerpc/ms03_026_dcom","http://www.cvedetails.com/cve/2003-0352/")
10317 $Null = $TableExploits.Rows.Add("Windows XP","","exploit/windows/dcerpc/ms05_017_msmq","http://www.cvedetails.com/cve/2005-0059")
10318 $Null = $TableExploits.Rows.Add("Windows XP","","exploit/windows/smb/ms06_040_netapi","http://www.cvedetails.com/cve/2006-3439")
10319 $Null = $TableExploits.Rows.Add("Windows XP","","exploit/windows/smb/ms08_067_netapi","http://www.cvedetails.com/cve/2008-4250")
10320
10321 # Status user
10322 Write-Verbose "[*] Checking computers for vulnerable OS and SP levels..."
10323
10324 # ----------------------------------------------------------------
10325 # Setup data table to store vulnerable systems
10326 # ----------------------------------------------------------------
10327
10328 # Create data table to house vulnerable server list
10329 $TableVulnComputers = New-Object System.Data.DataTable
10330 $Null = $TableVulnComputers.Columns.Add('ComputerName')
10331 $Null = $TableVulnComputers.Columns.Add('OperatingSystem')
10332 $Null = $TableVulnComputers.Columns.Add('ServicePack')
10333 $Null = $TableVulnComputers.Columns.Add('LastLogon')
10334 $Null = $TableVulnComputers.Columns.Add('MsfModule')
10335 $Null = $TableVulnComputers.Columns.Add('CVE')
10336
10337 # Iterate through each exploit
10338 $TableExploits | ForEach-Object {
10339
10340 $ExploitOS = $_.OperatingSystem
10341 $ExploitSP = $_.ServicePack
10342 $ExploitMsf = $_.MsfModule
10343 $ExploitCVE = $_.CVE
10344
10345 # Iterate through each ADS computer
10346 $TableAdsComputers | ForEach-Object {
10347
10348 $AdsHostname = $_.Hostname
10349 $AdsOS = $_.OperatingSystem
10350 $AdsSP = $_.ServicePack
10351 $AdsLast = $_.LastLogon
10352
10353 # Add exploitable systems to vul computers data table
10354 if ($AdsOS -like "$ExploitOS*" -and $AdsSP -like "$ExploitSP" ) {
10355 # Add domain computer to data table
10356 $Null = $TableVulnComputers.Rows.Add($AdsHostname,$AdsOS,$AdsSP,$AdsLast,$ExploitMsf,$ExploitCVE)
10357 }
10358 }
10359 }
10360
10361 # Display results
10362 $VulnComputer = $TableVulnComputers | Select-Object ComputerName -Unique | Measure-Object
10363 $VulnComputerCount = $VulnComputer.Count
10364 if ($VulnComputer.Count -gt 0) {
10365 # Return vulnerable server list order with some hack date casting
10366 Write-Verbose "[+] Found $VulnComputerCount potentially vulnerable systems!"
10367 $TableVulnComputers | Sort-Object { $_.lastlogon -as [datetime]} -Descending
10368 }
10369 else {
10370 Write-Verbose "[-] No vulnerable systems were found."
10371 }
10372}
10373
10374
10375function Invoke-EnumerateLocalAdmin {
10376<#
10377 .SYNOPSIS
10378
10379 This function queries the domain for all active machines with
10380 Get-NetComputer, then for each server it queries the local
10381 Administrators with Get-NetLocalGroup.
10382
10383 Author: @harmj0y
10384 License: BSD 3-Clause
10385
10386 .PARAMETER ComputerName
10387
10388 Host array to enumerate, passable on the pipeline.
10389
10390 .PARAMETER ComputerFile
10391
10392 File of hostnames/IPs to search.
10393
10394 .PARAMETER ComputerFilter
10395
10396 Host filter name to query AD for, wildcards accepted.
10397
10398 .PARAMETER ComputerADSpath
10399
10400 The LDAP source to search through for hosts, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
10401 Useful for OU queries.
10402
10403 .PARAMETER NoPing
10404
10405 Switch. Don't ping each host to ensure it's up before enumerating.
10406
10407 .PARAMETER Delay
10408
10409 Delay between enumerating hosts, defaults to 0
10410
10411 .PARAMETER Jitter
10412
10413 Jitter for the host delay, defaults to +/- 0.3
10414
10415 .PARAMETER OutFile
10416
10417 Output results to a specified csv output file.
10418
10419 .PARAMETER NoClobber
10420
10421 Switch. Don't overwrite any existing output file.
10422
10423 .PARAMETER TrustGroups
10424
10425 Switch. Only return results that are not part of the local machine
10426 or the machine's domain. Old Invoke-EnumerateLocalTrustGroup
10427 functionality.
10428
10429 .PARAMETER Domain
10430
10431 Domain to query for machines, defaults to the current domain.
10432
10433 .PARAMETER DomainController
10434
10435 Domain controller to reflect LDAP queries through.
10436
10437 .PARAMETER SearchForest
10438
10439 Switch. Search all domains in the forest for target users instead of just
10440 a single domain.
10441
10442 .PARAMETER Threads
10443
10444 The maximum concurrent threads to execute.
10445
10446 .EXAMPLE
10447
10448 PS C:\> Invoke-EnumerateLocalAdmin
10449
10450 Enumerates the members of local administrators for all machines
10451 in the current domain.
10452
10453 .EXAMPLE
10454
10455 PS C:\> Invoke-EnumerateLocalAdmin -Threads 10
10456
10457 Threaded local admin enumeration, replaces Invoke-EnumerateLocalAdminThreaded
10458
10459 .LINK
10460
10461 http://blog.harmj0y.net/
10462#>
10463
10464 [CmdletBinding()]
10465 param(
10466 [Parameter(Position=0,ValueFromPipeline=$True)]
10467 [Alias('Hosts')]
10468 [String[]]
10469 $ComputerName,
10470
10471 [ValidateScript({Test-Path -Path $_ })]
10472 [Alias('HostList')]
10473 [String]
10474 $ComputerFile,
10475
10476 [String]
10477 $ComputerFilter,
10478
10479 [String]
10480 $ComputerADSpath,
10481
10482 [Switch]
10483 $NoPing,
10484
10485 [UInt32]
10486 $Delay = 0,
10487
10488 [Double]
10489 $Jitter = .3,
10490
10491 [String]
10492 $OutFile,
10493
10494 [Switch]
10495 $NoClobber,
10496
10497 [Switch]
10498 $TrustGroups,
10499
10500 [String]
10501 $Domain,
10502
10503 [String]
10504 $DomainController,
10505
10506 [Switch]
10507 $SearchForest,
10508
10509 [ValidateRange(1,100)]
10510 [Int]
10511 $Threads
10512 )
10513
10514 begin {
10515 if ($PSBoundParameters['Debug']) {
10516 $DebugPreference = 'Continue'
10517 }
10518
10519 # random object for delay
10520 $RandNo = New-Object System.Random
10521
10522 Write-Verbose "[*] Running Invoke-EnumerateLocalAdmin with delay of $Delay"
10523
10524 if(!$ComputerName) {
10525
10526 if($Domain) {
10527 $TargetDomains = @($Domain)
10528 }
10529 elseif($SearchForest) {
10530 # get ALL the domains in the forest to search
10531 $TargetDomains = Get-NetForestDomain | ForEach-Object { $_.Name }
10532 }
10533 else {
10534 # use the local domain
10535 $TargetDomains = @( (Get-NetDomain).name )
10536 }
10537
10538 # if we're using a host list, read the targets in and add them to the target list
10539 if($ComputerFile) {
10540 $ComputerName = Get-Content -Path $ComputerFile
10541 }
10542 else {
10543 [array]$ComputerName = @()
10544 ForEach ($Domain in $TargetDomains) {
10545 Write-Verbose "[*] Querying domain $Domain for hosts"
10546 $ComputerName += Get-NetComputer -Filter $ComputerFilter -ADSpath $ComputerADSpath -Domain $Domain -DomainController $DomainController
10547 }
10548 }
10549
10550 # remove any null target hosts, uniquify the list and shuffle it
10551 $ComputerName = $ComputerName | Where-Object { $_ } | Sort-Object -Unique | Sort-Object { Get-Random }
10552 if($($ComputerName.Count) -eq 0) {
10553 throw "No hosts found!"
10554 }
10555 }
10556
10557 # delete any existing output file if it already exists
10558 if(!$NoClobber) {
10559 if ($OutFile -and (Test-Path -Path $OutFile)) { Remove-Item -Path $OutFile }
10560 }
10561
10562 if($TrustGroups) {
10563
10564 Write-Verbose "Determining domain trust groups"
10565
10566 # find all group names that have one or more users in another domain
10567 $TrustGroupNames = Find-ForeignGroup -Domain $Domain -DomainController $DomainController | ForEach-Object { $_.GroupName } | Sort-Object -Unique
10568
10569 $TrustGroupsSIDs = $TrustGroupNames | ForEach-Object {
10570 # ignore the builtin administrators group for a DC (S-1-5-32-544)
10571 # TODO: ignore all default built in sids?
10572 Get-NetGroup -Domain $Domain -DomainController $DomainController -GroupName $_ -FullData | Where-Object { $_.objectsid -notmatch "S-1-5-32-544" } | ForEach-Object { $_.objectsid }
10573 }
10574
10575 # query for the primary domain controller so we can extract the domain SID for filtering
10576 $DomainSID = Get-DomainSID -Domain $Domain
10577 }
10578
10579 # script block that enumerates a server
10580 $HostEnumBlock = {
10581 param($ComputerName, $Ping, $OutFile, $DomainSID, $TrustGroupsSIDs)
10582
10583 # optionally check if the server is up first
10584 $Up = $True
10585 if($Ping) {
10586 $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
10587 }
10588 if($Up) {
10589 # grab the users for the local admins on this server
10590 $LocalAdmins = Get-NetLocalGroup -ComputerName $ComputerName
10591
10592 # if we just want to return cross-trust users
10593 if($DomainSID -and $TrustGroupSIDS) {
10594 # get the local machine SID
10595 $LocalSID = ($LocalAdmins | Where-Object { $_.SID -match '.*-500$' }).SID -replace "-500$"
10596
10597 # filter out accounts that begin with the machine SID and domain SID
10598 # but preserve any groups that have users across a trust ($TrustGroupSIDS)
10599 $LocalAdmins = $LocalAdmins | Where-Object { ($TrustGroupsSIDs -contains $_.SID) -or ((-not $_.SID.startsWith($LocalSID)) -and (-not $_.SID.startsWith($DomainSID))) }
10600 }
10601
10602 if($LocalAdmins -and ($LocalAdmins.Length -ne 0)) {
10603 # output the results to a csv if specified
10604 if($OutFile) {
10605 $LocalAdmins | Export-PowerViewCSV -OutFile $OutFile
10606 }
10607 else {
10608 # otherwise return the user objects
10609 $LocalAdmins
10610 }
10611 }
10612 else {
10613 Write-Verbose "[!] No users returned from $Server"
10614 }
10615 }
10616 }
10617
10618 }
10619
10620 process {
10621
10622 if($Threads) {
10623 Write-Verbose "Using threading with threads = $Threads"
10624
10625 # if we're using threading, kick off the script block with Invoke-ThreadedFunction
10626 $ScriptParams = @{
10627 'Ping' = $(-not $NoPing)
10628 'OutFile' = $OutFile
10629 'DomainSID' = $DomainSID
10630 'TrustGroupsSIDs' = $TrustGroupsSIDs
10631 }
10632
10633 # kick off the threaded script block + arguments
10634 Invoke-ThreadedFunction -ComputerName $ComputerName -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParams
10635 }
10636
10637 else {
10638 if(-not $NoPing -and ($ComputerName.count -ne 1)) {
10639 # ping all hosts in parallel
10640 $Ping = {param($ComputerName) if(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet -ErrorAction Stop){$ComputerName}}
10641 $ComputerName = Invoke-ThreadedFunction -NoImports -ComputerName $ComputerName -ScriptBlock $Ping -Threads 100
10642 }
10643
10644 Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
10645 $Counter = 0
10646
10647 ForEach ($Computer in $ComputerName) {
10648
10649 $Counter = $Counter + 1
10650
10651 # sleep for our semi-randomized interval
10652 Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
10653
10654 Write-Verbose "[*] Enumerating server $Computer ($Counter of $($ComputerName.count))"
10655 Invoke-Command -ScriptBlock $HostEnumBlock -ArgumentList $Computer, $False, $OutFile, $DomainSID, $TrustGroupsSIDs
10656 }
10657 }
10658 }
10659}
10660
10661
10662########################################################
10663#
10664# Domain trust functions below.
10665#
10666########################################################
10667
10668function Get-NetDomainTrust {
10669<#
10670 .SYNOPSIS
10671
10672 Return all domain trusts for the current domain or
10673 a specified domain.
10674
10675 .PARAMETER Domain
10676
10677 The domain whose trusts to enumerate, defaults to the current domain.
10678
10679 .PARAMETER DomainController
10680
10681 Domain controller to reflect LDAP queries through.
10682
10683 .PARAMETER LDAP
10684
10685 Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections.
10686 More likely to get around network segmentation, but not as accurate.
10687
10688 .PARAMETER PageSize
10689
10690 The PageSize to set for the LDAP searcher object.
10691
10692 .EXAMPLE
10693
10694 PS C:\> Get-NetDomainTrust
10695
10696 Return domain trusts for the current domain.
10697
10698 .EXAMPLE
10699
10700 PS C:\> Get-NetDomainTrust -Domain "prod.testlab.local"
10701
10702 Return domain trusts for the "prod.testlab.local" domain.
10703
10704 .EXAMPLE
10705
10706 PS C:\> Get-NetDomainTrust -Domain "prod.testlab.local" -DomainController "PRIMARY.testlab.local"
10707
10708 Return domain trusts for the "prod.testlab.local" domain, reflecting
10709 queries through the "Primary.testlab.local" domain controller
10710#>
10711
10712 [CmdletBinding()]
10713 param(
10714 [Parameter(Position=0,ValueFromPipeline=$True)]
10715 [String]
10716 $Domain = (Get-NetDomain).Name,
10717
10718 [String]
10719 $DomainController,
10720
10721 [Switch]
10722 $LDAP,
10723
10724 [ValidateRange(1,10000)]
10725 [Int]
10726 $PageSize = 200
10727 )
10728
10729 process {
10730 if($LDAP -or $DomainController) {
10731
10732 $TrustSearcher = Get-DomainSearcher -Domain $Domain -DomainController $DomainController -PageSize $PageSize
10733
10734 if($TrustSearcher) {
10735
10736 $TrustSearcher.filter = '(&(objectClass=trustedDomain))'
10737
10738 $TrustSearcher.FindAll() | Where-Object {$_} | ForEach-Object {
10739 $Props = $_.Properties
10740 $DomainTrust = New-Object PSObject
10741 $TrustAttrib = Switch ($Props.trustattributes)
10742 {
10743 0x001 { "non_transitive" }
10744 0x002 { "uplevel_only" }
10745 0x004 { "quarantined_domain" }
10746 0x008 { "forest_transitive" }
10747 0x010 { "cross_organization" }
10748 0x020 { "within_forest" }
10749 0x040 { "treat_as_external" }
10750 0x080 { "trust_uses_rc4_encryption" }
10751 0x100 { "trust_uses_aes_keys" }
10752 Default {
10753 Write-Warning "Unknown trust attribute: $($Props.trustattributes)";
10754 "$($Props.trustattributes)";
10755 }
10756 }
10757 $Direction = Switch ($Props.trustdirection) {
10758 0 { "Disabled" }
10759 1 { "Inbound" }
10760 2 { "Outbound" }
10761 3 { "Bidirectional" }
10762 }
10763 $ObjectGuid = New-Object Guid @(,$Props.objectguid[0])
10764 $DomainTrust | Add-Member Noteproperty 'SourceName' $Domain
10765 $DomainTrust | Add-Member Noteproperty 'TargetName' $Props.name[0]
10766 $DomainTrust | Add-Member Noteproperty 'ObjectGuid' "{$ObjectGuid}"
10767 $DomainTrust | Add-Member Noteproperty 'TrustType' "$TrustAttrib"
10768 $DomainTrust | Add-Member Noteproperty 'TrustDirection' "$Direction"
10769 $DomainTrust
10770 }
10771 }
10772 }
10773
10774 else {
10775 # if we're using direct domain connections
10776 $FoundDomain = Get-NetDomain -Domain $Domain
10777
10778 if($FoundDomain) {
10779 (Get-NetDomain -Domain $Domain).GetAllTrustRelationships()
10780 }
10781 }
10782 }
10783}
10784
10785
10786function Get-NetForestTrust {
10787<#
10788 .SYNOPSIS
10789
10790 Return all trusts for the current forest.
10791
10792 .PARAMETER Forest
10793
10794 Return trusts for the specified forest.
10795
10796 .EXAMPLE
10797
10798 PS C:\> Get-NetForestTrust
10799
10800 Return current forest trusts.
10801
10802 .EXAMPLE
10803
10804 PS C:\> Get-NetForestTrust -Forest "test"
10805
10806 Return trusts for the "test" forest.
10807#>
10808
10809 [CmdletBinding()]
10810 param(
10811 [Parameter(Position=0,ValueFromPipeline=$True)]
10812 [String]
10813 $Forest
10814 )
10815
10816 process {
10817 $FoundForest = Get-NetForest -Forest $Forest
10818 if($FoundForest) {
10819 $FoundForest.GetAllTrustRelationships()
10820 }
10821 }
10822}
10823
10824
10825function Find-ForeignUser {
10826<#
10827 .SYNOPSIS
10828
10829 Enumerates users who are in groups outside of their
10830 principal domain. The -Recurse option will try to map all
10831 transitive domain trust relationships and enumerate all
10832 users who are in groups outside of their principal domain.
10833
10834 .PARAMETER UserName
10835
10836 Username to filter results for, wildcards accepted.
10837
10838 .PARAMETER Domain
10839
10840 Domain to query for users, defaults to the current domain.
10841
10842 .PARAMETER DomainController
10843
10844 Domain controller to reflect LDAP queries through.
10845
10846 .PARAMETER LDAP
10847
10848 Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections.
10849 More likely to get around network segmentation, but not as accurate.
10850
10851 .PARAMETER Recurse
10852
10853 Switch. Enumerate all user trust groups from all reachable domains recursively.
10854
10855 .PARAMETER PageSize
10856
10857 The PageSize to set for the LDAP searcher object.
10858
10859 .LINK
10860
10861 http://blog.harmj0y.net/
10862#>
10863
10864 [CmdletBinding()]
10865 param(
10866 [String]
10867 $UserName,
10868
10869 [String]
10870 $Domain,
10871
10872 [String]
10873 $DomainController,
10874
10875 [Switch]
10876 $LDAP,
10877
10878 [Switch]
10879 $Recurse,
10880
10881 [ValidateRange(1,10000)]
10882 [Int]
10883 $PageSize = 200
10884 )
10885
10886 function Get-ForeignUser {
10887 # helper used to enumerate users who are in groups outside of their principal domain
10888 param(
10889 [String]
10890 $UserName,
10891
10892 [String]
10893 $Domain,
10894
10895 [String]
10896 $DomainController,
10897
10898 [ValidateRange(1,10000)]
10899 [Int]
10900 $PageSize = 200
10901 )
10902
10903 if ($Domain) {
10904 # get the domain name into distinguished form
10905 $DistinguishedDomainName = "DC=" + $Domain -replace '\.',',DC='
10906 }
10907 else {
10908 $DistinguishedDomainName = [String] ([adsi]'').distinguishedname
10909 $Domain = $DistinguishedDomainName -replace 'DC=','' -replace ',','.'
10910 }
10911
10912 Get-NetUser -Domain $Domain -DomainController $DomainController -UserName $UserName -PageSize $PageSize | Where-Object {$_.memberof} | ForEach-Object {
10913 ForEach ($Membership in $_.memberof) {
10914 $Index = $Membership.IndexOf("DC=")
10915 if($Index) {
10916
10917 $GroupDomain = $($Membership.substring($Index)) -replace 'DC=','' -replace ',','.'
10918
10919 if ($GroupDomain.CompareTo($Domain)) {
10920 # if the group domain doesn't match the user domain, output
10921 $GroupName = $Membership.split(",")[0].split("=")[1]
10922 $ForeignUser = New-Object PSObject
10923 $ForeignUser | Add-Member Noteproperty 'UserDomain' $Domain
10924 $ForeignUser | Add-Member Noteproperty 'UserName' $_.samaccountname
10925 $ForeignUser | Add-Member Noteproperty 'GroupDomain' $GroupDomain
10926 $ForeignUser | Add-Member Noteproperty 'GroupName' $GroupName
10927 $ForeignUser | Add-Member Noteproperty 'GroupDN' $Membership
10928 $ForeignUser
10929 }
10930 }
10931 }
10932 }
10933 }
10934
10935 if ($Recurse) {
10936 # get all rechable domains in the trust mesh and uniquify them
10937 if($LDAP -or $DomainController) {
10938 $DomainTrusts = Invoke-MapDomainTrust -LDAP -DomainController $DomainController -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique
10939 }
10940 else {
10941 $DomainTrusts = Invoke-MapDomainTrust -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique
10942 }
10943
10944 ForEach($DomainTrust in $DomainTrusts) {
10945 # get the trust groups for each domain in the trust mesh
10946 Write-Verbose "Enumerating trust groups in domain $DomainTrust"
10947 Get-ForeignUser -Domain $DomainTrust -UserName $UserName -PageSize $PageSize
10948 }
10949 }
10950 else {
10951 Get-ForeignUser -Domain $Domain -DomainController $DomainController -UserName $UserName -PageSize $PageSize
10952 }
10953}
10954
10955
10956function Find-ForeignGroup {
10957<#
10958 .SYNOPSIS
10959
10960 Enumerates all the members of a given domain's groups
10961 and finds users that are not in the queried domain.
10962 The -Recurse flag will perform this enumeration for all
10963 eachable domain trusts.
10964
10965 .PARAMETER GroupName
10966
10967 Groupname to filter results for, wildcards accepted.
10968
10969 .PARAMETER Domain
10970
10971 Domain to query for groups, defaults to the current domain.
10972
10973 .PARAMETER DomainController
10974
10975 Domain controller to reflect LDAP queries through.
10976
10977 .PARAMETER LDAP
10978
10979 Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections.
10980 More likely to get around network segmentation, but not as accurate.
10981
10982 .PARAMETER Recurse
10983
10984 Switch. Enumerate all group trust users from all reachable domains recursively.
10985
10986 .PARAMETER PageSize
10987
10988 The PageSize to set for the LDAP searcher object.
10989
10990 .LINK
10991
10992 http://blog.harmj0y.net/
10993#>
10994
10995 [CmdletBinding()]
10996 param(
10997 [String]
10998 $GroupName = '*',
10999
11000 [String]
11001 $Domain,
11002
11003 [String]
11004 $DomainController,
11005
11006 [Switch]
11007 $LDAP,
11008
11009 [Switch]
11010 $Recurse,
11011
11012 [ValidateRange(1,10000)]
11013 [Int]
11014 $PageSize = 200
11015 )
11016
11017 function Get-ForeignGroup {
11018 param(
11019 [String]
11020 $GroupName = '*',
11021
11022 [String]
11023 $Domain,
11024
11025 [String]
11026 $DomainController,
11027
11028 [ValidateRange(1,10000)]
11029 [Int]
11030 $PageSize = 200
11031 )
11032
11033 if(-not $Domain) {
11034 $Domain = (Get-NetDomain).Name
11035 }
11036
11037 $DomainDN = "DC=$($Domain.Replace('.', ',DC='))"
11038 Write-Verbose "DomainDN: $DomainDN"
11039
11040 # standard group names to ignore
11041 $ExcludeGroups = @("Users", "Domain Users", "Guests")
11042
11043 # get all the groupnames for the given domain
11044 Get-NetGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -FullData -PageSize $PageSize | Where-Object {$_.member} | Where-Object {
11045 # exclude common large groups
11046 -not ($ExcludeGroups -contains $_.samaccountname) } | ForEach-Object {
11047
11048 $GroupName = $_.samAccountName
11049
11050 $_.member | ForEach-Object {
11051 # filter for foreign SIDs in the cn field for users in another domain,
11052 # or if the DN doesn't end with the proper DN for the queried domain
11053 if (($_ -match 'CN=S-1-5-21.*-.*') -or ($DomainDN -ne ($_.substring($_.IndexOf("DC="))))) {
11054
11055 $UserDomain = $_.subString($_.IndexOf("DC=")) -replace 'DC=','' -replace ',','.'
11056 $UserName = $_.split(",")[0].split("=")[1]
11057
11058 $ForeignGroupUser = New-Object PSObject
11059 $ForeignGroupUser | Add-Member Noteproperty 'GroupDomain' $Domain
11060 $ForeignGroupUser | Add-Member Noteproperty 'GroupName' $GroupName
11061 $ForeignGroupUser | Add-Member Noteproperty 'UserDomain' $UserDomain
11062 $ForeignGroupUser | Add-Member Noteproperty 'UserName' $UserName
11063 $ForeignGroupUser | Add-Member Noteproperty 'UserDN' $_
11064 $ForeignGroupUser
11065 }
11066 }
11067 }
11068 }
11069
11070 if ($Recurse) {
11071 # get all rechable domains in the trust mesh and uniquify them
11072 if($LDAP -or $DomainController) {
11073 $DomainTrusts = Invoke-MapDomainTrust -LDAP -DomainController $DomainController -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique
11074 }
11075 else {
11076 $DomainTrusts = Invoke-MapDomainTrust -PageSize $PageSize | ForEach-Object { $_.SourceDomain } | Sort-Object -Unique
11077 }
11078
11079 ForEach($DomainTrust in $DomainTrusts) {
11080 # get the trust groups for each domain in the trust mesh
11081 Write-Verbose "Enumerating trust groups in domain $DomainTrust"
11082 Get-ForeignGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -PageSize $PageSize
11083 }
11084 }
11085 else {
11086 Get-ForeignGroup -GroupName $GroupName -Domain $Domain -DomainController $DomainController -PageSize $PageSize
11087 }
11088}
11089
11090
11091function Invoke-MapDomainTrust {
11092<#
11093 .SYNOPSIS
11094
11095 This function gets all trusts for the current domain,
11096 and tries to get all trusts for each domain it finds.
11097
11098 .PARAMETER LDAP
11099
11100 Switch. Use LDAP queries to enumerate the trusts instead of direct domain connections.
11101 More likely to get around network segmentation, but not as accurate.
11102
11103 .PARAMETER DomainController
11104
11105 Domain controller to reflect LDAP queries through.
11106
11107 .PARAMETER PageSize
11108
11109 The PageSize to set for the LDAP searcher object.
11110
11111 .EXAMPLE
11112
11113 PS C:\> Invoke-MapDomainTrust | Export-CSV -NoTypeInformation trusts.csv
11114
11115 Map all reachable domain trusts and output everything to a .csv file.
11116
11117 .LINK
11118
11119 http://blog.harmj0y.net/
11120#>
11121 [CmdletBinding()]
11122 param(
11123 [Switch]
11124 $LDAP,
11125
11126 [String]
11127 $DomainController,
11128
11129 [ValidateRange(1,10000)]
11130 [Int]
11131 $PageSize = 200
11132 )
11133
11134 # keep track of domains seen so we don't hit infinite recursion
11135 $SeenDomains = @{}
11136
11137 # our domain status tracker
11138 $Domains = New-Object System.Collections.Stack
11139
11140 # get the current domain and push it onto the stack
11141 $CurrentDomain = (Get-NetDomain).Name
11142 $Domains.push($CurrentDomain)
11143
11144 while($Domains.Count -ne 0) {
11145
11146 $Domain = $Domains.Pop()
11147
11148 # if we haven't seen this domain before
11149 if (-not $SeenDomains.ContainsKey($Domain)) {
11150
11151 Write-Verbose "Enumerating trusts for domain '$Domain'"
11152
11153 # mark it as seen in our list
11154 $Null = $SeenDomains.add($Domain, "")
11155
11156 try {
11157 # get all the trusts for this domain
11158 if($LDAP -or $DomainController) {
11159 $Trusts = Get-NetDomainTrust -Domain $Domain -LDAP -DomainController $DomainController -PageSize $PageSize
11160 }
11161 else {
11162 $Trusts = Get-NetDomainTrust -Domain $Domain -PageSize $PageSize
11163 }
11164
11165 if($Trusts -isnot [system.array]) {
11166 $Trusts = @($Trusts)
11167 }
11168
11169 # get any forest trusts, if they exist
11170 $Trusts += Get-NetForestTrust -Forest $Domain
11171
11172 if ($Trusts) {
11173
11174 # enumerate each trust found
11175 ForEach ($Trust in $Trusts) {
11176 $SourceDomain = $Trust.SourceName
11177 $TargetDomain = $Trust.TargetName
11178 $TrustType = $Trust.TrustType
11179 $TrustDirection = $Trust.TrustDirection
11180
11181 # make sure we process the target
11182 $Null = $Domains.push($TargetDomain)
11183
11184 # build the nicely-parsable custom output object
11185 $DomainTrust = New-Object PSObject
11186 $DomainTrust | Add-Member Noteproperty 'SourceDomain' "$SourceDomain"
11187 $DomainTrust | Add-Member Noteproperty 'TargetDomain' "$TargetDomain"
11188 $DomainTrust | Add-Member Noteproperty 'TrustType' "$TrustType"
11189 $DomainTrust | Add-Member Noteproperty 'TrustDirection' "$TrustDirection"
11190 $DomainTrust
11191 }
11192 }
11193 }
11194 catch {
11195 Write-Warning "[!] Error: $_"
11196 }
11197 }
11198 }
11199}
11200
11201
11202########################################################
11203#
11204# Expose the Win32API functions and datastructures below
11205# using PSReflect.
11206# Warning: Once these are executed, they are baked in
11207# and can't be changed while the script is running!
11208#
11209########################################################
11210
11211$Mod = New-InMemoryModule -ModuleName Win32
11212
11213# all of the Win32 API functions we need
11214$FunctionDefinitions = @(
11215 (func netapi32 NetShareEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
11216 (func netapi32 NetWkstaUserEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
11217 (func netapi32 NetSessionEnum ([Int]) @([String], [String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
11218 (func netapi32 NetApiBufferFree ([Int]) @([IntPtr])),
11219 (func advapi32 OpenSCManagerW ([IntPtr]) @([String], [String], [Int])),
11220 (func advapi32 CloseServiceHandle ([Int]) @([IntPtr])),
11221 (func wtsapi32 WTSOpenServerEx ([IntPtr]) @([String])),
11222 (func wtsapi32 WTSEnumerateSessionsEx ([Int]) @([IntPtr], [Int32].MakeByRefType(), [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType())),
11223 (func wtsapi32 WTSQuerySessionInformation ([Int]) @([IntPtr], [Int], [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType())),
11224 (func wtsapi32 WTSFreeMemoryEx ([Int]) @([Int32], [IntPtr], [Int32])),
11225 (func wtsapi32 WTSFreeMemory ([Int]) @([IntPtr])),
11226 (func wtsapi32 WTSCloseServer ([Int]) @([IntPtr])),
11227 (func kernel32 GetLastError ([Int]) @())
11228)
11229
11230# enum used by $WTS_SESSION_INFO_1 below
11231$WTSConnectState = psenum $Mod WTS_CONNECTSTATE_CLASS UInt16 @{
11232 Active = 0
11233 Connected = 1
11234 ConnectQuery = 2
11235 Shadow = 3
11236 Disconnected = 4
11237 Idle = 5
11238 Listen = 6
11239 Reset = 7
11240 Down = 8
11241 Init = 9
11242}
11243
11244# the WTSEnumerateSessionsEx result structure
11245$WTS_SESSION_INFO_1 = struct $Mod WTS_SESSION_INFO_1 @{
11246 ExecEnvId = field 0 UInt32
11247 State = field 1 $WTSConnectState
11248 SessionId = field 2 UInt32
11249 pSessionName = field 3 String -MarshalAs @('LPWStr')
11250 pHostName = field 4 String -MarshalAs @('LPWStr')
11251 pUserName = field 5 String -MarshalAs @('LPWStr')
11252 pDomainName = field 6 String -MarshalAs @('LPWStr')
11253 pFarmName = field 7 String -MarshalAs @('LPWStr')
11254}
11255
11256# the particular WTSQuerySessionInformation result structure
11257$WTS_CLIENT_ADDRESS = struct $mod WTS_CLIENT_ADDRESS @{
11258 AddressFamily = field 0 UInt32
11259 Address = field 1 Byte[] -MarshalAs @('ByValArray', 20)
11260}
11261
11262# the NetShareEnum result structure
11263$SHARE_INFO_1 = struct $Mod SHARE_INFO_1 @{
11264 shi1_netname = field 0 String -MarshalAs @('LPWStr')
11265 shi1_type = field 1 UInt32
11266 shi1_remark = field 2 String -MarshalAs @('LPWStr')
11267}
11268
11269# the NetWkstaUserEnum result structure
11270$WKSTA_USER_INFO_1 = struct $Mod WKSTA_USER_INFO_1 @{
11271 wkui1_username = field 0 String -MarshalAs @('LPWStr')
11272 wkui1_logon_domain = field 1 String -MarshalAs @('LPWStr')
11273 wkui1_oth_domains = field 2 String -MarshalAs @('LPWStr')
11274 wkui1_logon_server = field 3 String -MarshalAs @('LPWStr')
11275}
11276
11277# the NetSessionEnum result structure
11278$SESSION_INFO_10 = struct $Mod SESSION_INFO_10 @{
11279 sesi10_cname = field 0 String -MarshalAs @('LPWStr')
11280 sesi10_username = field 1 String -MarshalAs @('LPWStr')
11281 sesi10_time = field 2 UInt32
11282 sesi10_idle_time = field 3 UInt32
11283}
11284
11285
11286$Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32'
11287$Netapi32 = $Types['netapi32']
11288$Advapi32 = $Types['advapi32']
11289$Kernel32 = $Types['kernel32']
11290$Wtsapi32 = $Types['wtsapi32']
11291
11292
11293# aliases to help the PowerView 2.0 transition
11294Set-Alias Get-NetForestDomains Get-NetForestDomain
11295Set-Alias Get-NetDomainControllers Get-NetDomainController
11296Set-Alias Get-NetUserSPNs Get-NetUser
11297Set-Alias Invoke-NetUserAdd Add-NetUser
11298Set-Alias Invoke-NetGroupUserAdd Add-NetGroupUser
11299Set-Alias Get-NetComputers Get-NetComputer
11300Set-Alias Get-NetOUs Get-NetOU
11301Set-Alias Get-NetGUIDOUs Get-NetOU
11302Set-Alias Get-NetFileServers Get-NetFileServer
11303Set-Alias Get-NetSessions Get-NetSession
11304Set-Alias Get-NetRDPSessions Get-NetRDPSession
11305Set-Alias Get-NetProcesses Get-NetProcess
11306Set-Alias Get-UserLogonEvents Get-UserEvent
11307Set-Alias Get-UserTGTEvents Get-UserEvent
11308Set-Alias Get-UserProperties Get-UserProperty
11309Set-Alias Get-ComputerProperties Get-ComputerProperty
11310Set-Alias Invoke-UserHunterThreaded Invoke-UserHunter
11311Set-Alias Invoke-ProcessHunterThreaded Invoke-ProcessHunter
11312Set-Alias Invoke-ShareFinderThreaded Invoke-ShareFinder
11313Set-Alias Invoke-SearchFiles Find-InterestingFile
11314Set-Alias Invoke-UserFieldSearch Find-UserField
11315Set-Alias Invoke-ComputerFieldSearch Find-ComputerField
11316Set-Alias Invoke-FindLocalAdminAccess Find-LocalAdminAccess
11317Set-Alias Invoke-FindLocalAdminAccessThreaded Find-LocalAdminAccess
11318Set-Alias Get-NetDomainTrusts Get-NetDomainTrust
11319Set-Alias Get-NetForestTrusts Get-NetForestTrust
11320Set-Alias Invoke-MapDomainTrusts Invoke-MapDomainTrust
11321Set-Alias Invoke-FindUserTrustGroups Find-ForeignUser
11322Set-Alias Invoke-FindGroupTrustUsers Find-ForeignGroup
11323Set-Alias Invoke-EnumerateLocalTrustGroups Invoke-EnumerateLocalAdmin
11324Set-Alias Invoke-EnumerateLocalAdmins Invoke-EnumerateLocalAdmin
11325Set-Alias Invoke-EnumerateLocalAdminsThreaded Invoke-EnumerateLocalAdmin
11326Set-Alias Invoke-FindAllUserTrustGroups Find-ForeignUser
11327Set-Alias Find-UserTrustGroup Find-ForeignUser
11328Set-Alias Invoke-FindAllGroupTrustUsers Find-ForeignGroup