Skip to content
This repository was archived by the owner on Jan 21, 2021. It is now read-only.

Commit 5e2200b

Browse files
authored
Merge pull request #173 from joncave/user_polling
PowerView: Invoke-UserHunter -Poll
2 parents 01a289e + 9b365e8 commit 5e2200b

File tree

1 file changed

+138
-113
lines changed

1 file changed

+138
-113
lines changed

Recon/PowerView.ps1

Lines changed: 138 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -9304,11 +9304,16 @@ function Invoke-ThreadedFunction {
93049304
$Pool = [runspacefactory]::CreateRunspacePool(1, $Threads, $SessionState, $Host)
93059305
$Pool.Open()
93069306

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+
}
93109315

9311-
$Counter = 0
9316+
$Jobs = @()
93129317
}
93139318

93149319
process {
@@ -9324,54 +9329,42 @@ function Invoke-ThreadedFunction {
93249329
}
93259330

93269331
# create a "powershell pipeline runner"
9327-
$PS += [powershell]::create()
9332+
$p = [powershell]::create()
93289333

9329-
$PS[$Counter].runspacepool = $Pool
9334+
$p.runspacepool = $Pool
93309335

93319336
# add the script block + arguments
9332-
$Null = $PS[$Counter].AddScript($ScriptBlock).AddParameter('ComputerName', $Computer)
9337+
$Null = $p.AddScript($ScriptBlock).AddParameter('ComputerName', $Computer)
93339338
if($ScriptParameters) {
93349339
ForEach ($Param in $ScriptParameters.GetEnumerator()) {
9335-
$Null = $PS[$Counter].AddParameter($Param.Name, $Param.Value)
9340+
$Null = $p.AddParameter($Param.Name, $Param.Value)
93369341
}
93379342
}
93389343

9339-
# start job
9340-
$Jobs += $PS[$Counter].BeginInvoke();
9344+
$o = New-Object Management.Automation.PSDataCollection[Object]
93419345

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+
}
93449351
}
9345-
$Counter = $Counter + 1
93469352
}
93479353
}
93489354

93499355
end {
9356+
Write-Verbose "Waiting for threads to finish..."
93509357

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()
93589361
}
9362+
} While (($Jobs | Where-Object { ! $_.Result.IsCompleted }).Count -gt 0)
93599363

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()
93739366
}
9374-
9367+
93759368
$Pool.Dispose()
93769369
Write-Verbose "All threads completed!"
93779370
}
@@ -9507,6 +9500,11 @@ function Invoke-UserHunter {
95079500
95089501
The maximum concurrent threads to execute.
95099502
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+
95109508
.EXAMPLE
95119509
95129510
PS C:\> Invoke-UserHunter -CheckAccess
@@ -9561,6 +9559,13 @@ function Invoke-UserHunter {
95619559
Executes old Invoke-StealthUserHunter functionality, enumerating commonly
95629560
used servers and checking just sessions for each.
95639561
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+
95649569
.LINK
95659570
http://blog.harmj0y.net
95669571
#>
@@ -9650,7 +9655,10 @@ function Invoke-UserHunter {
96509655

96519656
[Int]
96529657
[ValidateRange(1,100)]
9653-
$Threads
9658+
$Threads,
9659+
9660+
[UInt32]
9661+
$Poll = 0
96549662
)
96559663

96569664
begin {
@@ -9659,9 +9667,6 @@ function Invoke-UserHunter {
96599667
$DebugPreference = 'Continue'
96609668
}
96619669

9662-
# random object for delay
9663-
$RandNo = New-Object System.Random
9664-
96659670
Write-Verbose "[*] Running Invoke-UserHunter with delay of $Delay"
96669671

96679672
#####################################################
@@ -9732,6 +9737,14 @@ function Invoke-UserHunter {
97329737
}
97339738
}
97349739

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+
97359748
#####################################################
97369749
#
97379750
# Now we build the user target set
@@ -9829,97 +9842,54 @@ function Invoke-UserHunter {
98299842

98309843
# script block that enumerates a server
98319844
$HostEnumBlock = {
9832-
param($ComputerName, $Ping, $TargetUsers, $CurrentUser, $Stealth, $DomainShortName)
9845+
param($ComputerName, $Ping, $TargetUsers, $CurrentUser, $Stealth, $DomainShortName, $Poll, $Delay, $Jitter)
98339846

98349847
# optionally check if the server is up first
98359848
$Up = $True
98369849
if($Ping) {
98379850
$Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName
98389851
}
98399852
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("\")
98839866
}
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
98959867

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))) {
98989870

9899-
$TargetUsers | Where-Object {$UserName -like $_.MemberName} | ForEach-Object {
9871+
$TargetUsers | Where-Object {$UserName -like $_.MemberName} | ForEach-Object {
99009872

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) {
99119873
$IPAddress = @(Get-IPAddress -ComputerName $ComputerName)[0].IPAddress
99129874
$FoundUser = New-Object PSObject
9913-
$FoundUser | Add-Member Noteproperty 'UserDomain' $UserDomain
9875+
$FoundUser | Add-Member Noteproperty 'UserDomain' $_.MemberDomain
99149876
$FoundUser | Add-Member Noteproperty 'UserName' $UserName
99159877
$FoundUser | Add-Member Noteproperty 'ComputerName' $ComputerName
99169878
$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+
}
99199889

99209890
# see if we're checking to see if we have local admin access on this machine
99219891
if ($CheckAccess) {
9922-
$Admin = Invoke-CheckLocalAdminAccess -ComputerName $ComputerName
9892+
$Admin = Invoke-CheckLocalAdminAccess -ComputerName $CName
99239893
$FoundUser | Add-Member Noteproperty 'LocalAdmin' $Admin.IsAdmin
99249894
}
99259895
else {
@@ -9931,10 +9901,61 @@ function Invoke-UserHunter {
99319901
}
99329902
}
99339903
}
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)
99359957
}
99369958
}
9937-
99389959
}
99399960

99409961
process {
@@ -9949,6 +9970,9 @@ function Invoke-UserHunter {
99499970
'CurrentUser' = $CurrentUser
99509971
'Stealth' = $Stealth
99519972
'DomainShortName' = $DomainShortName
9973+
'Poll' = $Poll
9974+
'Delay' = $Delay
9975+
'Jitter' = $Jitter
99529976
}
99539977

99549978
# kick off the threaded script block + arguments
@@ -9964,6 +9988,7 @@ function Invoke-UserHunter {
99649988

99659989
Write-Verbose "[*] Total number of active hosts: $($ComputerName.count)"
99669990
$Counter = 0
9991+
$RandNo = New-Object System.Random
99679992

99689993
ForEach ($Computer in $ComputerName) {
99699994

@@ -9973,7 +9998,7 @@ function Invoke-UserHunter {
99739998
Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay)
99749999

997510000
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
997710002
$Result
997810003

997910004
if($Result -and $StopOnSuccess) {

0 commit comments

Comments
 (0)