Scripted Network Defense (Part 1) – Programmatic Defense of Windows Hosts Against Network Attacks

If you would like to read the next parts in this article series please go to:

Introduction

Defense-in-depth, or layered protection, is a basic tenet of security in both the online and physical worlds. In modern computing environments this translates into network, host and application-level security. Depending on the resource being protected, one or all may play a role. This article is the first of a series that focuses on network security as seen at the host level, an area typically managed by Windows systems administrators. While we respect and value our network admin colleagues, who may just be us wearing a different hat, they can’t always help us (for example, when we’re beyond the perimeter of our organizational LAN). Besides, defense-in-depth emphasizes that people working from multiple perspectives increase their ability to achieve common goals.

With many CPU cycles to spare, it is easy to bake an enhanced layer of security directly into modern PCs. Windows offers a rich toolset for effecting host hardening using programmatic techniques accessible to admins who know a little scripting.

What we’ll do in this series is take on some well-known network attacks and examine free, readily-available tools and scripting techniques that can help you respond to them. Even if a given vulnerability isn’t relevant in your environment, the methodologies we develop may be. My hope is as much to inspire your creativity as it is to demonstrate specific defenses.

Man-in-the-Middle

As an example of how you can build your own no-cost security tools for dealing with a nasty network security bug, we’ll take on the so-called Man-in-the-Middle ARP Cache Poisoning attack. ARP Cache poisoning is one of the oldest vulnerabilities on Ethernet LANs and goes back to the very origins of the protocol nearly 30 years ago. It has been discussed in many places, including an excellent series of articles by Chris Sanders on this publication’s sister site WindowsSecurity.com.

As a quick reminder, all Ethernet devices maintain a logical (IP address) to physical (hardware address) mapping table called the ARP Cache. This mapping is so fundamental to the operation of Ethernet LANs that it essentially works in reptilian brain mode, directly in the network stack, with little oversight by the rest of the operating system. The problem is that the ARP protocol is extremely simple (there are just two kinds of packets: request and reply) and was never designed with security in mind. Many implementations of the protocol, including the Windows OS network stack, will blindly update the ARP Cache upon receipt of an ARP reply packet such as the one seen in this Wireshark capture:


Figure 1: Wireshark capture showing an ARP Reply packet

Upon receipt of this particular packet, many hosts will automatically update their ARP Cache with the Sender’s MAC and IP addresses. The problem is that while the Target info in this packet is guaranteed to be truthful (how else would we have received it?) there’s nothing to assure that the Sender info is legitimate. The ARP protocol was never included an authentication mechanism (compare it to DNS, another name mapping protocol that also started life as a trusting naif but that now includes robust security capabilities such as DNSSec). Either or both of the Sender’s IP and MAC addresses might be forged.

In this particular case, the packet was generated by the well-known security/hacking tool Ettercap, which forged the IP address but used a valid Ethernet address. The purpose of doing this was to masquerade as a different Layer 3 host and redirect an unsuspecting PC’s network traffic to a machine to which it did not rightfully belong. This redirection attack is known as ARP Cache Poisoning.

In most cases the attacker will pass the hijacked traffic on to its original destination, with the sender being none the wiser. This is known in the business as a Man-in-the-Middle (MITM) attack. The point of a MITM attack, of course, is to enable an attacker to surreptitiously intercept the victim’s network traffic, perhaps capturing and saving it to disk for leisurely post-attack examination.

MITM ARP Cache Poisoning Defenses

There are a variety of defenses to ARP Cache Poisoning. First, not all host operating systems accept unsolicited ARP replies. From my own tests, Ubuntu Linux and MacOS ignore the reply packet above if they haven’t issued a previous ARP request themselves. Second, in controlled LAN environments switches can be programmed to ignore or refuse to pass on ARP replies that don’t meet certain criteria (in the Cisco world this is known as Dynamic ARP Inspection). Third, some host-level firewalls provide stateful monitoring and hence the ability to reject unsolicited ARP replies (notably, the built-in Windows firewall does not). Fourth, various third party source intrusion detection tools can help. And fifth, static ARP entries can be used for certain critical resources (static entries can’t be overwritten by dynamic ARP replies).

Regarding the latter, it is commonly said that static ARP doesn’t scale well. In addition, modern virtual environments, with mobile VMs, provide challenges of their own. However, most LANs segment their server traffic to a separate VLAN and thus the only address that really counts from the point of view of a typical client PC is its default IP gateway. As just a single address, it is both much easier to program against (programmatic APIs exist for detecting it) and to protect.

What this means is that the majority of MITM ARP Cache Poisoning attacks can be defeated if just the ARP entry for the default gateway is protected.

We examine two ways of making a static ARP entry for the default gateway: one in environments which we control, the other for networks where we don’t. In both cases, we’ll do the job at boot time programmatically using Microsoft’s free, built-in PowerShell scripting environment.

Setting a static ARP entry via config file

For the controlled environment case, such as your own LAN, here are our key design criteria:

  • we want to easily be able to update the gateway MAC address should this become necessary, e.g. if router interfaces get replaced
  • since there may be multiple NICs in a machine, there may be more than one default gateway whose mapping needs to be protected

We’ll maintain a centralized list of IP-MAC mappings for the organization’s default gateways and store it on a universally-accessible file share, for example \\admsrv\scripts. Only a single file needs to be updated in order for any changes to be made instantly available. Here’s an example config file:

IP,Ethernet

192.168.1.1,00-18-3a-e1-40-b5

192.168.2.1,00-0c-29-eb-ae-57

192.168.3.1,00-0c-29-42-82-95

192.168.4.1,00-50-56-4b-fb-c5

Recent versions of Windows don’t appear to permit the use of the old “arp –s” to add static ARP entries, preferring instead the modern netsh.exe command. We’ll first use some Windows Management Instrumentation (WMI) tricks to pull out info we’ll need as arguments to netsh.exe, combine it with the config file info, and finally hand craft and execute the netsh command for adding a static ARP entry. (Note that we use an argument “store=active”. This makes the ARP entry static just for the current bootup instead of persistent across bootups. This is a design choice to allow us to keep a modicum of flexibility while still giving us the security of static ARP.)

Here is a PowerShell script that does the trick:

# Obtain MAC address of IP gateway(s), look up corresponding MAC address in a

# config file, and set static ARP entry

$ARPTable = @{}

$strComputer = “.”

$arrAdapters = @()

$arrIPGW   = @()

# Load desired mappings from config file

$arpdata = Import-Csv -Path \\admsrv\\scripts\mactable.csv -Header “IP”,”MAC”

# Create hash lookup table of default gateway mappings

foreach ($entry in $arpdata)

{

  if ($entry.IP -match “\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}” -and

    $entry.MAC -match “((?:(\d{1,2}|[a-fA-F]{1,2}){2})(?::|-*)){6}” ) {

   $ARPTable[$entry.IP] = $entry.MAC

  }

}

# Look for active adapters

$NetworkAdapters = Get-WMIObject -class “Win32_NetworkAdapterConfiguration” `

  -namespace “root\CIMV2” -computername $strComputer -filter “IPEnabled = true”

# Iterate through all adapters and obtain all default IP gateways

foreach ($Adapter in $NetworkAdapters) {

  $arrAdapters += $Adapter.InterfaceIndex

  foreach ($IPGW in $Adapter.DefaultIPGateway) {

   if ($IPGW -notlike “*:*”) { # exclude IPv6 addresses

    $arrIPGW += $IPGW

   }

  }

}

$arrIPGW = $arrIPGW | select -uniq

# Purge existing gateway entries from ARP table

foreach ($gw in $arrIPGW) { (arp -d $gw) }

# Finally, get Interface IDs, which are required by netsh

foreach ($Adapter in $NetworkAdapters) {

  foreach ($IPGW in $Adapter.DefaultIPGateway) {

   if ($IPGW -notlike “*:*”) { # exclude IPv6 addresses

     $LANID = $Adapter.InterfaceIndex

     $MAC = $ARPTable[$IPGW]

     # Do a final sanity check to make sure IP and MAC addresses are properly formed,

     # then set static ARP entry. This check also catches the case where an IP address

     # wasn’t found in the MAC table config file.

     if ($IPGW -match “\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}” -and

       $MAC -match “((?:(\d{1,2}|[a-fA-F]{1,2}){2})(?::|-*)){6}”) {

      (netsh interface ipv4 add neighbors interface=$LANID $IPGW $MAC store=active)

     }

   }

  }

}

This script should run as close to boot time as possible in order to assure that a device is protected in a potentially hostile environment. One simple approach is to make it a startup script in one of your Group Policies, e.g.


Figure 2: Running a PowerShell startup script via Group Policy

In this case I’m using a batch file to launch PowerShell as you can’t run it directly as a startup script on older versions of Windows. I’m also using a local scripts directory (c:\admin\scripts) both to avoid potential issues with PowerShell executing off of a network share and to anticipate Example 2, where a PC may not even be on the corporate LAN.

Here’s the result of running the command interactively, showing us getting the default gateway, viewing the ARP table, and setting a static entry:


Figure 3: Interactive demo of setting static ARP entry for default gateway

A dynamic approach to static ARP entries

While the above works fine, it does require some management overhead and doesn’t take into account the common case of mobile devices booting up on unmanaged LANs, such as WiFi hotspots, other organizations’ networks, hotels, etc. Arguably the risk of a MITM attack is even greater in such cases, but how is one to guard against an attack where one has no way of knowing the default IP gateway in advance?

To devise a defense, first we must understand the modus operandi of ARP poisoning tools. In the most common, and aggressive, approach these tools send out forged reply packets on a continuous basis, even if the victim hasn’t issued an ARP request.  

One of the most popular network hacker tools, Ettercap, has a number of options for configuring its MITM ARP cache poisoning engine. By default, it first sends 5 quick forged ARP “reply” packets one second apart, then backs off to one every 10 seconds. These values are controlled, respectively, by the Ettercap configuration parameters arp_poison_warm_up and arp_poison_delay. These parameters can be made more aggressive but that increases the risk of being discovered by intrusion detection systems. Here’s what the attack looks like in Wireshark (note the time intervals between packets):


Figure 4: An Ettercap MITM ARP poisoning attack as seen by Wireshark

During the long term background ARP poisoning phase, a PC thus has at most 10 seconds to set a valid ARP entry before it receives a poison packet. Assuming it is able to do so, we then have a short time to read this dynamic value and rewrite it as static. So here is our plan:

  1. obtain a list of default gateway(s)
  2. purge any existing ARP table entries for them
  3. immediately send a single ping packet to the gateway to force a fresh ARP lookup
  4. assume that the dynamic ARP mapping that was made in step 3 is legitimate and rewrite it as a static ARP entry

A race condition between us and the attacker prevails during steps 3 and 4 so we can’t guarantee 100% success. But the odds are heavily in our favor (only a fraction of a second separates steps 3 and 4) and besides, if we do nothing, in a poorly managed or unmanaged network environment where other layers of defense are lacking, the attacker will win by default.

Here is some code that can implement our host-level defense strategy (to get this script to run at bootup when a PC is not on the corporate LAN, you may need to use Local Group Policies instead of the domain policy we used previously):

# Dynamically obtain MAC address of IP gateway(s) and set static ARP entry

$strComputer = “.”

$arrAdapters = @()

$arrIPGW   = @()

# Look for active adapters

$NetworkAdapters = Get-WMIObject -class “Win32_NetworkAdapterConfiguration” `

  -namespace “root\CIMV2” -computername $strComputer -filter “IPEnabled = true”

# Iterate through all adapters and obtain all default IP gateways

foreach ($Adapter in $NetworkAdapters) {

  $arrAdapters += $Adapter.InterfaceIndex

  foreach ($IPGW in $Adapter.DefaultIPGateway) {

   if ($IPGW -notlike “*:*”) { # exclude IPv6 addresses

    $arrIPGW += $IPGW

   }

  }

}

$arrIPGW = $arrIPGW | select -uniq

# We now purge the existing ARP table entries for any default gateways and use ping to quickly

# force the creation of a fresh ARP entry, which we assume is valid because most ARP poisoning

# tools operate in stealth mode and don’t actually listen for ARP requests (instead, they simply

# send out poison packets every few seconds). This means that the “real” device will respond to

# our ARP request. The only risk in this approach is the brief race condition that exists

# between the time the ping command updates the ARP Cache and the execution of our netsh “add

# neighbors” command.

foreach ($gw in $arrIPGW) {

 (arp -d $gw)   # Purge existing entry

 (ping -n 1 $gw) # Reinitialize

}

# Extract default gateway from each adapter

foreach ($Adapter in $NetworkAdapters) {

  foreach ($IPGW in $Adapter.DefaultIPGateway) {

   if ($IPGW -notlike “*:*”) { # exclude IPv6 addresses

     # Get interface ID – we’ll need this for our netsh command

     $LANID = $Adapter.InterfaceIndex

     # Get the MAC address associated with the newly-created ARP table entry for

     # the default IP gateway on this adapter

     $MAC = (netsh interface ipv4 show neighbors interface=”$LANID” address=$IPGW) `

      -match $IPGW | Foreach {($_ -split “\s+”)[1]}

     # Do a final sanity check to make sure IP and MAC addresses are properly formed,

     # then set static ARP entry. Store=active makes the entry static just for this

     # boot session.

     if ($IPGW -match “\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}” -and

       $MAC -match “((?:(\d{1,2}|[a-fA-F]{1,2}){2})(?::|-*)){6}”) {

      (netsh interface ipv4 add neighbors interface=$LANID $IPGW $MAC store=active)

     }

   }

  }

}

As a final note, there are other weaknesses in the ARP protocol that hackers regularly exploit. One is an attack on routers themselves (in this case, host-level security won’t help you) and another is ARP spoofing (where an attacker listens for ARP requests and engages in a race with the “real” host to issue an ARP reply). We have not dealt with these here but I did want to mention them in the interests of full disclosure.

Summing up, this article has given you a flavor of how to use built-in Windows scripting capabilities to tackle an insidious network security vulnerability. The upcoming second article in this series will demonstrate how to use Windows Performance Monitor for beefing up host-level network security.

If you would like to read the next parts in this article series please go to:

About The Author

Leave a Comment

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Scroll to Top