3
\$\begingroup\$

To learn some PowerShell I made a tool, I've made before in bash. It pings the subnet for arp values and checks if there is no weird behaviour.

function IPv4toBin ($i){
    $b = $i -split '\.' | ForEach-Object {[System.Convert]::ToString($_,2).PadLeft(8,'0')}
    return $b -join ""
}

function Get-IPs($subnet) 
{         
    #Split IP and subnet 
    $IP = ($Subnet -split "\/")[0] 
    $SubnetBits = ($Subnet -split "\/")[1] 
    $IPInBinary = IPv4toBin($IP)

    $HostBits = 32-$SubnetBits 
    $NetworkIDInBinary = $IPInBinary.Substring(0, $SubnetBits)         
    $HostIDInBinary = $IPInBinary.Substring($SubnetBits, $HostBits)         
    $HostIDInBinary = $HostIDInBinary -replace "1","0"

    $imax = [convert]::ToInt32(("1" * $HostBits),2) -1 

    $IPs = @()

    For ($i = 1 ; $i -le $imax ; $i++) 
    { 
        $NextHostIDInDecimal = ([convert]::ToInt32($HostIDInBinary,2) + $i) 
        $NextHostIDInBinary = [convert]::ToString($NextHostIDInDecimal,2) 
        $NoOfZerosToAdd = $HostIDInBinary.Length - $NextHostIDInBinary.Length 
        $NextHostIDInBinary = ("0" * $NoOfZerosToAdd) + $NextHostIDInBinary 
        $NextIPInBinary = $NetworkIDInBinary + $NextHostIDInBinary 

        $IP = @() 
        For ($x = 1 ; $x -le 4 ; $x++) 
        { 
            $StartCharNumber = ($x-1)*8 
            $IPOctetInBinary = $NextIPInBinary.Substring($StartCharNumber,8) 
            $IPOctetInDecimal = [convert]::ToInt32($IPOctetInBinary,2) 
            $IP += $IPOctetInDecimal 
        }  
        $IP = $IP -join "." 
        $IPs += $IP 

    } 
    return $IPs
}

function Check-Spoof($iplist)
{
    $gate_ip = ipconfig | Select-String "Default Gateway" | % { $_.ToString().Split(':')[1].Trim() }
    $gate_ip = $gate_ip[0]
    $gate_mac = arp -a $gate_ip | Select-String $gate_ip |  % { $_.ToString() -split "\s+" }
    $gate_mac = $gate_mac[2].ToUpper()

    echo ""
    echo "Gateway with ip: $gate_ip and mac: $gate_mac"
    echo ""

    for ($i = 0; $i -lt $iplist.Count; $i++) 
    {
        if ($iplist[$i] -ne $gate_ip)
        {
            $ip = $iplist[$i]
            $arp_call = arp -a $ip | Select-String "$ip"
            if ($arp_call -ne $null)
            {
                  $call = $arp_call -split "\s+"
                  $mac = $call[2].ToUpper()
                  echo "Active IP: $ip with MAC: $mac"

                  if ($mac -eq $gate_mac)
                  {
                     echo ""
                     echo "You are being Spoofed a duplicate ARP value found"
                     return
                  }
            }
        }
    }
    echo ""
    echo "You are Safe, no duplicate ARP values found."
}

$subnet = "192.168.1.1/24"
$IPLIST = Get-IPs($subnet)
Check-Spoof($IPLIST)

I am fairly new to PowerShell, so I'm mostly concerned about the proper syntax.

\$\endgroup\$

2 Answers 2

5
\$\begingroup\$
  • split and spread to variables:

    $IP, $SubnetBits = $Subnet -split '/'
    
  • your user-function invocation with parentheses is incorrect and works only for single parameter (basically it's interpreted as user-function (param1), the correct syntax is user-function param1 param2 param3 e.g. IPv4toBin $IP

  • to get a power of 2 use a bitwise left shift:

    $imax = (1 -shl $HostBits) - 2
    
  • use $array = foreach ($i in 1..$imax) { ... } to generate arrays directly instead of overly verbose for loops and += operator, which recreates the entire array each time

  • ipconfig may list many networks with empty gateways so you need to account for that or use .NET method (C# source):

    $gate_ip = [Net.NetworkInformation.NetworkInterface]::GetAllNetworkInterfaces() |
      where { $_.OperationalStatus -eq 'Up' -and $_.NetworkInterfaceType -ne 'Loopback' }|
      foreach { $_.GetIPProperties().GatewayAddresses } |
      select -expand Address -First 1 |
      select -expand IPAddressToString
    
  • Select-String $gate_ip will incorrectly select the header line with no ARP info in case the interface IP address matches the gateway IP e.g. 192.168.1.1 and 192.168.1.176

  • work with actual numbers instead of binary representation and use bitwise operators:

    function Get-IPs($subnet) {     
      $IP, $SubnetBits = $Subnet -split '/'
      [uint32]$imax = (1 -shl (32 - $SubnetBits)) - 2
      [uint32]$baseIP = 0
      foreach ($s in ($IP -split '\.')) {
        $baseIP = ($baseIP -shl 8) + $s
      }
      $baseIP = $baseIP -band -bnot ($imax + 1)
      foreach ($i in 1..$imax) { 
        $IP = $baseIP + $i
        "$($IP -shr 24).$($IP -shr 16 -band 255).$($IP -shr 8 -band 255).$($IP -band 255)"
      } 
    }
    

    I'm using implicit output here: a statement that consists only of a value (or an expression) is equal to Write-Output $value_or_expression and the entire collected output is returned from the function without the need for an explicit return

\$\endgroup\$
2
\$\begingroup\$

Instead of screen-scraping ipconfig, you can use native PowerShell commands to get the default gateway:

Get-NetIPConfiguration | % IPv4DefaultGateway | select -First 1 -ExpandProperty NextHop

There may be an arp equivalent too. I haven't looked.


This is not PowerShell specific, but as a general rule in any programming language, it's best to have a function that operates on a single item rather than on a list of items. That removes a lot of noise in the function and makes it more readable and flexible. Something like this:

function Check-Spoof($gate_ip, $gate_mac, $ip)
{
    if ($ip -ne $gate_ip)
    {
        $arp_call = arp -a $ip | Select-String "$ip"

        if ($arp_call -ne $null)
        {
              $call = $arp_call -split "\s+"
              $mac = $call[2].ToUpper()
              echo "Active IP: $ip with MAC: $mac"

              if ($mac -eq $gate_mac)
              {
                 echo ""
                 echo "You are being Spoofed a duplicate ARP value found"
                 return
              }
        }
    }
}

If you want a function that operates on a list of items, then you can define that function in terms of the single-item function

\$\endgroup\$
2
  • \$\begingroup\$ Id like to disagree on this, utilizing a function with Begin{}, Process{}, and End{} blocks would be better and allow for both lists and single items to be processed and will most likely execute faster as you reduce redundant processing. \$\endgroup\$
    – Nick W.
    Commented Jul 9, 2018 at 13:13
  • \$\begingroup\$ @NickW., clarity is in general the most important thing. You can put the for loop inside the function or outside of it. Outside of it is clearer. You shouldn't worry about micro-efficiency unless there is a demonstrated need for it. \$\endgroup\$
    – Dangph
    Commented Jul 9, 2018 at 14:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.