How to speed up PowerShell portscanner

Viewed 478

For various reasons, I started to write a PowerShell portscanner, not least to start learning it.

First iteration used Test-Netconnection. This seemed as if it would be too slow; so I went one level down to use sockets, specifically System.Net.Sockets.TcpClient. (Have started looking at System.Net.Sockets.Socket as the MS docs make mention of the Socket.BeginConnect() method which can begin an asynchronous request for a remote connection, but not sure if this will help yet.)

This still seemed too slow, so I looked at jobs. All this did was consume more resources for not much speed increase, so after much googling, I managed to make threading (or what PowerShell calls threading any way) work through the use of RunSpacePools. I thought it was pretty much done, and performance is ok if you're looking at an input file of 5 IP addresses. However, tried it out with a CIDR /24 this morning, and it took about 20-30 minutes.

[Edit] I should add that this script will take a 'thread' value, but if none is provided uses a default thread value of number of cores + 1 in order to take advantage of the RunSpacePool multithreading.

So I started looking at how Fyodor increased his speed, and in The Art of Scanning in PHRACK article 11 he states (whilst talking about TCP Connect() scanning):

While making a separate connect() call for every targeted port in a linear fashion would take ages over a slow connection, you can hasten the scan by using many sockets in parallel.

That is clearly where some optimisation is available.

So, is anyone able to point me in the direction of how I enable this - as I say, still quite new to PoSH, so am pushing the limits of my comprehension with RunSpacePools. Specifically, I would like some advice on a) if my instincts are right to increase the scan speed to increase socket parallelism, b) how to do that and c) if System.Net.Sockets.Socket is more appropriate.

function doConnect {
    $ipLoopCount = 0
    $portLoopCount = 0
    # check for randomise switch
    if ($randomise) {
        $ipArray = makeRange | Sort-Object {Get-Random}
        $portArray = makePortRange | Sort-Object {Get-Random}
    } else {
        # Connects to IPs in order
        $ipArray = makeRange
        $portArray = makePortRange
    }
    # initialise runspaces
    if ($threads) {
        $useThreads = $threads
    } else {
        $useThreads = ([int]$env:NUMBER_OF_PROCESSORS + 1)
    }
    $pool = [RunspaceFactory]::CreateRunspacePool(1, $useThreads)
    $pool.ApartmentState = "MTA"
    $pool.Open()
    $runspaces = @()
    # set higher priority for powershell process
    if ($priority) {
        $proc = Get-Process -Id $pid;
        $proc.PriorityClass = 'High'
    } else{
        $proc = Get-Process -Id $pid;
        $proc.PriorityClass = 'Normal'
    }

    # info object
    $infoDisplay = @{
        InputFile = $inFile
        Target_IPs = $ipArray
        Target_Ports = $portArray
        Process_Priority = $proc.PriorityClass
        Threads = $useThreads
    }
    [PSCustomObject]$infoDisplay
    # set up scriptblock to pass to runspaces
    $scriptblock = {
        Param (
            [IPAddress]$sb_ip,
            [int]$sb_port
        )
        # This progress bar doesn't work yet
        Write-Progress -Activity "Scan range $StartIPaddress - $EndIPAddress" -Status "% Complete:" -PercentComplete((($portLoopCount)/($ipArray.Length*$portArray.Length))*100)
        if ($delay) {
            $delay = Get-Random -Maximum 1000 -Minimum 1;
            Start-Sleep -m $delay
        }
        $socket = New-Object System.Net.Sockets.TcpClient
        $socket.Connect($sb_ip, $sb_Port)
        if ($socket.Connected) {
            Write-Output "Connected to $sb_port on $sb_ip"
        #} else {
        #    Write-Output "Failed to connect to port $sb_port on $sb_ip"
        }
        $socket.Close()
    }

    foreach ($nIP in $ipArray) {
        $ipLoopCount++
        foreach ($nPort in $portArray) {
            $portLoopCount++
            $runspace = [PowerShell]::Create()
            $null = $runspace.AddScript($scriptblock)
            $null = $runspace.AddArgument($nIP)
            $null = $runspace.AddArgument($nPort)
            $runspace.RunspacePool = $pool
            $runspaces += [PSCustomObject]@{
                Pipe = $runspace;
                Status = $runspace.BeginInvoke()
            }
        }
    }
    while ($runspaces.Status -ne $null) {
        $completed = $runspaces | Where-Object { $_.Status.IsCompleted -eq $true }
        foreach ($runspace in $completed) {
            $runspace.Pipe.EndInvoke($runspace.Status)
            $runspace.Status = $null
        }
    } 

    $pool.Close()
    $pool.Dispose()
}

It may be that PowerShell is entirely the wrong thing to attempt this in, but is a useful exercise as the environment is quite locked down, and installing a 'proper' portscanner - i.e. nmap - is impossible.

[Edit 2] I don't think reducing the timeout and plumbing that into the logic is the solutiuon that I'm after.

[Edit 3] The parallel switch didn't help.

[Edit 4] Have been thinking about Asynchronous socket connections, as this may help the overall connections speed - but then you have to have another thread/process looking after the incoming traffic. Unsure as to the efficacy.

0 Answers
Related