Part 1 – NetBIOS Names
NetBIOS is one of those legacy networking protocol suites that is still widely used. Don’t get the wrong idea about the “legacy” part, by the way. “Legacy” doesn’t always mean “old”—NetBIOS is actually more than ten years younger than TCP/IP suite which is powering the Internet. In this particular case, "legacy" means that NetBIOS is a thing of the past—no one will seriously consider using it in a new product. Still, it plays an important role in the Windows world by providing name resolution and discovery services (as well as the session layer below the SMB protocol—file and printer sharing, RPC, named pipes/mailslots, and so on). It is safe to assume that NetBIOS is here to stay for compatibility reasons. There still exists a substantial number of old PCs, file servers, and other devices that rely on NetBIOS, so built-in NetBIOS support is unlikely to be dropped from any major desktop operating system, not in the near future, at least.
Anatomy of NetBIOS
There is a lot of confusion about both the names (NetBIOS/NetBEUI/NBF/NBT/NBX) and provided functionality. It’s also really hard to find short samples—in C or other programming languages—that would demonstrate basic NetBIOS operations such as resolving a NetBIOS name or discovering all NetBIOS devices on a network segment, and the lack of samples always adds to the confusion.
So what is NetBIOS, anyway?
Originally, NetBIOS was designed by Sytec Inc as an API (application programming interface) for writing network programs for the very first PC LAN system in the world— the IBM PC Network in 1984. NetBIOS initially worked with Sytec proprietary network protocols. Subsequently, it was adapted to the IBM’s next big LAN architecture— TokenRing. This is when the complications with the names started—the new incarnation of NetBIOS API was called NetBEUI (NetBIOS Extended User Interface—what a horrible name choice!), and the accompanying protocol layer to enable NetBIOS over TokenRing networks was called NBF (NetBIOS frames). A year later a port for Novell Netware IPX networks appeared. This time, only one new name was introduced—the NBX protocol (NetBIOS over IPX/SPX). Finally, we get to something that emerged in 1987 and is still relevant today: the NetBIOS adaptation for TCP/IP networks and NBT protocol (NetBIOS over TCP/IP).
In summary, NetBIOS is an API and an umbrella name; NetBEUI essentially means the same thing (creating a new name for some incremental improvements was obviously the wrong thing to do), and NBF, NBX & NBT are protocols, of which only NBT remains relevant today.
NetBIOS features three groups of services:
- The name service
- The datagram service
- The session service
The session service was designed to provide connection-oriented communications and session semantics, but this functionality is not used that much now—beginning with Windows 2000 SMB can work directly over TCP port 445—with a thin residual layer of NBT session protocol on top of TCP.
The first two are used extensively in Windows Networking—for name resolution, in Microsoft Browser service for discovering neighbor computers, and in SMB stack. In this article I decided to give a practical and (hopefully) fun introduction to NetBIOS name service and neighboring computers discovery service. After reading the article, you will be able to perform certain NetBIOS and Computer Browser operations by manually sending and analyzing NBT packets.
What’s in the name?
NetBIOS name service (often abbreviated as NBNS), as you would guess, provides a framework for name resolution, registration and conflict detection. NetBIOS name is a string of 15 characters, with the last (16th) character reserved for a type suffix (0x00 for Workstations, 0x20 for File Servers, 0x1D for Master Browser, etc.). If a name is shorter than 15 characters, it is padded with spaces. Now let me ask you a question: how many bytes do you think it occupies in a packet? You would probably guess 16, 17 or 18 bytes (perhaps, there is an extra length field and a null-terminator—this would make sense). However, the correct answer is 34. I know, I was surprised, too. Here’s why.
NBT name service packets use DNS format for names, and it means that a full name is represented as a sequence of so-called labels up to 63 bytes in length each. A label starts with a special byte, which either has its two highest bits both cleared or both set. If these two bits are zeroes, then the lower 6 bits specify the length of the label that immediately follows (hence, the limitation of 63 bytes). If the highest two bits are ones, then the lower 6 bits together with the next byte contain a back-pointer to a previously defined name. If the sequence of labels does not end up with a back-pointer, it must be terminated by a zero byte.
So, let’s say we have a simple NetBIOS name that is a sequence of one—a single label. We start with the length byte, follow it by the NetBIOS name, and terminate with zero. Still not 34 bytes you say? Well, the explanation for this mystery is simple—NetBIOS label occupies 32 bytes, not 16. Each character is encoded using 2 bytes: each half-byte of the character’s ASCII code is added to the ASCII code ‘A’, so encoded NetBIOS name looks like a random sequence of characters between 'A' and 'P'. For example, "EEEFFGEFEMEPFAENEFEOFECACACACACA" stands for "DEVELOPMENT". What a brilliant way to minimize network traffic and at the same time make things easy to debug!
Now, how to convert a NetBIOS name to the IP address? Windows attempts the following steps to resolve a NetBIOS name:
- NetBIOS name cache
- WINS server query
- LLMNR multicast over UDP port 5355
- NBT broadcast over UDP port 137
- lmhosts file lookup
The first step is pretty obvious—Windows will start with checking whether it already knows this name from past efforts. The final step is pretty straightforward too—lmhosts is a special file with name mappings that are stored in Windows/System32/Drivers/Etc directory, so we open the file, look for our name if the mapping for this name is found, and hooray! We are done! What's less obvious is the fact that rather than doing this right after the cache lookup, Windows uses this file as the last resort. In any case, this method is inherently static by nature and is only applicable to very specific situations.
If your LAN has a WINS server, then a direct query to it would obviously be a preferred way of NetBIOS name resolution. However, many LANs, such as most home networks, don’t have one, so the final two steps provide a fallback mechanism that works for most ad-hoc LANs—the multicast/broadcast approach. We shout into the network: “Who bears THIS name?” And whoever does will send us a reply.
LLMNR stands for "link-local multicast name resolution". This is a relatively new protocol that uses multicast groups rather than broadcasts and is capable of resolving names to both IPv4 and IPv6 addresses (NetBIOS name service is incapable of dealing with IPv6). The detailed discussion of LLMNR is beyond the scope of this article.
Finally, there are NetBIOS name queries sent as broadcasts over UDP port 137. As a matter of fact, even when Windows is configured to use the WINS server, it still will fallback to NBT broadcast, should WINS query fail—just in case the node in question has not registered itself on WINS.
Resolving Names Manually
Now let’s get practical. For demonstration purposes, I will be using IO Ninja (http://tibbo.com/ninja). This is a programmable terminal from Tibbo Technology, and I find it very convenient for low-level IO experiments, such as broadcasting specially crafted binary packets. However, you are free to follow me with your favorite IO terminal. You can also create a simple C or Python test application and do everything programmatically.
First, let’s open a UDP Socket session and configure it for subnet broadcasts to UDP port 137.
Next, let’s define some essential NBT packet structures. The code below is in Jancy language – the native scripting language of IO Ninja. If you want to use it from your C program, minor adjustments will be required (Python would require even more adjustments, obviously):
alignment (1); enum NbnsOpcode { Query = 0, Registration = 5, Release = 6, Wack = 7, Refresh = 8, } bitflag enum NbnsFlags { Broadcast = 0x01, RecursionAvilable = 0x08, RecursionDesired = 0x10, Truncated = 0x20, AuthoritativeAnswer = 0x40, } struct NbnsHdr { bigendian uint16_t m_transactionId; bigendian uint16_t m_isResponse : 1; bigendian uint16_t m_opcode : 4; bigendian uint16_t m_flags : 7; bigendian uint16_t m_rcode : 4; bigendian uint16_t m_questionCount; bigendian uint16_t m_answerRecordCount; bigendian uint16_t m_authorityRecordCount; bigendian uint16_t m_additionalRecordCount; } enum NbnsQuestionType { General = 0x0020, NodeStatus = 0x0021, } enum NbnsQuestionClass { Internet = 0x0001, } struct NbnsQuestion { uint8_t m_nameLength; // 32 (0x20) char m_name [32]; char m_nameZeroTerminator; bigendian uint16_t m_type; bigendian uint16_t m_class; }
The name query request itself is a combination of a header and a question and looks like this:
struct NbnsNameQueryReq { NbnsHdr m_hdr; NbnsQuestion m_question; }
The most troublesome part is, of course, the process of encoding a NetBIOS name into that padded double-byte format. Doing this by hand would not be fun at all, so let’s write simple routines and make the computer do it for us. Also, I apply packetTemplateAction attribute to instruct IO Ninja to expose these methods to the end-user in the form of clickable hyperlinks:
encodeNetBiosChar ( char* buffer, char c ) { buffer [0] = 'A' + (c >> 4); buffer [1] = 'A' + (c & 0x0f); } encodeNetBiosName ( char* buffer, const char* name, char paddingChar = ' ', char typeSuffixChar = 0 ) { char* typeSuffix = buffer + 30; while (buffer < typeSuffix) { uchar_t c = *name++; if (!c) { while (buffer < typeSuffix) { encodeNetBiosChar (buffer, paddingChar); buffer += 2; } break; } encodeNetBiosChar (buffer, toupper (c)); buffer += 2; } encodeNetBiosChar (buffer, typeSuffixChar); } struct NbnsNameQueryReq { NbnsHdr m_hdr; NbnsQuestion m_question; [ packetTemplateAction ] void initialize () { m_hdr = null; m_hdr.m_opcode = NbnsOpcode.Query; m_hdr.m_flags = NbnsFlags.Broadcast | NbnsFlags.RecursionDesired; m_hdr.m_questionCount = 1; m_question = null; m_question.m_nameLength = countof (m_question.m_name); m_question.m_type = NbnsQuestionType.General; m_question.m_class = NbnsQuestionClass.Internet; } [ packetTemplateAction ] void setName (char const* name) { encodeNetBiosName (m_question.m_name, name); } }
There. Time to kick the tires. Copy-paste that code into the packet template editor (Settings->Transmit->Binary Transmit->Packet Template-> Edit Scratch Pad Library), then select NbnsNameQueryReq struct as a packet template, and you will be able to prepare outgoing packets simply by clicking hyperlinks (first initialize (), then setName () with the NetBIOS name of your choice, and then you can do more fine-tuning with the hex editor if you want).
You can see two packets. The first one is the outgoing packet in dark blue color. This is our carefully crafted NameQuery request for TIBBO-SERVER. The second is a reply from 192.168.1.80. Notice how it contains the same encoded NetBIOS name and also its IP in the last four bytes of the packet (c0 a8 01 50 means 192.168.1.80).
Here’s how this looks in Wireshark packet analyzer:
You can experiment and resolve other computer or workgroup names on your LAN and see how the process is affected by different type suffixes (the code above sets the type suffix to 0 – Workstation).
Getting a Name from IP
Now let’s reverse the process and try to find out a NetBIOS name from an IP-address. This is where the NodeStatus request comes into play. The packet format for this request is essentially the same. Hence, we can reuse struct NbnsNameQueryReq. The name field, however, must be formatted differently—after all, we don’t even know the actual name yet! Here’s a method for setting this up—just add it to the packet template NbnsNameQueryReq:
[ packetTemplateAction ] void setNodeStatusReq () { m_hdr.m_flags = 0; m_question.m_type = NbnsQuestionType.NodeStatus; encodeNetBiosName (m_question.m_name, "*", 0, 0); }
Unlike with the NameQuery request, we need to send the NodeStatus request to a particular IP, as most NetBIOS devices will simply ignore broadcast NodeStatus packets. So make sure you’ve set a valid unicast IP of some NetBIOS node in the Remote address edit box.
NodeStatus replies are rather complex. Each reply contains all the NetBIOS names and workgroups this IP is associated with, and also carries lots of statistics (which actually appear to be completely bogus as the values are always zeroed-out). I’m not going to paste the code for parsing the reply (you can find the packet definitions in the links below), but let me show you how a reply to our packet is decoded and displayed in Wireshark:
You can also notice that one of the names associated with this IP is __MSBROWSE__. The latter means that this computer was elected as a master browser of the group. More on that in the next installment of my article, It will describe the process of computer discovery in Windows.
Meanwhile, you can experiment with one of the methods of discovering neighbor NetBIOS devices around you—scanning a range of IP addresses with NodeStatus requests.
Useful Links
NetBIOS suffixes: https://support.microsoft.com/en-us/kb/163409
NetBIOS name encoding: https://support.microsoft.com/en-us/kb/194203
RFC specification for NetBIOS over TCP part 1: https://tools.ietf.org/html/rfc1001
RFC specification for NetBIOS over TCP part 2: https://tools.ietf.org/html/rfc1002
IO Ninja packet template for NetBIOS name query: http://www.tibbo.com/downloads/open/ioninja-plugin-samples/NetBiosPacketTemplates.jnc
Some extra NetBIOS-related defintions: http://www.tibbo.com/downloads/open/ioninja-plugin-samples/NetBios.jnc
Originally published at: dzone.com