@@ -9304,11 +9304,16 @@ function Invoke-ThreadedFunction {
9304
9304
$Pool = [runspacefactory ]::CreateRunspacePool(1 , $Threads , $SessionState , $Host )
9305
9305
$Pool.Open ()
9306
9306
9307
- $Jobs = @ ()
9308
- $PS = @ ()
9309
- $Wait = @ ()
9307
+ $method = $null
9308
+ ForEach ($m in [PowerShell ].GetMethods() | Where-Object { $_.Name -eq " BeginInvoke" }) {
9309
+ $methodParameters = $m.GetParameters ()
9310
+ if (($methodParameters.Count -eq 2 ) -and $methodParameters [0 ].Name -eq " input" -and $methodParameters [1 ].Name -eq " output" ) {
9311
+ $method = $m.MakeGenericMethod ([Object ], [Object ])
9312
+ break
9313
+ }
9314
+ }
9310
9315
9311
- $Counter = 0
9316
+ $Jobs = @ ()
9312
9317
}
9313
9318
9314
9319
process {
@@ -9324,54 +9329,42 @@ function Invoke-ThreadedFunction {
9324
9329
}
9325
9330
9326
9331
# create a "powershell pipeline runner"
9327
- $PS + = [powershell ]::create()
9332
+ $p = [powershell ]::create()
9328
9333
9329
- $PS [ $Counter ] .runspacepool = $Pool
9334
+ $p .runspacepool = $Pool
9330
9335
9331
9336
# add the script block + arguments
9332
- $Null = $PS [ $Counter ] .AddScript($ScriptBlock ).AddParameter(' ComputerName' , $Computer )
9337
+ $Null = $p .AddScript ($ScriptBlock ).AddParameter(' ComputerName' , $Computer )
9333
9338
if ($ScriptParameters ) {
9334
9339
ForEach ($Param in $ScriptParameters.GetEnumerator ()) {
9335
- $Null = $PS [ $Counter ] .AddParameter($Param.Name , $Param.Value )
9340
+ $Null = $p .AddParameter ($Param.Name , $Param.Value )
9336
9341
}
9337
9342
}
9338
9343
9339
- # start job
9340
- $Jobs += $PS [$Counter ].BeginInvoke();
9344
+ $o = New-Object Management.Automation.PSDataCollection[Object ]
9341
9345
9342
- # store wait handles for WaitForAll call
9343
- $Wait += $Jobs [$Counter ].AsyncWaitHandle
9346
+ $Jobs += @ {
9347
+ PS = $p
9348
+ Output = $o
9349
+ Result = $method.Invoke ($p , @ ($null , [Management.Automation.PSDataCollection [Object ]]$o ))
9350
+ }
9344
9351
}
9345
- $Counter = $Counter + 1
9346
9352
}
9347
9353
}
9348
9354
9349
9355
end {
9356
+ Write-Verbose " Waiting for threads to finish..."
9350
9357
9351
- Write-Verbose " Waiting for scanning threads to finish..."
9352
-
9353
- $WaitTimeout = Get-Date
9354
-
9355
- # set a 60 second timeout for the scanning threads
9356
- while ($ ($Jobs | Where-Object {$_.IsCompleted -eq $False }).count -gt 0 -and $ ($ ($ (Get-Date ) - $WaitTimeout ).totalSeconds) -lt 60 ) {
9357
- Start-Sleep - MilliSeconds 500
9358
+ Do {
9359
+ ForEach ($Job in $Jobs ) {
9360
+ $Job.Output.ReadAll ()
9358
9361
}
9362
+ } While (($Jobs | Where-Object { ! $_.Result.IsCompleted }).Count -gt 0 )
9359
9363
9360
- # end async call
9361
- for ($y = 0 ; $y -lt $Counter ; $y ++ ) {
9362
-
9363
- try {
9364
- # complete async job
9365
- $PS [$y ].EndInvoke($Jobs [$y ])
9366
-
9367
- } catch {
9368
- Write-Warning " error: $_ "
9369
- }
9370
- finally {
9371
- $PS [$y ].Dispose()
9372
- }
9364
+ ForEach ($Job in $Jobs ) {
9365
+ $Job.PS.Dispose ()
9373
9366
}
9374
-
9367
+
9375
9368
$Pool.Dispose ()
9376
9369
Write-Verbose " All threads completed!"
9377
9370
}
@@ -9507,6 +9500,11 @@ function Invoke-UserHunter {
9507
9500
9508
9501
The maximum concurrent threads to execute.
9509
9502
9503
+ . PARAMETER Poll
9504
+
9505
+ Continuously poll for sessions for the given duration. Automatically
9506
+ sets Threads to the number of computers being polled.
9507
+
9510
9508
. EXAMPLE
9511
9509
9512
9510
PS C:\> Invoke-UserHunter -CheckAccess
@@ -9561,6 +9559,13 @@ function Invoke-UserHunter {
9561
9559
Executes old Invoke-StealthUserHunter functionality, enumerating commonly
9562
9560
used servers and checking just sessions for each.
9563
9561
9562
+ . EXAMPLE
9563
+
9564
+ PS C:\> Invoke-UserHunter -Stealth -StealthSource DC -Poll 3600 -Delay 5 -ShowAll | ? { ! $_.UserName.EndsWith('$') }
9565
+
9566
+ Poll Domain Controllers in parallel for sessions for an hour, waiting five
9567
+ seconds before querying each DC again and filtering out computer accounts.
9568
+
9564
9569
. LINK
9565
9570
http://blog.harmj0y.net
9566
9571
#>
@@ -9650,7 +9655,10 @@ function Invoke-UserHunter {
9650
9655
9651
9656
[Int ]
9652
9657
[ValidateRange (1 , 100 )]
9653
- $Threads
9658
+ $Threads ,
9659
+
9660
+ [UInt32 ]
9661
+ $Poll = 0
9654
9662
)
9655
9663
9656
9664
begin {
@@ -9659,9 +9667,6 @@ function Invoke-UserHunter {
9659
9667
$DebugPreference = ' Continue'
9660
9668
}
9661
9669
9662
- # random object for delay
9663
- $RandNo = New-Object System.Random
9664
-
9665
9670
Write-Verbose " [*] Running Invoke-UserHunter with delay of $Delay "
9666
9671
9667
9672
# ####################################################
@@ -9732,6 +9737,14 @@ function Invoke-UserHunter {
9732
9737
}
9733
9738
}
9734
9739
9740
+ if ($Poll -gt 0 ) {
9741
+ Write-Verbose " [*] Polling for $Poll seconds. Automatically enabling threaded mode."
9742
+ if ($ComputerName.Count -gt 100 ) {
9743
+ throw " Too many hosts to poll! Try fewer than 100."
9744
+ }
9745
+ $Threads = $ComputerName.Count
9746
+ }
9747
+
9735
9748
# ####################################################
9736
9749
#
9737
9750
# Now we build the user target set
@@ -9829,97 +9842,54 @@ function Invoke-UserHunter {
9829
9842
9830
9843
# script block that enumerates a server
9831
9844
$HostEnumBlock = {
9832
- param ($ComputerName , $Ping , $TargetUsers , $CurrentUser , $Stealth , $DomainShortName )
9845
+ param ($ComputerName , $Ping , $TargetUsers , $CurrentUser , $Stealth , $DomainShortName , $Poll , $Delay , $Jitter )
9833
9846
9834
9847
# optionally check if the server is up first
9835
9848
$Up = $True
9836
9849
if ($Ping ) {
9837
9850
$Up = Test-Connection - Count 1 - Quiet - ComputerName $ComputerName
9838
9851
}
9839
9852
if ($Up ) {
9840
- if (! $DomainShortName ) {
9841
- # if we're not searching for foreign users, check session information
9842
- $Sessions = Get-NetSession - ComputerName $ComputerName
9843
- ForEach ($Session in $Sessions ) {
9844
- $UserName = $Session.sesi10_username
9845
- $CName = $Session.sesi10_cname
9846
-
9847
- if ($CName -and $CName.StartsWith (" \\" )) {
9848
- $CName = $CName.TrimStart (" \" )
9849
- }
9850
-
9851
- # make sure we have a result
9852
- if (($UserName ) -and ($UserName.trim () -ne ' ' ) -and (! ($UserName -match $CurrentUser ))) {
9853
-
9854
- $TargetUsers | Where-Object {$UserName -like $_.MemberName } | ForEach-Object {
9855
-
9856
- $IPAddress = @ (Get-IPAddress - ComputerName $ComputerName )[0 ].IPAddress
9857
- $FoundUser = New-Object PSObject
9858
- $FoundUser | Add-Member Noteproperty ' UserDomain' $_.MemberDomain
9859
- $FoundUser | Add-Member Noteproperty ' UserName' $UserName
9860
- $FoundUser | Add-Member Noteproperty ' ComputerName' $ComputerName
9861
- $FoundUser | Add-Member Noteproperty ' IPAddress' $IPAddress
9862
- $FoundUser | Add-Member Noteproperty ' SessionFrom' $CName
9863
-
9864
- # Try to resolve the DNS hostname of $Cname
9865
- try {
9866
- $CNameDNSName = [System.Net.Dns ]::GetHostEntry($CName ) | Select-Object - ExpandProperty HostName
9867
- $FoundUser | Add-Member NoteProperty ' SessionFromName' $CnameDNSName
9868
- }
9869
- catch {
9870
- $FoundUser | Add-Member NoteProperty ' SessionFromName' $Null
9871
- }
9872
-
9873
- # see if we're checking to see if we have local admin access on this machine
9874
- if ($CheckAccess ) {
9875
- $Admin = Invoke-CheckLocalAdminAccess - ComputerName $CName
9876
- $FoundUser | Add-Member Noteproperty ' LocalAdmin' $Admin.IsAdmin
9877
- }
9878
- else {
9879
- $FoundUser | Add-Member Noteproperty ' LocalAdmin' $Null
9880
- }
9881
- $FoundUser.PSObject.TypeNames.Add (' PowerView.UserSession' )
9882
- $FoundUser
9853
+ $Timer = [System.Diagnostics.Stopwatch ]::StartNew()
9854
+ $RandNo = New-Object System.Random
9855
+
9856
+ Do {
9857
+ if (! $DomainShortName ) {
9858
+ # if we're not searching for foreign users, check session information
9859
+ $Sessions = Get-NetSession - ComputerName $ComputerName
9860
+ ForEach ($Session in $Sessions ) {
9861
+ $UserName = $Session.sesi10_username
9862
+ $CName = $Session.sesi10_cname
9863
+
9864
+ if ($CName -and $CName.StartsWith (" \\" )) {
9865
+ $CName = $CName.TrimStart (" \" )
9883
9866
}
9884
- }
9885
- }
9886
- }
9887
- if (! $Stealth ) {
9888
- # if we're not 'stealthy', enumerate loggedon users as well
9889
- $LoggedOn = Get-NetLoggedon - ComputerName $ComputerName
9890
- ForEach ($User in $LoggedOn ) {
9891
- $UserName = $User.wkui1_username
9892
- # TODO: translate domain to authoratative name
9893
- # then match domain name ?
9894
- $UserDomain = $User.wkui1_logon_domain
9895
9867
9896
- # make sure wet have a result
9897
- if (($UserName ) -and ($UserName.trim () -ne ' ' )) {
9868
+ # make sure we have a result
9869
+ if (($UserName ) -and ($UserName.trim () -ne ' ' ) -and ( ! ( $UserName -match $CurrentUser ) )) {
9898
9870
9899
- $TargetUsers | Where-Object {$UserName -like $_.MemberName } | ForEach-Object {
9871
+ $TargetUsers | Where-Object {$UserName -like $_.MemberName } | ForEach-Object {
9900
9872
9901
- $Proceed = $True
9902
- if ($DomainShortName ) {
9903
- if ($DomainShortName.ToLower () -ne $UserDomain.ToLower ()) {
9904
- $Proceed = $True
9905
- }
9906
- else {
9907
- $Proceed = $False
9908
- }
9909
- }
9910
- if ($Proceed ) {
9911
9873
$IPAddress = @ (Get-IPAddress - ComputerName $ComputerName )[0 ].IPAddress
9912
9874
$FoundUser = New-Object PSObject
9913
- $FoundUser | Add-Member Noteproperty ' UserDomain' $UserDomain
9875
+ $FoundUser | Add-Member Noteproperty ' UserDomain' $_ .MemberDomain
9914
9876
$FoundUser | Add-Member Noteproperty ' UserName' $UserName
9915
9877
$FoundUser | Add-Member Noteproperty ' ComputerName' $ComputerName
9916
9878
$FoundUser | Add-Member Noteproperty ' IPAddress' $IPAddress
9917
- $FoundUser | Add-Member Noteproperty ' SessionFrom' $Null
9918
- $FoundUser | Add-Member Noteproperty ' SessionFromName' $Null
9879
+ $FoundUser | Add-Member Noteproperty ' SessionFrom' $CName
9880
+
9881
+ # Try to resolve the DNS hostname of $Cname
9882
+ try {
9883
+ $CNameDNSName = [System.Net.Dns ]::GetHostEntry($CName ) | Select-Object - ExpandProperty HostName
9884
+ $FoundUser | Add-Member NoteProperty ' SessionFromName' $CnameDNSName
9885
+ }
9886
+ catch {
9887
+ $FoundUser | Add-Member NoteProperty ' SessionFromName' $Null
9888
+ }
9919
9889
9920
9890
# see if we're checking to see if we have local admin access on this machine
9921
9891
if ($CheckAccess ) {
9922
- $Admin = Invoke-CheckLocalAdminAccess - ComputerName $ComputerName
9892
+ $Admin = Invoke-CheckLocalAdminAccess - ComputerName $CName
9923
9893
$FoundUser | Add-Member Noteproperty ' LocalAdmin' $Admin.IsAdmin
9924
9894
}
9925
9895
else {
@@ -9931,10 +9901,61 @@ function Invoke-UserHunter {
9931
9901
}
9932
9902
}
9933
9903
}
9934
- }
9904
+ if (! $Stealth ) {
9905
+ # if we're not 'stealthy', enumerate loggedon users as well
9906
+ $LoggedOn = Get-NetLoggedon - ComputerName $ComputerName
9907
+ ForEach ($User in $LoggedOn ) {
9908
+ $UserName = $User.wkui1_username
9909
+ # TODO: translate domain to authoratative name
9910
+ # then match domain name ?
9911
+ $UserDomain = $User.wkui1_logon_domain
9912
+
9913
+ # make sure wet have a result
9914
+ if (($UserName ) -and ($UserName.trim () -ne ' ' )) {
9915
+
9916
+ $TargetUsers | Where-Object {$UserName -like $_.MemberName } | ForEach-Object {
9917
+
9918
+ $Proceed = $True
9919
+ if ($DomainShortName ) {
9920
+ if ($DomainShortName.ToLower () -ne $UserDomain.ToLower ()) {
9921
+ $Proceed = $True
9922
+ }
9923
+ else {
9924
+ $Proceed = $False
9925
+ }
9926
+ }
9927
+ if ($Proceed ) {
9928
+ $IPAddress = @ (Get-IPAddress - ComputerName $ComputerName )[0 ].IPAddress
9929
+ $FoundUser = New-Object PSObject
9930
+ $FoundUser | Add-Member Noteproperty ' UserDomain' $UserDomain
9931
+ $FoundUser | Add-Member Noteproperty ' UserName' $UserName
9932
+ $FoundUser | Add-Member Noteproperty ' ComputerName' $ComputerName
9933
+ $FoundUser | Add-Member Noteproperty ' IPAddress' $IPAddress
9934
+ $FoundUser | Add-Member Noteproperty ' SessionFrom' $Null
9935
+ $FoundUser | Add-Member Noteproperty ' SessionFromName' $Null
9936
+
9937
+ # see if we're checking to see if we have local admin access on this machine
9938
+ if ($CheckAccess ) {
9939
+ $Admin = Invoke-CheckLocalAdminAccess - ComputerName $ComputerName
9940
+ $FoundUser | Add-Member Noteproperty ' LocalAdmin' $Admin.IsAdmin
9941
+ }
9942
+ else {
9943
+ $FoundUser | Add-Member Noteproperty ' LocalAdmin' $Null
9944
+ }
9945
+ $FoundUser.PSObject.TypeNames.Add (' PowerView.UserSession' )
9946
+ $FoundUser
9947
+ }
9948
+ }
9949
+ }
9950
+ }
9951
+ }
9952
+
9953
+ if ($Poll -gt 0 ) {
9954
+ Start-Sleep - Seconds $RandNo.Next ((1 - $Jitter )* $Delay , (1 + $Jitter )* $Delay )
9955
+ }
9956
+ } While ($Poll -gt 0 -and $Timer.Elapsed.TotalSeconds -lt $Poll )
9935
9957
}
9936
9958
}
9937
-
9938
9959
}
9939
9960
9940
9961
process {
@@ -9949,6 +9970,9 @@ function Invoke-UserHunter {
9949
9970
' CurrentUser' = $CurrentUser
9950
9971
' Stealth' = $Stealth
9951
9972
' DomainShortName' = $DomainShortName
9973
+ ' Poll' = $Poll
9974
+ ' Delay' = $Delay
9975
+ ' Jitter' = $Jitter
9952
9976
}
9953
9977
9954
9978
# kick off the threaded script block + arguments
@@ -9964,6 +9988,7 @@ function Invoke-UserHunter {
9964
9988
9965
9989
Write-Verbose " [*] Total number of active hosts: $ ( $ComputerName.count ) "
9966
9990
$Counter = 0
9991
+ $RandNo = New-Object System.Random
9967
9992
9968
9993
ForEach ($Computer in $ComputerName ) {
9969
9994
@@ -9973,7 +9998,7 @@ function Invoke-UserHunter {
9973
9998
Start-Sleep - Seconds $RandNo.Next ((1 - $Jitter )* $Delay , (1 + $Jitter )* $Delay )
9974
9999
9975
10000
Write-Verbose " [*] Enumerating server $Computer ($Counter of $ ( $ComputerName.count ) )"
9976
- $Result = Invoke-Command - ScriptBlock $HostEnumBlock - ArgumentList $Computer , $False , $TargetUsers , $CurrentUser , $Stealth , $DomainShortName
10001
+ $Result = Invoke-Command - ScriptBlock $HostEnumBlock - ArgumentList $Computer , $False , $TargetUsers , $CurrentUser , $Stealth , $DomainShortName , 0 , 0 , 0
9977
10002
$Result
9978
10003
9979
10004
if ($Result -and $StopOnSuccess ) {
0 commit comments